Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Tests/test_imagefont.py
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,23 @@ def test_too_many_characters(font: ImageFont.FreeTypeFont) -> None:
imagefont.getmask("A" * 1_000_001)


def test_bytes(font: ImageFont.FreeTypeFont) -> None:
assert font.getlength(b"test") == font.getlength("test")

assert font.getbbox(b"test") == font.getbbox("test")

assert_image_equal(
Image.Image()._new(font.getmask(b"test")),
Image.Image()._new(font.getmask("test")),
)

assert_image_equal(
Image.Image()._new(font.getmask2(b"test")[0]),
Image.Image()._new(font.getmask2("test")[0]),
)
assert font.getmask2(b"test")[1] == font.getmask2("test")[1]


@pytest.mark.parametrize(
"test_file",
[
Expand Down
8 changes: 4 additions & 4 deletions src/PIL/ImageFont.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ def getmetrics(self) -> tuple[int, int]:
return self.font.ascent, self.font.descent

def getlength(
self, text: str, mode="", direction=None, features=None, language=None
self, text: str | bytes, mode="", direction=None, features=None, language=None
) -> float:
"""
Returns length (in pixels with 1/64 precision) of given text when rendered
Expand Down Expand Up @@ -354,7 +354,7 @@ def getlength(

def getbbox(
self,
text: str,
text: str | bytes,
mode: str = "",
direction: str | None = None,
features: list[str] | None = None,
Expand Down Expand Up @@ -511,7 +511,7 @@ def getmask(

def getmask2(
self,
text: str,
text: str | bytes,
mode="",
direction=None,
features=None,
Expand Down Expand Up @@ -730,7 +730,7 @@ def getbbox(self, text, *args, **kwargs):
return 0, 0, height, width
return 0, 0, width, height

def getlength(self, text: str, *args, **kwargs) -> float:
def getlength(self, text: str | bytes, *args, **kwargs) -> float:
if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270):
msg = "text length is undefined for text rotated by 90 or 270 degrees"
raise ValueError(msg)
Expand Down
4 changes: 2 additions & 2 deletions src/PIL/_imagingft.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Font:
def glyphs(self) -> int: ...
def render(
self,
string: str,
string: str | bytes,
fill,
mode=...,
dir=...,
Expand All @@ -51,7 +51,7 @@ class Font:
/,
) -> tuple[tuple[int, int], tuple[int, int]]: ...
def getlength(
self, string: str, mode=..., dir=..., features=..., lang=..., /
self, string: str | bytes, mode=..., dir=..., features=..., lang=..., /
) -> float: ...
def getvarnames(self) -> list[bytes]: ...
def getvaraxes(self) -> list[_Axis] | None: ...
Expand Down
64 changes: 30 additions & 34 deletions src/_imagingft.c
Original file line number Diff line number Diff line change
Expand Up @@ -233,18 +233,6 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
return (PyObject *)self;
}

static int
font_getchar(PyObject *string, int index, FT_ULong *char_out) {
if (PyUnicode_Check(string)) {
if (index >= PyUnicode_GET_LENGTH(string)) {
return 0;
}
*char_out = PyUnicode_READ_CHAR(string, index);
return 1;
}
return 0;
}

#ifdef HAVE_RAQM

static size_t
Expand All @@ -266,28 +254,34 @@ text_layout_raqm(
goto failed;
}

Py_ssize_t size;
int set_text;
if (PyUnicode_Check(string)) {
Py_UCS4 *text = PyUnicode_AsUCS4Copy(string);
Py_ssize_t size = PyUnicode_GET_LENGTH(string);
size = PyUnicode_GET_LENGTH(string);
if (!text || !size) {
/* return 0 and clean up, no glyphs==no size,
and raqm fails with empty strings */
goto failed;
}
int set_text = raqm_set_text(rq, text, size);
set_text = raqm_set_text(rq, text, size);
PyMem_Free(text);
if (!set_text) {
PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
} else {
char *buffer;
PyBytes_AsStringAndSize(string, &buffer, &size);
if (!buffer || !size) {
/* return 0 and clean up, no glyphs==no size,
and raqm fails with empty strings */
goto failed;
}
if (lang) {
if (!raqm_set_language(rq, lang, start, size)) {
PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
goto failed;
}
}
} else {
PyErr_SetString(PyExc_TypeError, "expected string");
set_text = raqm_set_text_utf8(rq, buffer, size);
}
if (!set_text) {
PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
goto failed;
}
if (lang && !raqm_set_language(rq, lang, start, size)) {
PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
goto failed;
}

Expand Down Expand Up @@ -405,28 +399,25 @@ text_layout_fallback(
GlyphInfo **glyph_info,
int mask,
int color) {
int error, load_flags;
int error, load_flags, i;
char *buffer = NULL;
FT_ULong ch;
Py_ssize_t count;
FT_GlyphSlot glyph;
FT_Bool kerning = FT_HAS_KERNING(self->face);
FT_UInt last_index = 0;
int i;

if (features != Py_None || dir != NULL || lang != NULL) {
PyErr_SetString(
PyExc_KeyError,
"setting text direction, language or font features is not supported "
"without libraqm");
}
if (!PyUnicode_Check(string)) {
PyErr_SetString(PyExc_TypeError, "expected string");
return 0;
}

count = 0;
while (font_getchar(string, count, &ch)) {
count++;
if (PyUnicode_Check(string)) {
count = PyUnicode_GET_LENGTH(string);
} else {
PyBytes_AsStringAndSize(string, &buffer, &count);
}
if (count == 0) {
return 0;
Expand All @@ -445,7 +436,12 @@ text_layout_fallback(
if (color) {
load_flags |= FT_LOAD_COLOR;
}
for (i = 0; font_getchar(string, i, &ch); i++) {
for (i = 0; i < count; i++) {
if (buffer) {
ch = buffer[i];
} else {
ch = PyUnicode_READ_CHAR(string, i);
}
(*glyph_info)[i].index = FT_Get_Char_Index(self->face, ch);
error = FT_Load_Glyph(self->face, (*glyph_info)[i].index, load_flags);
if (error) {
Expand Down