Skip to content

Commit

Permalink
Added byte support to FreeTypeFont
Browse files Browse the repository at this point in the history
  • Loading branch information
radarhere committed Jun 15, 2024
1 parent cdd2bbe commit b3e3784
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 40 deletions.
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

0 comments on commit b3e3784

Please sign in to comment.