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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@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")
 | 
			
		||||
def test_stroke_multiline():
 | 
			
		||||
    # Arrange
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -452,7 +452,11 @@ class ImageDraw:
 | 
			
		|||
            mode = self.fontmode
 | 
			
		||||
            if stroke_width == 0 and embedded_color:
 | 
			
		||||
                mode = "RGBA"
 | 
			
		||||
            coord = xy
 | 
			
		||||
            coord = []
 | 
			
		||||
            start = []
 | 
			
		||||
            for i in range(2):
 | 
			
		||||
                coord.append(int(xy[i]))
 | 
			
		||||
                start.append(math.modf(xy[i])[0])
 | 
			
		||||
            try:
 | 
			
		||||
                mask, offset = font.getmask2(
 | 
			
		||||
                    text,
 | 
			
		||||
| 
						 | 
				
			
			@ -463,6 +467,7 @@ class ImageDraw:
 | 
			
		|||
                    stroke_width=stroke_width,
 | 
			
		||||
                    anchor=anchor,
 | 
			
		||||
                    ink=ink,
 | 
			
		||||
                    start=start,
 | 
			
		||||
                    *args,
 | 
			
		||||
                    **kwargs,
 | 
			
		||||
                )
 | 
			
		||||
| 
						 | 
				
			
			@ -478,6 +483,7 @@ class ImageDraw:
 | 
			
		|||
                        stroke_width,
 | 
			
		||||
                        anchor,
 | 
			
		||||
                        ink,
 | 
			
		||||
                        start=start,
 | 
			
		||||
                        *args,
 | 
			
		||||
                        **kwargs,
 | 
			
		||||
                    )
 | 
			
		||||
| 
						 | 
				
			
			@ -490,7 +496,7 @@ class ImageDraw:
 | 
			
		|||
                # extract mask and set text alpha
 | 
			
		||||
                color, mask = mask, mask.getband(3)
 | 
			
		||||
                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)
 | 
			
		||||
            else:
 | 
			
		||||
                self.draw.draw_bitmap(coord, mask, ink)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,6 +26,7 @@
 | 
			
		|||
#
 | 
			
		||||
 | 
			
		||||
import base64
 | 
			
		||||
import math
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import warnings
 | 
			
		||||
| 
						 | 
				
			
			@ -588,6 +589,7 @@ class FreeTypeFont:
 | 
			
		|||
        stroke_width=0,
 | 
			
		||||
        anchor=None,
 | 
			
		||||
        ink=0,
 | 
			
		||||
        start=None,
 | 
			
		||||
    ):
 | 
			
		||||
        """
 | 
			
		||||
        Create a bitmap for the text.
 | 
			
		||||
| 
						 | 
				
			
			@ -659,6 +661,7 @@ class FreeTypeFont:
 | 
			
		|||
            stroke_width=stroke_width,
 | 
			
		||||
            anchor=anchor,
 | 
			
		||||
            ink=ink,
 | 
			
		||||
            start=start,
 | 
			
		||||
        )[0]
 | 
			
		||||
 | 
			
		||||
    def getmask2(
 | 
			
		||||
| 
						 | 
				
			
			@ -672,6 +675,7 @@ class FreeTypeFont:
 | 
			
		|||
        stroke_width=0,
 | 
			
		||||
        anchor=None,
 | 
			
		||||
        ink=0,
 | 
			
		||||
        start=None,
 | 
			
		||||
        *args,
 | 
			
		||||
        **kwargs,
 | 
			
		||||
    ):
 | 
			
		||||
| 
						 | 
				
			
			@ -750,12 +754,23 @@ class FreeTypeFont:
 | 
			
		|||
        size, offset = self.font.getsize(
 | 
			
		||||
            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
 | 
			
		||||
        Image._decompression_bomb_check(size)
 | 
			
		||||
        im = fill("RGBA" if mode == "RGBA" else "L", size, 0)
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -777,13 +777,15 @@ font_render(FontObject *self, PyObject *args) {
 | 
			
		|||
    const char *lang = NULL;
 | 
			
		||||
    PyObject *features = Py_None;
 | 
			
		||||
    PyObject *string;
 | 
			
		||||
    float x_start = 0;
 | 
			
		||||
    float y_start = 0;
 | 
			
		||||
 | 
			
		||||
    /* render string into given buffer (the buffer *must* have
 | 
			
		||||
       the right size, or this will crash) */
 | 
			
		||||
 | 
			
		||||
    if (!PyArg_ParseTuple(
 | 
			
		||||
            args,
 | 
			
		||||
            "On|zzOziL:render",
 | 
			
		||||
            "On|zzOziLff:render",
 | 
			
		||||
            &string,
 | 
			
		||||
            &id,
 | 
			
		||||
            &mode,
 | 
			
		||||
| 
						 | 
				
			
			@ -791,7 +793,9 @@ font_render(FontObject *self, PyObject *args) {
 | 
			
		|||
            &features,
 | 
			
		||||
            &lang,
 | 
			
		||||
            &stroke_width,
 | 
			
		||||
            &foreground_ink_long)) {
 | 
			
		||||
            &foreground_ink_long,
 | 
			
		||||
            &x_start,
 | 
			
		||||
            &y_start)) {
 | 
			
		||||
        return NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -876,8 +880,8 @@ font_render(FontObject *self, PyObject *args) {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    /* set pen position to text origin */
 | 
			
		||||
    x = (-x_min + stroke_width) << 6;
 | 
			
		||||
    y = (-y_max + (-stroke_width)) << 6;
 | 
			
		||||
    x = (-x_min + stroke_width + x_start) * 64;
 | 
			
		||||
    y = (-y_max + (-stroke_width) - y_start) * 64;
 | 
			
		||||
 | 
			
		||||
    if (stroker == NULL) {
 | 
			
		||||
        load_flags |= FT_LOAD_RENDER;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||