Skip to content

Commit 97e7a97

Browse files
committed
Added FreeTypeFont has_characters()
1 parent 9d39fe6 commit 97e7a97

File tree

4 files changed

+59
-0
lines changed

4 files changed

+59
-0
lines changed

Tests/test_imagefont.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,15 @@ def test_anchor_invalid(font: ImageFont.FreeTypeFont) -> None:
891891
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor)
892892

893893

894+
def test_has_characters(font: ImageFont.FreeTypeFont) -> None:
895+
assert font.has_characters("")
896+
897+
assert font.has_characters("Test text")
898+
assert font.has_characters(b"Test text")
899+
900+
assert not font.has_characters("Test \u0001")
901+
902+
894903
@pytest.mark.parametrize("bpp", (1, 2, 4, 8))
895904
def test_bitmap_font(layout_engine: ImageFont.Layout, bpp: int) -> None:
896905
text = "Bitmap Font"

src/PIL/ImageFont.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,16 @@ def fill(width: int, height: int) -> Image.core.ImagingCore:
636636
start,
637637
)
638638

639+
def has_characters(self, text: str | bytes) -> bool:
640+
"""
641+
Check if the font has all of the characters in the text.
642+
643+
:param text: Text to render.
644+
645+
:return: Boolean.
646+
"""
647+
return self.font.hascharacters(text)
648+
639649
def font_variant(
640650
self,
641651
font: StrOrBytesPath | BinaryIO | None = None,

src/PIL/_imagingft.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class Font:
5454
lang: str | None,
5555
/,
5656
) -> float: ...
57+
def hascharacters(self, string: str | bytes) -> bool: ...
5758
def getvarnames(self) -> list[bytes]: ...
5859
def getvaraxes(self) -> list[ImageFont.Axis]: ...
5960
def setvarname(self, instance_index: int, /) -> None: ...

src/_imagingft.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,44 @@ text_layout(
517517
return count;
518518
}
519519

520+
static PyObject *
521+
font_hascharacters(FontObject *self, PyObject *args) {
522+
int i;
523+
char *buffer = NULL;
524+
FT_ULong ch;
525+
Py_ssize_t count;
526+
FT_GlyphSlot glyph;
527+
PyObject *string;
528+
529+
if (!PyArg_ParseTuple(args, "O", &string)) {
530+
return NULL;
531+
}
532+
533+
if (PyUnicode_Check(string)) {
534+
count = PyUnicode_GET_LENGTH(string);
535+
} else if (PyBytes_Check(string)) {
536+
PyBytes_AsStringAndSize(string, &buffer, &count);
537+
} else {
538+
PyErr_SetString(PyExc_TypeError, "expected string or bytes");
539+
return 0;
540+
}
541+
if (count == 0) {
542+
return Py_True;
543+
}
544+
545+
for (i = 0; i < count; i++) {
546+
if (buffer) {
547+
ch = buffer[i];
548+
} else {
549+
ch = PyUnicode_READ_CHAR(string, i);
550+
}
551+
if (FT_Get_Char_Index(self->face, ch) == 0) {
552+
return Py_False;
553+
}
554+
}
555+
return Py_True;
556+
}
557+
520558
static PyObject *
521559
font_getlength(FontObject *self, PyObject *args) {
522560
int length; /* length along primary axis, in 26.6 precision */
@@ -1451,6 +1489,7 @@ static PyMethodDef font_methods[] = {
14511489
{"render", (PyCFunction)font_render, METH_VARARGS},
14521490
{"getsize", (PyCFunction)font_getsize, METH_VARARGS},
14531491
{"getlength", (PyCFunction)font_getlength, METH_VARARGS},
1492+
{"hascharacters", (PyCFunction)font_hascharacters, METH_VARARGS},
14541493
#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) || \
14551494
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
14561495
{"getvarnames", (PyCFunction)font_getvarnames, METH_NOARGS},

0 commit comments

Comments
 (0)