Skip to content

Commit 563f45c

Browse files
authored
Merge pull request #8141 from radarhere/freetypefont_bytes
2 parents 5b1a9e1 + 81c1bf1 commit 563f45c

File tree

4 files changed

+53
-40
lines changed

4 files changed

+53
-40
lines changed

Tests/test_imagefont.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,6 +1097,23 @@ def test_too_many_characters(font: ImageFont.FreeTypeFont) -> None:
10971097
imagefont.getmask("A" * 1_000_001)
10981098

10991099

1100+
def test_bytes(font: ImageFont.FreeTypeFont) -> None:
1101+
assert font.getlength(b"test") == font.getlength("test")
1102+
1103+
assert font.getbbox(b"test") == font.getbbox("test")
1104+
1105+
assert_image_equal(
1106+
Image.Image()._new(font.getmask(b"test")),
1107+
Image.Image()._new(font.getmask("test")),
1108+
)
1109+
1110+
assert_image_equal(
1111+
Image.Image()._new(font.getmask2(b"test")[0]),
1112+
Image.Image()._new(font.getmask2("test")[0]),
1113+
)
1114+
assert font.getmask2(b"test")[1] == font.getmask2("test")[1]
1115+
1116+
11001117
@pytest.mark.parametrize(
11011118
"test_file",
11021119
[

src/PIL/ImageFont.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ def getmetrics(self) -> tuple[int, int]:
283283
return self.font.ascent, self.font.descent
284284

285285
def getlength(
286-
self, text: str, mode="", direction=None, features=None, language=None
286+
self, text: str | bytes, mode="", direction=None, features=None, language=None
287287
) -> float:
288288
"""
289289
Returns length (in pixels with 1/64 precision) of given text when rendered
@@ -358,7 +358,7 @@ def getlength(
358358

359359
def getbbox(
360360
self,
361-
text: str,
361+
text: str | bytes,
362362
mode: str = "",
363363
direction: str | None = None,
364364
features: list[str] | None = None,
@@ -515,7 +515,7 @@ def getmask(
515515

516516
def getmask2(
517517
self,
518-
text: str,
518+
text: str | bytes,
519519
mode="",
520520
direction=None,
521521
features=None,
@@ -734,7 +734,7 @@ def getbbox(self, text, *args, **kwargs):
734734
return 0, 0, height, width
735735
return 0, 0, width, height
736736

737-
def getlength(self, text: str, *args, **kwargs) -> float:
737+
def getlength(self, text: str | bytes, *args, **kwargs) -> float:
738738
if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270):
739739
msg = "text length is undefined for text rotated by 90 or 270 degrees"
740740
raise ValueError(msg)

src/PIL/_imagingft.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class Font:
2727
def glyphs(self) -> int: ...
2828
def render(
2929
self,
30-
string: str,
30+
string: str | bytes,
3131
fill,
3232
mode=...,
3333
dir=...,
@@ -51,7 +51,7 @@ class Font:
5151
/,
5252
) -> tuple[tuple[int, int], tuple[int, int]]: ...
5353
def getlength(
54-
self, string: str, mode=..., dir=..., features=..., lang=..., /
54+
self, string: str | bytes, mode=..., dir=..., features=..., lang=..., /
5555
) -> float: ...
5656
def getvarnames(self) -> list[bytes]: ...
5757
def getvaraxes(self) -> list[_Axis] | None: ...

src/_imagingft.c

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -233,18 +233,6 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
233233
return (PyObject *)self;
234234
}
235235

236-
static int
237-
font_getchar(PyObject *string, int index, FT_ULong *char_out) {
238-
if (PyUnicode_Check(string)) {
239-
if (index >= PyUnicode_GET_LENGTH(string)) {
240-
return 0;
241-
}
242-
*char_out = PyUnicode_READ_CHAR(string, index);
243-
return 1;
244-
}
245-
return 0;
246-
}
247-
248236
#ifdef HAVE_RAQM
249237

250238
static size_t
@@ -266,28 +254,34 @@ text_layout_raqm(
266254
goto failed;
267255
}
268256

257+
Py_ssize_t size;
258+
int set_text;
269259
if (PyUnicode_Check(string)) {
270260
Py_UCS4 *text = PyUnicode_AsUCS4Copy(string);
271-
Py_ssize_t size = PyUnicode_GET_LENGTH(string);
261+
size = PyUnicode_GET_LENGTH(string);
272262
if (!text || !size) {
273263
/* return 0 and clean up, no glyphs==no size,
274264
and raqm fails with empty strings */
275265
goto failed;
276266
}
277-
int set_text = raqm_set_text(rq, text, size);
267+
set_text = raqm_set_text(rq, text, size);
278268
PyMem_Free(text);
279-
if (!set_text) {
280-
PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
269+
} else {
270+
char *buffer;
271+
PyBytes_AsStringAndSize(string, &buffer, &size);
272+
if (!buffer || !size) {
273+
/* return 0 and clean up, no glyphs==no size,
274+
and raqm fails with empty strings */
281275
goto failed;
282276
}
283-
if (lang) {
284-
if (!raqm_set_language(rq, lang, start, size)) {
285-
PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
286-
goto failed;
287-
}
288-
}
289-
} else {
290-
PyErr_SetString(PyExc_TypeError, "expected string");
277+
set_text = raqm_set_text_utf8(rq, buffer, size);
278+
}
279+
if (!set_text) {
280+
PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
281+
goto failed;
282+
}
283+
if (lang && !raqm_set_language(rq, lang, start, size)) {
284+
PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
291285
goto failed;
292286
}
293287

@@ -405,28 +399,25 @@ text_layout_fallback(
405399
GlyphInfo **glyph_info,
406400
int mask,
407401
int color) {
408-
int error, load_flags;
402+
int error, load_flags, i;
403+
char *buffer = NULL;
409404
FT_ULong ch;
410405
Py_ssize_t count;
411406
FT_GlyphSlot glyph;
412407
FT_Bool kerning = FT_HAS_KERNING(self->face);
413408
FT_UInt last_index = 0;
414-
int i;
415409

416410
if (features != Py_None || dir != NULL || lang != NULL) {
417411
PyErr_SetString(
418412
PyExc_KeyError,
419413
"setting text direction, language or font features is not supported "
420414
"without libraqm");
421415
}
422-
if (!PyUnicode_Check(string)) {
423-
PyErr_SetString(PyExc_TypeError, "expected string");
424-
return 0;
425-
}
426416

427-
count = 0;
428-
while (font_getchar(string, count, &ch)) {
429-
count++;
417+
if (PyUnicode_Check(string)) {
418+
count = PyUnicode_GET_LENGTH(string);
419+
} else {
420+
PyBytes_AsStringAndSize(string, &buffer, &count);
430421
}
431422
if (count == 0) {
432423
return 0;
@@ -445,7 +436,12 @@ text_layout_fallback(
445436
if (color) {
446437
load_flags |= FT_LOAD_COLOR;
447438
}
448-
for (i = 0; font_getchar(string, i, &ch); i++) {
439+
for (i = 0; i < count; i++) {
440+
if (buffer) {
441+
ch = buffer[i];
442+
} else {
443+
ch = PyUnicode_READ_CHAR(string, i);
444+
}
449445
(*glyph_info)[i].index = FT_Get_Char_Index(self->face, ch);
450446
error = FT_Load_Glyph(self->face, (*glyph_info)[i].index, load_flags);
451447
if (error) {

0 commit comments

Comments
 (0)