From 9a680e812a13b1bb1ef440ae5f1422893dcfe2eb Mon Sep 17 00:00:00 2001 From: dr0id Date: Thu, 18 May 2023 20:36:11 +0200 Subject: [PATCH 1/3] font: add is_char_defined method in Font and Freetype --- buildconfig/stubs/pygame/font.pyi | 3 +- buildconfig/stubs/pygame/freetype.pyi | 1 + docs/reST/ref/font.rst | 11 ++++++ docs/reST/ref/freetype.rst | 12 ++++++ src_c/_freetype.c | 38 ++++++++++++++++++ src_c/doc/font_doc.h | 1 + src_c/doc/freetype_doc.h | 1 + src_c/font.c | 57 +++++++++++++++++++++++++++ test/font_test.py | 24 +++++++++++ 9 files changed, 147 insertions(+), 1 deletion(-) diff --git a/buildconfig/stubs/pygame/font.pyi b/buildconfig/stubs/pygame/font.pyi index b1795d14f5..559ad5afbc 100644 --- a/buildconfig/stubs/pygame/font.pyi +++ b/buildconfig/stubs/pygame/font.pyi @@ -41,7 +41,7 @@ class Font: antialias: bool, color: ColorValue, bgcolor: Optional[ColorValue] = None, - wraplength: int = 0 + wraplength: int = 0, ) -> Surface: ... def size(self, text: Union[str, bytes]) -> Tuple[int, int]: ... def set_underline(self, value: bool) -> None: ... @@ -59,6 +59,7 @@ class Font: def get_height(self) -> int: ... def get_ascent(self) -> int: ... def get_descent(self) -> int: ... + def is_char_defined(self, char: Union[str, bytes]) -> bool: ... def set_script(self, script_code: str) -> None: ... def set_direction(self, direction: int) -> None: ... diff --git a/buildconfig/stubs/pygame/freetype.pyi b/buildconfig/stubs/pygame/freetype.pyi index 3ab6cbed44..7e3ab97ace 100644 --- a/buildconfig/stubs/pygame/freetype.pyi +++ b/buildconfig/stubs/pygame/freetype.pyi @@ -85,6 +85,7 @@ class Font: ) -> List[Tuple[int, int, int, int, float, float]]: ... def get_sized_ascender(self, size: float) -> int: ... def get_sized_descender(self, size: float) -> int: ... + def is_char_defined(self, char: Union[str, bytes]) -> bool: ... def get_sized_height(self, size: float) -> int: ... def get_sized_glyph_height(self, size: float) -> int: ... def get_sizes(self) -> List[Tuple[int, int, int, float, float]]: ... diff --git a/docs/reST/ref/font.rst b/docs/reST/ref/font.rst index a95ee1e986..bf55fe3f47 100644 --- a/docs/reST/ref/font.rst +++ b/docs/reST/ref/font.rst @@ -275,6 +275,17 @@ solves no longer exists, it will likely be removed in the future. .. ## Font.align ## + .. method:: is_char_defined + + | :sl:`Check if a char is defined in the font.` + | :sg:`is_char_defined(char) -> bool` + + This checks if the char is defined in this font. + + .. versionadded:: 2.2.0 + + .. ## Font.is_char_defined ## + .. method:: render | :sl:`draw text on a new Surface` diff --git a/docs/reST/ref/freetype.rst b/docs/reST/ref/freetype.rst index 48f6e5e15e..c445d475de 100644 --- a/docs/reST/ref/freetype.rst +++ b/docs/reST/ref/freetype.rst @@ -379,6 +379,18 @@ loaded. This module must be imported explicitly to be used. :: width in pixels, horizontal ppem (nominal width) in fractional pixels, and vertical ppem (nominal height) in fractional pixels. + + .. method:: is_char_defined + + | :sl:`Check if a char is defined in the font.` + | :sg:`is_char_defined(char) -> bool` + + This checks if the char is defined in this font. + + .. versionadded:: 2.2.0 + + .. ## Font.is_char_defined ## + .. method:: render | :sl:`Return rendered text as a surface` diff --git a/src_c/_freetype.c b/src_c/_freetype.c index 2c7e698767..1bfe0ce278 100644 --- a/src_c/_freetype.c +++ b/src_c/_freetype.c @@ -81,6 +81,8 @@ _ftfont_getrect(pgFontObject *, PyObject *, PyObject *); static PyObject * _ftfont_getmetrics(pgFontObject *, PyObject *, PyObject *); static PyObject * +_ftfont_is_char_defined(pgFontObject *self, PyObject *textobj); +static PyObject * _ftfont_render(pgFontObject *, PyObject *, PyObject *); static PyObject * _ftfont_render_to(pgFontObject *, PyObject *, PyObject *); @@ -522,6 +524,8 @@ static PyMethodDef _ftfont_methods[] = { DOC_FREETYPE_FONT_GETRECT}, {"get_metrics", (PyCFunction)_ftfont_getmetrics, METH_VARARGS | METH_KEYWORDS, DOC_FREETYPE_FONT_GETMETRICS}, + {"is_char_defined", (PyCFunction)_ftfont_is_char_defined, METH_O, + DOC_FREETYPE_FONT_ISCHARDEFINED}, {"get_sizes", (PyCFunction)_ftfont_getsizes, METH_NOARGS, DOC_FREETYPE_FONT_GETSIZES}, {"render", (PyCFunction)_ftfont_render, METH_VARARGS | METH_KEYWORDS, @@ -1418,6 +1422,40 @@ _ftfont_getmetrics(pgFontObject *self, PyObject *args, PyObject *kwds) return 0; } +static PyObject * +_ftfont_is_char_defined(pgFontObject *self, PyObject *textobj) +{ + PGFT_String *text = 0; + int glyph_index; + FT_Face face; + + /* Encode text */ + text = _PGFT_EncodePyString(textobj, self->render_flags & FT_RFLAG_UCS4); + if (!text) + goto error; + + if (text->length > 1) { + free_string(text); + return RAISE(PyExc_ValueError, "Too long, only 1 char supported."); + } + + ASSERT_SELF_IS_ALIVE(self); + + face = _PGFT_GetFont(self->freetype, self); + PGFT_char charcode = text->data[0]; + glyph_index = FT_Get_Char_Index(face, charcode); + if (glyph_index > 0) { + free_string(text); + Py_RETURN_TRUE; + } + + goto error; + +error: + free_string(text); + Py_RETURN_FALSE; +} + static PyObject * _ftfont_getsizedascender(pgFontObject *self, PyObject *args) { diff --git a/src_c/doc/font_doc.h b/src_c/doc/font_doc.h index 8beff6ff9f..7ed99e0878 100644 --- a/src_c/doc/font_doc.h +++ b/src_c/doc/font_doc.h @@ -15,6 +15,7 @@ #define DOC_FONT_FONT_UNDERLINE "underline -> bool\nGets or sets whether the font should be rendered with an underline." #define DOC_FONT_FONT_STRIKETHROUGH "strikethrough -> bool\nGets or sets whether the font should be rendered with a strikethrough." #define DOC_FONT_FONT_ALIGN "align -> int\nSet how rendered text is aligned when given a wrap length" +#define DOC_FONT_FONT_ISCHARDEFINED "is_char_defined(char) -> bool\nCheck if a char is defined in the font." #define DOC_FONT_FONT_RENDER "render(text, antialias, color, bgcolor=None, wraplength=0) -> Surface\ndraw text on a new Surface" #define DOC_FONT_FONT_SIZE "size(text) -> (width, height)\ndetermine the amount of space needed to render text" #define DOC_FONT_FONT_SETUNDERLINE "set_underline(bool) -> None\ncontrol if text is rendered with an underline" diff --git a/src_c/doc/freetype_doc.h b/src_c/doc/freetype_doc.h index 499580d339..7c7d60e685 100644 --- a/src_c/doc/freetype_doc.h +++ b/src_c/doc/freetype_doc.h @@ -25,6 +25,7 @@ #define DOC_FREETYPE_FONT_GETSIZEDHEIGHT "get_sized_height(=0) -> int\nThe scaled height of the font in pixels" #define DOC_FREETYPE_FONT_GETSIZEDGLYPHHEIGHT "get_sized_glyph_height(=0) -> int\nThe scaled bounding box height of the font in pixels" #define DOC_FREETYPE_FONT_GETSIZES "get_sizes() -> [(int, int, int, float, float), ...]\nget_sizes() -> []\nreturn the available sizes of embedded bitmaps" +#define DOC_FREETYPE_FONT_ISCHARDEFINED "is_char_defined(char) -> bool\nCheck if a char is defined in the font." #define DOC_FREETYPE_FONT_RENDER "render(text, fgcolor=None, bgcolor=None, style=STYLE_DEFAULT, rotation=0, size=0) -> (Surface, Rect)\nReturn rendered text as a surface" #define DOC_FREETYPE_FONT_RENDERTO "render_to(surf, dest, text, fgcolor=None, bgcolor=None, style=STYLE_DEFAULT, rotation=0, size=0) -> Rect\nRender text onto an existing surface" #define DOC_FREETYPE_FONT_RENDERRAW "render_raw(text, style=STYLE_DEFAULT, rotation=0, size=0, invert=False) -> (bytes, (int, int))\nReturn rendered text as a string of bytes" diff --git a/src_c/font.c b/src_c/font.c index 0e30228171..66609cb8c8 100644 --- a/src_c/font.c +++ b/src_c/font.c @@ -474,6 +474,61 @@ font_set_strikethrough(PyObject *self, PyObject *arg) Py_RETURN_NONE; } +static PyObject * +font_is_char_provided(PyObject *self, PyObject *textobj) +{ + TTF_Font *font = PyFont_AsFont(self); + PyObject *temp; + PyObject *obj; + Uint16 *buffer; + Py_ssize_t length; + Uint16 ch; + + if (PyUnicode_Check(textobj)) { + obj = textobj; + Py_INCREF(obj); + } + else if (PyBytes_Check(textobj)) { + obj = PyUnicode_FromEncodedObject(textobj, "UTF-8", NULL); + if (!obj) { + return NULL; + } + } + else { + return RAISE_TEXT_TYPE_ERROR(); + } + temp = PyUnicode_AsUTF16String(obj); + Py_DECREF(obj); + if (!temp) + return NULL; + obj = temp; + +#if !SDL_TTF_VERSION_ATLEAST(2, 0, 15) + if (utf_8_needs_UCS_4(astring)) { + Py_DECREF(obj); + return RAISE(PyExc_UnicodeError, + "a Unicode character above '\\uFFFF' was found;" + " not supported with SDL_ttf version below 2.0.15"); + } +#endif + + buffer = (Uint16 *)PyBytes_AS_STRING(obj); + length = PyBytes_GET_SIZE(obj) / sizeof(Uint16); + + if (length > 2) { + Py_DECREF(obj); + return RAISE(PyExc_ValueError, "Too long, only 1 char supported."); + } + + ch = buffer[1]; /* skip BOM */ + int index = TTF_GlyphIsProvided(font, ch); + Py_DECREF(obj); + if (!index) { + Py_RETURN_FALSE; + } + Py_RETURN_TRUE; +} + static PyObject * font_render(PyObject *self, PyObject *args, PyObject *kwds) { @@ -877,6 +932,8 @@ static PyMethodDef font_methods[] = { {"metrics", font_metrics, METH_O, DOC_FONT_FONT_METRICS}, {"render", (PyCFunction)font_render, METH_VARARGS | METH_KEYWORDS, DOC_FONT_FONT_RENDER}, + {"is_char_defined", font_is_char_provided, METH_O, + DOC_FONT_FONT_ISCHARDEFINED}, {"size", font_size, METH_O, DOC_FONT_FONT_SIZE}, {"set_script", font_set_script, METH_O, DOC_FONT_FONT_SETSCRIPT}, {"set_direction", (PyCFunction)font_set_direction, diff --git a/test/font_test.py b/test/font_test.py index bb69e1e295..9617fe12b3 100644 --- a/test/font_test.py +++ b/test/font_test.py @@ -581,6 +581,30 @@ def test_set_name(): self.assertRaises(AttributeError, test_set_name) + def test_font_is_char_defined_true(self): + f = pygame_font.Font(None, 20) + self.assertTrue(f.is_char_defined("a")) + + def test_font_is_char_defined_true_unicode(self): + f = pygame_font.Font(None, 20) + self.assertTrue(f.is_char_defined("\u003F")) + + def test_font_is_char_defined_raises_value_error_if_too_long(self): + f = pygame_font.Font(None, 20) + self.assertRaises(ValueError, f.is_char_defined, "ab") + + def test_font_is_char_defined_false_is_empty(self): + f = pygame_font.Font(None, 20) + self.assertFalse(f.is_char_defined("")) + + def test_font_is_char_defined_false(self): + f = pygame_font.Font(None, 20) + self.assertFalse(f.is_char_defined("❤")) + + def test_font_is_char_defined_false_unicode(self): + f = pygame_font.Font(None, 20) + self.assertFalse(f.is_char_defined("\uFF00")) + def test_font_file_not_found(self): # A per BUG reported by Bo Jangeborg on pygame-user mailing list, # http://www.mail-archive.com/pygame-users@seul.org/msg11675.html From c2ea7eb0dbfd9e90039a6a23ea45503eeaff2499 Mon Sep 17 00:00:00 2001 From: dr0id Date: Thu, 18 May 2023 21:36:03 +0200 Subject: [PATCH 2/3] font/freetype: change version added to 2.3 for the is_char_defined method --- docs/reST/ref/font.rst | 2 +- docs/reST/ref/freetype.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reST/ref/font.rst b/docs/reST/ref/font.rst index bf55fe3f47..f35f1c4d1c 100644 --- a/docs/reST/ref/font.rst +++ b/docs/reST/ref/font.rst @@ -282,7 +282,7 @@ solves no longer exists, it will likely be removed in the future. This checks if the char is defined in this font. - .. versionadded:: 2.2.0 + .. versionadded:: 2.3.0 .. ## Font.is_char_defined ## diff --git a/docs/reST/ref/freetype.rst b/docs/reST/ref/freetype.rst index c445d475de..aa896995a1 100644 --- a/docs/reST/ref/freetype.rst +++ b/docs/reST/ref/freetype.rst @@ -387,7 +387,7 @@ loaded. This module must be imported explicitly to be used. :: This checks if the char is defined in this font. - .. versionadded:: 2.2.0 + .. versionadded:: 2.3.0 .. ## Font.is_char_defined ## From e8f45c3fb443ef488bbfdedc349b8b9bb15aa8f8 Mon Sep 17 00:00:00 2001 From: dr0id Date: Sat, 2 Sep 2023 21:21:04 +0200 Subject: [PATCH 3/3] font: update version added for is_char_defined docs to 2.4.0 --- docs/reST/ref/font.rst | 2 +- docs/reST/ref/freetype.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reST/ref/font.rst b/docs/reST/ref/font.rst index a16c2bd32f..26021c8dbf 100644 --- a/docs/reST/ref/font.rst +++ b/docs/reST/ref/font.rst @@ -313,7 +313,7 @@ solves no longer exists, it will likely be removed in the future. This checks if the char is defined in this font. - .. versionadded:: 2.3.0 + .. versionadded:: 2.4.0 .. ## Font.is_char_defined ## diff --git a/docs/reST/ref/freetype.rst b/docs/reST/ref/freetype.rst index d970950646..fd3f9269b3 100644 --- a/docs/reST/ref/freetype.rst +++ b/docs/reST/ref/freetype.rst @@ -403,7 +403,7 @@ loaded. This module must be imported explicitly to be used. :: This checks if the char is defined in this font. - .. versionadded:: 2.3.0 + .. versionadded:: 2.4.0 .. ## Font.is_char_defined ##