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;
|
||||||
|
|