From 6fbcb1aeecbba65389221652bd0e8a6da1468145 Mon Sep 17 00:00:00 2001 From: Nari Lee Date: Thu, 18 Apr 2024 07:27:16 +0900 Subject: [PATCH 01/11] rm --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000000..23636079ef --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +er From 3b15e7d4073fe4b7816dc0327d89a7527db93854 Mon Sep 17 00:00:00 2001 From: Nari Lee Date: Sat, 27 Apr 2024 06:02:21 +0900 Subject: [PATCH 02/11] Add outline & docs --- buildconfig/stubs/pygame/font.pyi | 2 ++ docs/reST/ref/font.rst | 11 +++++++++++ src_c/doc/font_doc.h | 1 + src_c/font.c | 22 ++++++++++++++++++++++ 4 files changed, 36 insertions(+) diff --git a/buildconfig/stubs/pygame/font.pyi b/buildconfig/stubs/pygame/font.pyi index 683a28ef2b..656b5b5375 100644 --- a/buildconfig/stubs/pygame/font.pyi +++ b/buildconfig/stubs/pygame/font.pyi @@ -56,6 +56,8 @@ class Font: def point_size(self) -> int: ... @point_size.setter def point_size(self, value: int) -> None: ... + @property + def outline(self) -> int: ... def __init__(self, filename: Optional[FileArg] = None, size: int = 20) -> None: ... def render( self, diff --git a/docs/reST/ref/font.rst b/docs/reST/ref/font.rst index 644a3b52b2..d6d5f7a87c 100644 --- a/docs/reST/ref/font.rst +++ b/docs/reST/ref/font.rst @@ -306,6 +306,17 @@ solves no longer exists, it will likely be removed in the future. .. ## Font.point_size ## + .. attribute:: outline + + | :sl:`Gets or sets the font's outline size` + | :sg:`outline -> int` + + Returns the size of the outline. + + .. versionadded:: 2.5.0 + + .. ## Font.outline ## + .. method:: render | :sl:`draw text on a new Surface` diff --git a/src_c/doc/font_doc.h b/src_c/doc/font_doc.h index 4ac4af922c..13a301ab2a 100644 --- a/src_c/doc/font_doc.h +++ b/src_c/doc/font_doc.h @@ -17,6 +17,7 @@ #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_POINTSIZE "point_size -> int\nGets or sets the font's point size" +#define DOC_FONT_FONT_OUTLINE "outline -> int\nGets or sets the font's outline size" #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/font.c b/src_c/font.c index 1574929567..4667d5a9d3 100644 --- a/src_c/font.c +++ b/src_c/font.c @@ -829,6 +829,26 @@ font_getter_style_name(PyObject *self, void *closure) return PyUnicode_FromString(font_style_name ? font_style_name : ""); } +static int +font_setter_outline(PyObject *self, PyObject *value, void *closure) +{ + TTF_Font *font = PyFont_AsFont(self); + int val; + + DEL_ATTR_NOT_SUPPORTED_CHECK("outline", value); + + val = PyLong_AsLong(value); + + TTF_SetFontOutline(font, val); + return 0; +} + +static PyObject * +font_getter_outline(PyObject *self, void *closure) +{ + return PyLong_FromLong(TTF_GetFontOutline(PyFont_AsFont(self))); +} + static PyObject * font_metrics(PyObject *self, PyObject *textobj) { @@ -1058,6 +1078,8 @@ static PyGetSetDef font_getsets[] = { DOC_FONT_FONT_ALIGN, NULL}, {"point_size", (getter)font_getter_point_size, (setter)font_setter_point_size, DOC_FONT_FONT_POINTSIZE, NULL}, + {"outline", (getter)font_getter_outline, (setter)font_setter_outline, + "TODO", NULL}, {NULL, NULL, NULL, NULL, NULL}}; static PyMethodDef font_methods[] = { From bdb70ed1a92b332de4702cb73d3b4897f7a6dc24 Mon Sep 17 00:00:00 2001 From: Nari Lee Date: Sat, 27 Apr 2024 06:10:00 +0900 Subject: [PATCH 03/11] Add testing --- test/font_test.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/font_test.py b/test/font_test.py index 219e0a7ba2..21ea3a69f3 100644 --- a/test/font_test.py +++ b/test/font_test.py @@ -953,6 +953,7 @@ def test_font_property_should_raise_exception_after_quit(self): ("fixed_width", None), ("fixed_sizes", None), ("antialiased", 45), + ("outline", 10), ("kerning", 46), ("vertical", 47), ("pad", 48), @@ -1057,6 +1058,7 @@ def query( underline=False, strikethrough=False, antialiase=False, + outline=False, ): if self.aborted: return False @@ -1067,7 +1069,7 @@ def query( screen = self.screen screen.fill((255, 255, 255)) pygame.display.flip() - if not (bold or italic or underline or strikethrough or antialiase): + if not (bold or italic or underline or strikethrough or antialiase or outline): text = "normal" else: modes = [] @@ -1081,11 +1083,14 @@ def query( modes.append("strikethrough") if antialiase: modes.append("antialiased") + if outline: + modes.append("outline") text = f"{'-'.join(modes)} (y/n):" f.set_bold(bold) f.set_italic(italic) f.set_underline(underline) f.set_strikethrough(strikethrough) + f.outline = outline s = f.render(text, antialiase, (0, 0, 0)) screen.blit(s, (offset, y)) y += s.get_size()[1] + spacing @@ -1125,6 +1130,9 @@ def test_strikethrough(self): def test_antialiase(self): self.assertTrue(self.query(antialiase=True)) + def test_outline(self): + self.assertTrue(self.query(outline=True)) + def test_bold_antialiase(self): self.assertTrue(self.query(bold=True, antialiase=True)) From fca930579bee0404edf7f153721638444c7e1cee Mon Sep 17 00:00:00 2001 From: Nari Lee Date: Sat, 27 Apr 2024 06:12:36 +0900 Subject: [PATCH 04/11] Remove README.md --- README.md | 1 - 1 file changed, 1 deletion(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 23636079ef..0000000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -er From a9752244b9d12d696e7d2d3810ce4a848beecf01 Mon Sep 17 00:00:00 2001 From: Nari Lee Date: Sat, 27 Apr 2024 13:37:11 +0900 Subject: [PATCH 05/11] Fix some bugs --- src_c/font.c | 10 +++++++++- test/font_test.py | 5 ++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src_c/font.c b/src_c/font.c index 4667d5a9d3..1c6ce57437 100644 --- a/src_c/font.c +++ b/src_c/font.c @@ -832,6 +832,10 @@ font_getter_style_name(PyObject *self, void *closure) static int font_setter_outline(PyObject *self, PyObject *value, void *closure) { + if (!PgFont_GenerationCheck(self)) { + RAISE_FONT_QUIT_ERROR_RETURN(-1); + } + TTF_Font *font = PyFont_AsFont(self); int val; @@ -846,6 +850,10 @@ font_setter_outline(PyObject *self, PyObject *value, void *closure) static PyObject * font_getter_outline(PyObject *self, void *closure) { + if (!PgFont_GenerationCheck(self)) { + return RAISE_FONT_QUIT_ERROR(); + } + return PyLong_FromLong(TTF_GetFontOutline(PyFont_AsFont(self))); } @@ -1079,7 +1087,7 @@ static PyGetSetDef font_getsets[] = { {"point_size", (getter)font_getter_point_size, (setter)font_setter_point_size, DOC_FONT_FONT_POINTSIZE, NULL}, {"outline", (getter)font_getter_outline, (setter)font_setter_outline, - "TODO", NULL}, + DOC_FONT_FONT_OUTLINE, NULL}, {NULL, NULL, NULL, NULL, NULL}}; static PyMethodDef font_methods[] = { diff --git a/test/font_test.py b/test/font_test.py index 21ea3a69f3..cff7039e6c 100644 --- a/test/font_test.py +++ b/test/font_test.py @@ -953,7 +953,6 @@ def test_font_property_should_raise_exception_after_quit(self): ("fixed_width", None), ("fixed_sizes", None), ("antialiased", 45), - ("outline", 10), ("kerning", 46), ("vertical", 47), ("pad", 48), @@ -993,6 +992,10 @@ def test_font_property_should_raise_exception_after_quit(self): properties.append(("point_size", 1)) else: skip_properties.add("point_size") + if version >= (2, 0, 12): + properties.append(("outline", 2006)) + else: + skip_properties.add("outline") font = pygame_font.Font(None, 10) actual_names = [] From edcb35fa06e98a62cb605a43116cc0477d523eab Mon Sep 17 00:00:00 2001 From: Nari Lee Date: Sat, 27 Apr 2024 21:10:57 +0900 Subject: [PATCH 06/11] Adjust code to Andrew Coffeey's reviews. --- buildconfig/stubs/pygame/font.pyi | 2 ++ src_c/font.c | 2 ++ test/font_test.py | 7 ++----- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/buildconfig/stubs/pygame/font.pyi b/buildconfig/stubs/pygame/font.pyi index 656b5b5375..eea43de194 100644 --- a/buildconfig/stubs/pygame/font.pyi +++ b/buildconfig/stubs/pygame/font.pyi @@ -58,6 +58,8 @@ class Font: def point_size(self, value: int) -> None: ... @property def outline(self) -> int: ... + @outline.setter + def outline(self, value: int) -> None: ... def __init__(self, filename: Optional[FileArg] = None, size: int = 20) -> None: ... def render( self, diff --git a/src_c/font.c b/src_c/font.c index 1c6ce57437..ad7b451a5b 100644 --- a/src_c/font.c +++ b/src_c/font.c @@ -842,6 +842,8 @@ font_setter_outline(PyObject *self, PyObject *value, void *closure) DEL_ATTR_NOT_SUPPORTED_CHECK("outline", value); val = PyLong_AsLong(value); + if ((val == -1) && (PyErr_Occurred() != NULL)) + return -1; TTF_SetFontOutline(font, val); return 0; diff --git a/test/font_test.py b/test/font_test.py index cff7039e6c..b4a719507b 100644 --- a/test/font_test.py +++ b/test/font_test.py @@ -981,6 +981,7 @@ def test_font_property_should_raise_exception_after_quit(self): ("italic", True), ("underline", True), ("strikethrough", True), + ("outline", 2), ] skip_properties = set() version = pygame.font.get_sdl_ttf_version() @@ -992,10 +993,6 @@ def test_font_property_should_raise_exception_after_quit(self): properties.append(("point_size", 1)) else: skip_properties.add("point_size") - if version >= (2, 0, 12): - properties.append(("outline", 2006)) - else: - skip_properties.add("outline") font = pygame_font.Font(None, 10) actual_names = [] @@ -1061,7 +1058,7 @@ def query( underline=False, strikethrough=False, antialiase=False, - outline=False, + outline=2, ): if self.aborted: return False From 0a617b98524f9a7bb94ef5cc0251fbf85896cb6a Mon Sep 17 00:00:00 2001 From: Nari Lee Date: Mon, 29 Apr 2024 07:32:32 +0900 Subject: [PATCH 07/11] Re-implement outline with color and size properties. --- buildconfig/stubs/pygame/font.pyi | 10 ++-- docs/reST/ref/font.rst | 17 +++++-- src_c/doc/font_doc.h | 3 +- src_c/font.c | 83 ++++++++++++++++++++++++++----- src_c/include/pygame_font.h | 4 ++ test/font_test.py | 32 +++++++++--- 6 files changed, 122 insertions(+), 27 deletions(-) diff --git a/buildconfig/stubs/pygame/font.pyi b/buildconfig/stubs/pygame/font.pyi index eea43de194..f47b655cb6 100644 --- a/buildconfig/stubs/pygame/font.pyi +++ b/buildconfig/stubs/pygame/font.pyi @@ -57,9 +57,13 @@ class Font: @point_size.setter def point_size(self, value: int) -> None: ... @property - def outline(self) -> int: ... - @outline.setter - def outline(self, value: int) -> None: ... + def outline_size(self) -> int: ... + @outline_size.setter + def outline_size(self, value: int) -> None: ... + @property + def outline_color(self) -> ColorValue: ... + @outline_color.setter + def outline_color(self, value: ColorValue) -> None: ... def __init__(self, filename: Optional[FileArg] = None, size: int = 20) -> None: ... def render( self, diff --git a/docs/reST/ref/font.rst b/docs/reST/ref/font.rst index d6d5f7a87c..b2d62e6743 100644 --- a/docs/reST/ref/font.rst +++ b/docs/reST/ref/font.rst @@ -306,16 +306,27 @@ solves no longer exists, it will likely be removed in the future. .. ## Font.point_size ## - .. attribute:: outline + .. attribute:: outline_size | :sl:`Gets or sets the font's outline size` - | :sg:`outline -> int` + | :sg:`outline_size -> int` Returns the size of the outline. .. versionadded:: 2.5.0 - .. ## Font.outline ## + .. ## Font.outline_size ## + + .. attribute:: outline_color + + | :sl:`Gets or sets the font's outline color` + | :sg:`outline_size -> RGB` + + Returns the color of the outline. + + .. versionadded:: 2.5.0 + + .. ## Font.outline_color ## .. method:: render diff --git a/src_c/doc/font_doc.h b/src_c/doc/font_doc.h index 13a301ab2a..0c14db51a4 100644 --- a/src_c/doc/font_doc.h +++ b/src_c/doc/font_doc.h @@ -17,7 +17,8 @@ #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_POINTSIZE "point_size -> int\nGets or sets the font's point size" -#define DOC_FONT_FONT_OUTLINE "outline -> int\nGets or sets the font's outline size" +#define DOC_FONT_FONT_OUTLINESIZE "outline_size -> int\nGets or sets the font's outline size" +#define DOC_FONT_FONT_OUTLINECOLOR "outline_size -> RGB\nGets or sets the font's outline color" #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/font.c b/src_c/font.c index ad7b451a5b..be3ec726e4 100644 --- a/src_c/font.c +++ b/src_c/font.c @@ -829,34 +829,91 @@ font_getter_style_name(PyObject *self, void *closure) return PyUnicode_FromString(font_style_name ? font_style_name : ""); } +static PyObject * +font_getter_outline_size(PyFontObject *self, void *closure) +{ + if (!PgFont_GenerationCheck(self)) + return RAISE_FONT_QUIT_ERROR(); + +#if SDL_TTF_VERSION_ATLEAST(2, 0, 12) + return PyLong_FromLong(self->outline_size); +#else + return RAISE(pgExc_SDLError, + "Incorrect SDL_TTF version (requires 2.0.12)"); +#endif +} + static int -font_setter_outline(PyObject *self, PyObject *value, void *closure) +font_setter_outline_size(PyFontObject *self, PyObject *value, void *closure) { if (!PgFont_GenerationCheck(self)) { RAISE_FONT_QUIT_ERROR_RETURN(-1); } - TTF_Font *font = PyFont_AsFont(self); - int val; +#if SDL_TTF_VERSION_ATLEAST(2, 0, 12) + int val = PyLong_AsLong(value); - DEL_ATTR_NOT_SUPPORTED_CHECK("outline", value); + if (PyErr_Occurred() && val == -1) + return -1; - val = PyLong_AsLong(value); - if ((val == -1) && (PyErr_Occurred() != NULL)) + if (val < 0) { + PyErr_SetString(PyExc_ValueError, + "outline_size cannot be less than 0"); return -1; + } + + self->outline_size = val; - TTF_SetFontOutline(font, val); return 0; +#else + PyErr_SetString(pgExc_SDLError, + "Incorrect SDL_TTF version (requires 2.0.12)"); + return -1; +#endif } static PyObject * -font_getter_outline(PyObject *self, void *closure) +font_getter_outline_color(PyFontObject *self, void *closure) { - if (!PgFont_GenerationCheck(self)) { + if (!PgFont_GenerationCheck(self)) return RAISE_FONT_QUIT_ERROR(); + +#if SDL_TTF_VERSION_ATLEAST(2, 0, 12) + return Py_BuildValue("(bbbb)", self->outline_color.r, + self->outline_color.g, self->outline_color.b, + self->outline_color.a); +#else + return RAISE(pgExc_SDLError, + "Incorrect SDL_TTF version (requires 2.0.12)"); +#endif +} + +static int +font_setter_outline_color(PyFontObject *self, PyObject *value, void *closure) +{ + Uint8 rgba[] = {0, 0, 0, 0}; + + if (!PgFont_GenerationCheck(self)) { + RAISE_FONT_QUIT_ERROR_RETURN(-1); } - return PyLong_FromLong(TTF_GetFontOutline(PyFont_AsFont(self))); +#if SDL_TTF_VERSION_ATLEAST(2, 0, 12) + if (!pg_RGBAFromObjEx(value, rgba, PG_COLOR_HANDLE_ALL)) { + return -1; + } + + if (PyErr_Occurred()) + return -1; + + SDL_Color val = {rgba[0], rgba[1], rgba[2], SDL_ALPHA_OPAQUE}; + self->outline_color = val; + + return 0; +#else + PyErr_SetString(pgExc_SDLError, + "Incorrect SDL_TTF version (requires 2.0.12)"); + return -1; +#endif } static PyObject * @@ -1088,8 +1145,10 @@ static PyGetSetDef font_getsets[] = { DOC_FONT_FONT_ALIGN, NULL}, {"point_size", (getter)font_getter_point_size, (setter)font_setter_point_size, DOC_FONT_FONT_POINTSIZE, NULL}, - {"outline", (getter)font_getter_outline, (setter)font_setter_outline, - DOC_FONT_FONT_OUTLINE, NULL}, + {"outline_color", (getter)font_getter_outline_color, + (setter)font_setter_outline_color, DOC_FONT_FONT_OUTLINECOLOR, NULL}, + {"outline_size", (getter)font_getter_outline_size, + (setter)font_setter_outline_size, DOC_FONT_FONT_OUTLINESIZE, NULL}, {NULL, NULL, NULL, NULL, NULL}}; static PyMethodDef font_methods[] = { diff --git a/src_c/include/pygame_font.h b/src_c/include/pygame_font.h index 127e5ed57b..2237a71873 100644 --- a/src_c/include/pygame_font.h +++ b/src_c/include/pygame_font.h @@ -29,6 +29,10 @@ typedef struct { PyObject_HEAD TTF_Font *font; PyObject *weakreflist; int ptsize; + + SDL_Color outline_color; + int outline_size; + unsigned int ttf_init_generation; } PyFontObject; #define PyFont_AsFont(x) (((PyFontObject *)x)->font) diff --git a/test/font_test.py b/test/font_test.py index b4a719507b..e88218fcfb 100644 --- a/test/font_test.py +++ b/test/font_test.py @@ -981,7 +981,8 @@ def test_font_property_should_raise_exception_after_quit(self): ("italic", True), ("underline", True), ("strikethrough", True), - ("outline", 2), + ("outline_size", 2), + ("outline_color", (20, 0, 6)), ] skip_properties = set() version = pygame.font.get_sdl_ttf_version() @@ -1058,7 +1059,8 @@ def query( underline=False, strikethrough=False, antialiase=False, - outline=2, + outline_size=2, + outline_color=(20, 0, 6), ): if self.aborted: return False @@ -1069,7 +1071,15 @@ def query( screen = self.screen screen.fill((255, 255, 255)) pygame.display.flip() - if not (bold or italic or underline or strikethrough or antialiase or outline): + if not ( + bold + or italic + or underline + or strikethrough + or antialiase + or outline_size + or outline_color + ): text = "normal" else: modes = [] @@ -1083,14 +1093,17 @@ def query( modes.append("strikethrough") if antialiase: modes.append("antialiased") - if outline: - modes.append("outline") + if outline_size: + modes.append("outline_size") + if outline_color: + modes.append("outline_color") text = f"{'-'.join(modes)} (y/n):" f.set_bold(bold) f.set_italic(italic) f.set_underline(underline) f.set_strikethrough(strikethrough) - f.outline = outline + f.outline_size = outline_size + f.outline_color = outline_color s = f.render(text, antialiase, (0, 0, 0)) screen.blit(s, (offset, y)) y += s.get_size()[1] + spacing @@ -1130,8 +1143,11 @@ def test_strikethrough(self): def test_antialiase(self): self.assertTrue(self.query(antialiase=True)) - def test_outline(self): - self.assertTrue(self.query(outline=True)) + def test_outline_size(self): + self.assertTrue(self.query(outline_size=2)) + + def test_outline_color(self): + self.assertTrue(self.query(outline_color=(20, 0, 6))) def test_bold_antialiase(self): self.assertTrue(self.query(bold=True, antialiase=True)) From fc29c93752c2dbef5e72cf9ebe5066560108cc8c Mon Sep 17 00:00:00 2001 From: Nari Lee Date: Mon, 29 Apr 2024 16:43:32 +0900 Subject: [PATCH 08/11] Move font rendering implementation into a separate method --- src_c/font.c | 88 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 51 insertions(+), 37 deletions(-) diff --git a/src_c/font.c b/src_c/font.c index be3ec726e4..e5c9a63fed 100644 --- a/src_c/font.c +++ b/src_c/font.c @@ -38,11 +38,11 @@ #include "structmember.h" #define RAISE_TEXT_TYPE_ERROR() \ - RAISE(PyExc_TypeError, "text must be a unicode or bytes"); + RAISE(PyExc_TypeError, "text must be a unicode or bytes") #define RAISE_FONT_QUIT_ERROR_RETURN(r) \ RAISERETURN(pgExc_SDLError, \ - "Invalid font (font module quit since font created)", r) + "Invalid font (font module quit since font created)", r); #define RAISE_FONT_QUIT_ERROR() \ RAISE(pgExc_SDLError, \ @@ -538,38 +538,19 @@ font_set_strikethrough(PyObject *self, PyObject *arg) Py_RETURN_NONE; } -static PyObject * -font_render(PyObject *self, PyObject *args, PyObject *kwds) +static SDL_Surface * +_create_font_surface(TTF_Font *font, PyObject *text, int antialias, + PyObject *fg_rgba_obj, PyObject *bg_rgba_obj, + int wraplength) { - if (!PgFont_GenerationCheck(self)) { - return RAISE_FONT_QUIT_ERROR(); - } - - TTF_Font *font = PyFont_AsFont(self); - int antialias; - PyObject *text, *final; - PyObject *fg_rgba_obj, *bg_rgba_obj = Py_None; - Uint8 rgba[] = {0, 0, 0, 0}; SDL_Surface *surf; + Uint8 rgba[] = {0, 0, 0, 0}; const char *astring = ""; - int wraplength = 0; - - if (!PgFont_GenerationCheck(self)) { - return RAISE_FONT_QUIT_ERROR() - } - - static char *kwlist[] = {"text", "antialias", "color", - "bgcolor", "wraplength", NULL}; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "OpO|Oi", kwlist, &text, - &antialias, &fg_rgba_obj, &bg_rgba_obj, - &wraplength)) { - return NULL; - } + // 글꼴 생성 if (!pg_RGBAFromObjEx(fg_rgba_obj, rgba, PG_COLOR_HANDLE_ALL)) { /* Exception already set for us */ - return NULL; + return (SDL_Surface *)(NULL); } SDL_Color foreg = {rgba[0], rgba[1], rgba[2], SDL_ALPHA_OPAQUE}; @@ -579,29 +560,29 @@ font_render(PyObject *self, PyObject *args, PyObject *kwds) if (bg_rgba_obj != Py_None) { if (!pg_RGBAFromObjEx(bg_rgba_obj, rgba, PG_COLOR_HANDLE_ALL)) { /* Exception already set for us */ - return NULL; + return (SDL_Surface *)(NULL); } backg = (SDL_Color){rgba[0], rgba[1], rgba[2], SDL_ALPHA_OPAQUE}; } if (!PyUnicode_Check(text) && !PyBytes_Check(text) && text != Py_None) { - return RAISE_TEXT_TYPE_ERROR(); + return (SDL_Surface *)(RAISE_TEXT_TYPE_ERROR()); } if (wraplength < 0) { - return RAISE(PyExc_ValueError, - "wraplength parameter must be positive"); + return (SDL_Surface *)(RAISE(PyExc_ValueError, + "wraplength parameter must be positive")); } if (PyUnicode_Check(text)) { Py_ssize_t _size = -1; astring = PyUnicode_AsUTF8AndSize(text, &_size); if (astring == NULL) { /* exception already set */ - return NULL; + return (SDL_Surface *)(NULL); } if (strlen(astring) != (size_t)_size) { - return RAISE(PyExc_ValueError, - "A null character was found in the text"); + return (SDL_Surface *)(RAISE( + PyExc_ValueError, "A null character was found in the text")); } } @@ -609,7 +590,7 @@ font_render(PyObject *self, PyObject *args, PyObject *kwds) /* Bytes_AsStringAndSize with NULL arg for length emits ValueError if internal NULL bytes are present */ if (PyBytes_AsStringAndSize(text, (char **)&astring, NULL) == -1) { - return NULL; /* exception already set */ + return (SDL_Surface *)(NULL); /* exception already set */ } } @@ -656,9 +637,42 @@ font_render(PyObject *self, PyObject *args, PyObject *kwds) } if (surf == NULL) { - return RAISE(pgExc_SDLError, TTF_GetError()); + return (SDL_Surface *)(RAISE(pgExc_SDLError, TTF_GetError())); } + return surf; +} + +static PyObject * +font_render(PyObject *self, PyObject *args, PyObject *kwds) +{ + if (!PgFont_GenerationCheck(self)) { + return RAISE_FONT_QUIT_ERROR(); + } + + int antialias; + PyObject *text, *final; + PyObject *fg_rgba_obj, *bg_rgba_obj = Py_None; + SDL_Surface *surf; + int wraplength = 0; + TTF_Font *font = PyFont_AsFont(self); + + if (!PgFont_GenerationCheck(self)) { + return RAISE_FONT_QUIT_ERROR() + } + + static char *kwlist[] = {"text", "antialias", "color", + "bgcolor", "wraplength", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OpO|Oi", kwlist, &text, + &antialias, &fg_rgba_obj, &bg_rgba_obj, + &wraplength)) { + return NULL; + } + + surf = _create_font_surface(font, text, antialias, fg_rgba_obj, + bg_rgba_obj, wraplength); + final = (PyObject *)pgSurface_New(surf); if (final == NULL) { SDL_FreeSurface(surf); From cd2142abe97159658f594f1fa5b34fc4cbe64bd4 Mon Sep 17 00:00:00 2001 From: Nari Lee Date: Tue, 30 Apr 2024 16:05:33 +0900 Subject: [PATCH 09/11] Modify surface through reference --- src_c/font.c | 73 +++++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/src_c/font.c b/src_c/font.c index e5c9a63fed..390235a7ff 100644 --- a/src_c/font.c +++ b/src_c/font.c @@ -538,19 +538,17 @@ font_set_strikethrough(PyObject *self, PyObject *arg) Py_RETURN_NONE; } -static SDL_Surface * +static int _create_font_surface(TTF_Font *font, PyObject *text, int antialias, PyObject *fg_rgba_obj, PyObject *bg_rgba_obj, - int wraplength) + int wraplength, SDL_Surface **dst_surf) { - SDL_Surface *surf; Uint8 rgba[] = {0, 0, 0, 0}; const char *astring = ""; // 글꼴 생성 if (!pg_RGBAFromObjEx(fg_rgba_obj, rgba, PG_COLOR_HANDLE_ALL)) { - /* Exception already set for us */ - return (SDL_Surface *)(NULL); + return 0; // exception already set } SDL_Color foreg = {rgba[0], rgba[1], rgba[2], SDL_ALPHA_OPAQUE}; @@ -559,30 +557,32 @@ _create_font_surface(TTF_Font *font, PyObject *text, int antialias, if (bg_rgba_obj != Py_None) { if (!pg_RGBAFromObjEx(bg_rgba_obj, rgba, PG_COLOR_HANDLE_ALL)) { - /* Exception already set for us */ - return (SDL_Surface *)(NULL); + return 0; // exception already set. } backg = (SDL_Color){rgba[0], rgba[1], rgba[2], SDL_ALPHA_OPAQUE}; } if (!PyUnicode_Check(text) && !PyBytes_Check(text) && text != Py_None) { - return (SDL_Surface *)(RAISE_TEXT_TYPE_ERROR()); + PyErr_Format(PyExc_TypeError, "text must be a unicode or bytes"); + return 0; } if (wraplength < 0) { - return (SDL_Surface *)(RAISE(PyExc_ValueError, - "wraplength parameter must be positive")); + PyErr_Format(PyExc_ValueError, + "The wraplength parameter must be positive."); + return 0; } if (PyUnicode_Check(text)) { Py_ssize_t _size = -1; astring = PyUnicode_AsUTF8AndSize(text, &_size); - if (astring == NULL) { /* exception already set */ - return (SDL_Surface *)(NULL); + if (astring == NULL) { + return 0; // exception already set. } if (strlen(astring) != (size_t)_size) { - return (SDL_Surface *)(RAISE( - PyExc_ValueError, "A null character was found in the text")); + PyErr_Format(PyExc_ValueError, + "A null character was found in the text."); + return 0; } } @@ -590,7 +590,7 @@ _create_font_surface(TTF_Font *font, PyObject *text, int antialias, /* Bytes_AsStringAndSize with NULL arg for length emits ValueError if internal NULL bytes are present */ if (PyBytes_AsStringAndSize(text, (char **)&astring, NULL) == -1) { - return (SDL_Surface *)(NULL); /* exception already set */ + return 0; // exception already set. } } @@ -599,48 +599,49 @@ _create_font_surface(TTF_Font *font, PyObject *text, int antialias, if (strlen(astring) == 0) { /* special 0 string case */ int height = TTF_FontHeight(font); - surf = PG_CreateSurface(0, height, PG_PIXELFORMAT_XRGB8888); + *dst_surf = PG_CreateSurface(0, height, PG_PIXELFORMAT_XRGB8888); } else { /* normal case */ if (antialias && bg_rgba_obj == Py_None) { #if SDL_TTF_VERSION_ATLEAST(2, 0, 18) - surf = TTF_RenderUTF8_Blended_Wrapped(font, astring, foreg, - wraplength); + *dst_surf = TTF_RenderUTF8_Blended_Wrapped(font, astring, foreg, + wraplength); #else - surf = TTF_RenderUTF8_Blended(font, astring, foreg); + *dst_surf = TTF_RenderUTF8_Blended(font, astring, foreg); #endif } else if (antialias) { #if SDL_TTF_VERSION_ATLEAST(2, 0, 18) - surf = TTF_RenderUTF8_Shaded_Wrapped(font, astring, foreg, backg, - wraplength); + *dst_surf = TTF_RenderUTF8_Shaded_Wrapped(font, astring, foreg, + backg, wraplength); #else - surf = TTF_RenderUTF8_Shaded(font, astring, foreg, backg); + *dst_surf = TTF_RenderUTF8_Shaded(font, astring, foreg, backg); #endif } else { #if SDL_TTF_VERSION_ATLEAST(2, 0, 18) - surf = + *dst_surf = TTF_RenderUTF8_Solid_Wrapped(font, astring, foreg, wraplength); #else - surf = TTF_RenderUTF8_Solid(font, astring, foreg); + *dst_surf = TTF_RenderUTF8_Solid(font, astring, foreg); #endif /* If an explicit background was provided and the rendering options resolve to Render_Solid, that needs to be explicitly handled. */ - if (surf != NULL && bg_rgba_obj != Py_None) { - SDL_SetColorKey(surf, 0, 0); - surf->format->palette->colors[0].r = backg.r; - surf->format->palette->colors[0].g = backg.g; - surf->format->palette->colors[0].b = backg.b; + if (*dst_surf != NULL && bg_rgba_obj != Py_None) { + SDL_SetColorKey(*dst_surf, 0, 0); + (*dst_surf)->format->palette->colors[0].r = backg.r; + (*dst_surf)->format->palette->colors[0].g = backg.g; + (*dst_surf)->format->palette->colors[0].b = backg.b; } } } - if (surf == NULL) { - return (SDL_Surface *)(RAISE(pgExc_SDLError, TTF_GetError())); + if (*dst_surf == NULL) { + PyErr_Format(pgExc_SDLError, TTF_GetError()); + return 0; } - return surf; + return 1; } static PyObject * @@ -653,7 +654,7 @@ font_render(PyObject *self, PyObject *args, PyObject *kwds) int antialias; PyObject *text, *final; PyObject *fg_rgba_obj, *bg_rgba_obj = Py_None; - SDL_Surface *surf; + SDL_Surface *surf = NULL; int wraplength = 0; TTF_Font *font = PyFont_AsFont(self); @@ -670,8 +671,10 @@ font_render(PyObject *self, PyObject *args, PyObject *kwds) return NULL; } - surf = _create_font_surface(font, text, antialias, fg_rgba_obj, - bg_rgba_obj, wraplength); + if (!_create_font_surface(font, text, antialias, fg_rgba_obj, bg_rgba_obj, + wraplength, &surf)) { + return NULL; + } final = (PyObject *)pgSurface_New(surf); if (final == NULL) { From 52c51baf2363c4306995cb24ff52ca30e3918851 Mon Sep 17 00:00:00 2001 From: Nari Lee Date: Sat, 4 May 2024 06:39:22 +0900 Subject: [PATCH 10/11] Creates a filled and outlined font --- src_c/font.c | 94 ++++++++++++++++++++++++++++++++++++----------- test/font_test.py | 33 ++++++++--------- 2 files changed, 88 insertions(+), 39 deletions(-) diff --git a/src_c/font.c b/src_c/font.c index 390235a7ff..1ae0cd5ba3 100644 --- a/src_c/font.c +++ b/src_c/font.c @@ -540,28 +540,11 @@ font_set_strikethrough(PyObject *self, PyObject *arg) static int _create_font_surface(TTF_Font *font, PyObject *text, int antialias, - PyObject *fg_rgba_obj, PyObject *bg_rgba_obj, + SDL_Color foreg, SDL_Color backg, int draw_backg, int wraplength, SDL_Surface **dst_surf) { - Uint8 rgba[] = {0, 0, 0, 0}; const char *astring = ""; - // 글꼴 생성 - if (!pg_RGBAFromObjEx(fg_rgba_obj, rgba, PG_COLOR_HANDLE_ALL)) { - return 0; // exception already set - } - - SDL_Color foreg = {rgba[0], rgba[1], rgba[2], SDL_ALPHA_OPAQUE}; - /* might be overridden right below, with an explicit background color */ - SDL_Color backg = {0, 0, 0, SDL_ALPHA_OPAQUE}; - - if (bg_rgba_obj != Py_None) { - if (!pg_RGBAFromObjEx(bg_rgba_obj, rgba, PG_COLOR_HANDLE_ALL)) { - return 0; // exception already set. - } - backg = (SDL_Color){rgba[0], rgba[1], rgba[2], SDL_ALPHA_OPAQUE}; - } - if (!PyUnicode_Check(text) && !PyBytes_Check(text) && text != Py_None) { PyErr_Format(PyExc_TypeError, "text must be a unicode or bytes"); return 0; @@ -602,7 +585,7 @@ _create_font_surface(TTF_Font *font, PyObject *text, int antialias, *dst_surf = PG_CreateSurface(0, height, PG_PIXELFORMAT_XRGB8888); } else { /* normal case */ - if (antialias && bg_rgba_obj == Py_None) { + if (antialias && !draw_backg) { #if SDL_TTF_VERSION_ATLEAST(2, 0, 18) *dst_surf = TTF_RenderUTF8_Blended_Wrapped(font, astring, foreg, wraplength); @@ -627,7 +610,7 @@ _create_font_surface(TTF_Font *font, PyObject *text, int antialias, #endif /* If an explicit background was provided and the rendering options resolve to Render_Solid, that needs to be explicitly handled. */ - if (*dst_surf != NULL && bg_rgba_obj != Py_None) { + if (*dst_surf != NULL && draw_backg) { SDL_SetColorKey(*dst_surf, 0, 0); (*dst_surf)->format->palette->colors[0].r = backg.r; (*dst_surf)->format->palette->colors[0].g = backg.g; @@ -655,8 +638,12 @@ font_render(PyObject *self, PyObject *args, PyObject *kwds) PyObject *text, *final; PyObject *fg_rgba_obj, *bg_rgba_obj = Py_None; SDL_Surface *surf = NULL; + SDL_Surface *outline_surf = NULL; int wraplength = 0; TTF_Font *font = PyFont_AsFont(self); + int outline_size = ((PyFontObject *)self)->outline_size; + SDL_Color outline_color = ((PyFontObject *)self)->outline_color; + Uint8 rgba[] = {0, 0, 0, 0}; if (!PgFont_GenerationCheck(self)) { return RAISE_FONT_QUIT_ERROR() @@ -671,14 +658,74 @@ font_render(PyObject *self, PyObject *args, PyObject *kwds) return NULL; } - if (!_create_font_surface(font, text, antialias, fg_rgba_obj, bg_rgba_obj, + // 글꼴 생성 + if (!pg_RGBAFromObjEx(fg_rgba_obj, rgba, PG_COLOR_HANDLE_ALL)) { + return 0; // exception already set + } + + SDL_Color foreg = {rgba[0], rgba[1], rgba[2], SDL_ALPHA_OPAQUE}; + SDL_Color backg = {0, 0, 0, SDL_ALPHA_OPAQUE}; + int draw_backg = 1; + /* might be overridden right below, with an explicit background color */ + + if (bg_rgba_obj != Py_None) { + if (!pg_RGBAFromObjEx(bg_rgba_obj, rgba, PG_COLOR_HANDLE_ALL)) { + return 0; // exception already set. + } + backg = (SDL_Color){rgba[0], rgba[1], rgba[2], SDL_ALPHA_OPAQUE}; + } + else { + draw_backg = 0; + } + + if (!_create_font_surface(font, text, antialias, foreg, backg, draw_backg, wraplength, &surf)) { return NULL; } - final = (PyObject *)pgSurface_New(surf); + SDL_Surface *filled_with_outline_surf = NULL; + + if (outline_size > 0) { + TTF_SetFontOutline(font, outline_size); + + if (!_create_font_surface(font, text, antialias, outline_color, backg, + draw_backg, wraplength, &outline_surf)) { + return NULL; + } + + TTF_SetFontOutline(font, 0); + + filled_with_outline_surf = PG_CreateSurface( + outline_surf->w, outline_surf->h, SDL_PIXELFORMAT_RGBA32); + + // `surf` and `outline_surf` are **NOT** the same size. + SDL_Rect outlinerect; + outlinerect.x = filled_with_outline_surf->w / 2 - outline_surf->w / 2; + outlinerect.y = filled_with_outline_surf->h / 2 - outline_surf->h / 2; + outlinerect.w = outline_surf->w; + outlinerect.h = outline_surf->h; + SDL_Rect fillrect; + fillrect.x = filled_with_outline_surf->w / 2 - surf->w / 2; + fillrect.y = filled_with_outline_surf->h / 2 - surf->h / 2; + fillrect.w = surf->w; + fillrect.h = surf->h; + SDL_BlitSurface(surf, NULL, filled_with_outline_surf, &fillrect); + SDL_BlitSurface(outline_surf, NULL, filled_with_outline_surf, + &outlinerect); + + final = (PyObject *)pgSurface_New(filled_with_outline_surf); + } + else { + final = (PyObject *)pgSurface_New(surf); + } + if (final == NULL) { SDL_FreeSurface(surf); + if (outline_surf != NULL) + SDL_FreeSurface(outline_surf); + + if (filled_with_outline_surf != NULL) + SDL_FreeSurface(filled_with_outline_surf); } return final; } @@ -1309,6 +1356,9 @@ font_init(PyFontObject *self, PyObject *args, PyObject *kwds) Py_DECREF(obj); self->font = font; self->ptsize = fontsize; + self->outline_size = 0; + SDL_Color init_color = {0, 0, 0, SDL_ALPHA_OPAQUE}; + self->outline_color = init_color; self->ttf_init_generation = current_ttf_generation; return 0; diff --git a/test/font_test.py b/test/font_test.py index e88218fcfb..87b384c2ae 100644 --- a/test/font_test.py +++ b/test/font_test.py @@ -981,8 +981,15 @@ def test_font_property_should_raise_exception_after_quit(self): ("italic", True), ("underline", True), ("strikethrough", True), - ("outline_size", 2), - ("outline_color", (20, 0, 6)), + ("outline_size", 5), + ( + "outline_color", + ( + 255, + 0, + 255, + ), + ), ] skip_properties = set() version = pygame.font.get_sdl_ttf_version() @@ -1045,7 +1052,7 @@ def setUp(self): self.screen = pygame.display.set_mode((600, 200)) self.screen.fill((255, 255, 255)) pygame.display.flip() - self.f = pygame_font.Font(None, 32) + self.f = pygame_font.Font(None, 96) def abort(self): if self.screen is not None: @@ -1059,8 +1066,8 @@ def query( underline=False, strikethrough=False, antialiase=False, - outline_size=2, - outline_color=(20, 0, 6), + outline_size=0, + outline_color=(0, 0, 0), ): if self.aborted: return False @@ -1072,13 +1079,7 @@ def query( screen.fill((255, 255, 255)) pygame.display.flip() if not ( - bold - or italic - or underline - or strikethrough - or antialiase - or outline_size - or outline_color + bold or italic or underline or strikethrough or antialiase or outline_size ): text = "normal" else: @@ -1095,8 +1096,6 @@ def query( modes.append("antialiased") if outline_size: modes.append("outline_size") - if outline_color: - modes.append("outline_color") text = f"{'-'.join(modes)} (y/n):" f.set_bold(bold) f.set_italic(italic) @@ -1144,10 +1143,10 @@ def test_antialiase(self): self.assertTrue(self.query(antialiase=True)) def test_outline_size(self): - self.assertTrue(self.query(outline_size=2)) + self.assertTrue(self.query(outline_size=1, outline_color=(255, 0, 255))) - def test_outline_color(self): - self.assertTrue(self.query(outline_color=(20, 0, 6))) + def test_outline_size_huge(self): + self.assertTrue(self.query(outline_size=10, outline_color=(0, 0, 255))) def test_bold_antialiase(self): self.assertTrue(self.query(bold=True, antialiase=True)) From c7bf3e294d0a834d9a753306b6e3d2200a1adfe7 Mon Sep 17 00:00:00 2001 From: Nari Lee Date: Tue, 14 May 2024 19:01:28 +0900 Subject: [PATCH 11/11] Adjust code to reviews from Robert Pfeiffer and Charlie Hayden --- buildconfig/stubs/pygame/font.pyi | 6 +- docs/reST/ref/font.rst | 16 ++-- src_c/doc/font_doc.h | 4 +- src_c/font.c | 122 ++++++++++++++++-------------- src_c/include/pygame_font.h | 4 +- test/font_test.py | 20 ++--- 6 files changed, 90 insertions(+), 82 deletions(-) diff --git a/buildconfig/stubs/pygame/font.pyi b/buildconfig/stubs/pygame/font.pyi index f47b655cb6..6e097542ae 100644 --- a/buildconfig/stubs/pygame/font.pyi +++ b/buildconfig/stubs/pygame/font.pyi @@ -57,9 +57,9 @@ class Font: @point_size.setter def point_size(self, value: int) -> None: ... @property - def outline_size(self) -> int: ... - @outline_size.setter - def outline_size(self, value: int) -> None: ... + def outline_width(self) -> int: ... + @outline_width.setter + def outline_width(self, value: int) -> None: ... @property def outline_color(self) -> ColorValue: ... @outline_color.setter diff --git a/docs/reST/ref/font.rst b/docs/reST/ref/font.rst index b2d62e6743..539109d948 100644 --- a/docs/reST/ref/font.rst +++ b/docs/reST/ref/font.rst @@ -306,23 +306,25 @@ solves no longer exists, it will likely be removed in the future. .. ## Font.point_size ## - .. attribute:: outline_size + .. attribute:: outline_width - | :sl:`Gets or sets the font's outline size` - | :sg:`outline_size -> int` + | :sl:`Gets or sets the font's outline radius size` + | :sg:`outline_width -> int` - Returns the size of the outline. + Gets or sets the radius size of the outline in pixels defaulting to 0. + + If the value is set to 0, it shows no outline. .. versionadded:: 2.5.0 - .. ## Font.outline_size ## + .. ## Font.outline_width ## .. attribute:: outline_color | :sl:`Gets or sets the font's outline color` - | :sg:`outline_size -> RGB` + | :sg:`outline_width -> RGB` - Returns the color of the outline. + Gets or sets the color of the outline defaulting to (0, 0, 0, 255) .. versionadded:: 2.5.0 diff --git a/src_c/doc/font_doc.h b/src_c/doc/font_doc.h index 0c14db51a4..d9e9f1f07b 100644 --- a/src_c/doc/font_doc.h +++ b/src_c/doc/font_doc.h @@ -17,8 +17,8 @@ #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_POINTSIZE "point_size -> int\nGets or sets the font's point size" -#define DOC_FONT_FONT_OUTLINESIZE "outline_size -> int\nGets or sets the font's outline size" -#define DOC_FONT_FONT_OUTLINECOLOR "outline_size -> RGB\nGets or sets the font's outline color" +#define DOC_FONT_FONT_OUTLINEWIDTH "outline_width -> int\nGets or sets the font's outline radius size" +#define DOC_FONT_FONT_OUTLINECOLOR "outline_width -> RGB\nGets or sets the font's outline color" #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/font.c b/src_c/font.c index 1ae0cd5ba3..01b9d4095e 100644 --- a/src_c/font.c +++ b/src_c/font.c @@ -38,11 +38,11 @@ #include "structmember.h" #define RAISE_TEXT_TYPE_ERROR() \ - RAISE(PyExc_TypeError, "text must be a unicode or bytes") + RAISE(PyExc_TypeError, "text must be a unicode or bytes"); #define RAISE_FONT_QUIT_ERROR_RETURN(r) \ RAISERETURN(pgExc_SDLError, \ - "Invalid font (font module quit since font created)", r); + "Invalid font (font module quit since font created)", r) #define RAISE_FONT_QUIT_ERROR() \ RAISE(pgExc_SDLError, \ @@ -539,74 +539,46 @@ font_set_strikethrough(PyObject *self, PyObject *arg) } static int -_create_font_surface(TTF_Font *font, PyObject *text, int antialias, +_create_font_surface(TTF_Font *font, const char *text, int antialias, SDL_Color foreg, SDL_Color backg, int draw_backg, int wraplength, SDL_Surface **dst_surf) { - const char *astring = ""; - - if (!PyUnicode_Check(text) && !PyBytes_Check(text) && text != Py_None) { - PyErr_Format(PyExc_TypeError, "text must be a unicode or bytes"); - return 0; - } - if (wraplength < 0) { PyErr_Format(PyExc_ValueError, "The wraplength parameter must be positive."); return 0; } - if (PyUnicode_Check(text)) { - Py_ssize_t _size = -1; - astring = PyUnicode_AsUTF8AndSize(text, &_size); - if (astring == NULL) { - return 0; // exception already set. - } - if (strlen(astring) != (size_t)_size) { - PyErr_Format(PyExc_ValueError, - "A null character was found in the text."); - return 0; - } - } - - else if (PyBytes_Check(text)) { - /* Bytes_AsStringAndSize with NULL arg for length emits - ValueError if internal NULL bytes are present */ - if (PyBytes_AsStringAndSize(text, (char **)&astring, NULL) == -1) { - return 0; // exception already set. - } - } - - /* if text is Py_None, leave astring as a null byte to represent 0 + /* if text is Py_None, leave text as a null byte to represent 0 length string */ - if (strlen(astring) == 0) { /* special 0 string case */ + if (strlen(text) == 0) { /* special 0 string case */ int height = TTF_FontHeight(font); *dst_surf = PG_CreateSurface(0, height, PG_PIXELFORMAT_XRGB8888); } else { /* normal case */ if (antialias && !draw_backg) { #if SDL_TTF_VERSION_ATLEAST(2, 0, 18) - *dst_surf = TTF_RenderUTF8_Blended_Wrapped(font, astring, foreg, - wraplength); + *dst_surf = + TTF_RenderUTF8_Blended_Wrapped(font, text, foreg, wraplength); #else - *dst_surf = TTF_RenderUTF8_Blended(font, astring, foreg); + *dst_surf = TTF_RenderUTF8_Blended(font, text, foreg); #endif } else if (antialias) { #if SDL_TTF_VERSION_ATLEAST(2, 0, 18) - *dst_surf = TTF_RenderUTF8_Shaded_Wrapped(font, astring, foreg, - backg, wraplength); + *dst_surf = TTF_RenderUTF8_Shaded_Wrapped(font, text, foreg, backg, + wraplength); #else - *dst_surf = TTF_RenderUTF8_Shaded(font, astring, foreg, backg); + *dst_surf = TTF_RenderUTF8_Shaded(font, text, foreg, backg); #endif } else { #if SDL_TTF_VERSION_ATLEAST(2, 0, 18) *dst_surf = - TTF_RenderUTF8_Solid_Wrapped(font, astring, foreg, wraplength); + TTF_RenderUTF8_Solid_Wrapped(font, text, foreg, wraplength); #else - *dst_surf = TTF_RenderUTF8_Solid(font, astring, foreg); + *dst_surf = TTF_RenderUTF8_Solid(font, text, foreg); #endif /* If an explicit background was provided and the rendering options resolve to Render_Solid, that needs to be explicitly handled. */ @@ -641,7 +613,7 @@ font_render(PyObject *self, PyObject *args, PyObject *kwds) SDL_Surface *outline_surf = NULL; int wraplength = 0; TTF_Font *font = PyFont_AsFont(self); - int outline_size = ((PyFontObject *)self)->outline_size; + int outline_width = ((PyFontObject *)self)->outline_width; SDL_Color outline_color = ((PyFontObject *)self)->outline_color; Uint8 rgba[] = {0, 0, 0, 0}; @@ -658,7 +630,6 @@ font_render(PyObject *self, PyObject *args, PyObject *kwds) return NULL; } - // 글꼴 생성 if (!pg_RGBAFromObjEx(fg_rgba_obj, rgba, PG_COLOR_HANDLE_ALL)) { return 0; // exception already set } @@ -678,23 +649,55 @@ font_render(PyObject *self, PyObject *args, PyObject *kwds) draw_backg = 0; } - if (!_create_font_surface(font, text, antialias, foreg, backg, draw_backg, - wraplength, &surf)) { + const char *astring = ""; + + if (!PyUnicode_Check(text) && !PyBytes_Check(text) && text != Py_None) { + PyErr_Format(PyExc_TypeError, "text must be a unicode or bytes"); + return 0; + } + + if (PyUnicode_Check(text)) { + Py_ssize_t _size = -1; + astring = PyUnicode_AsUTF8AndSize(text, &_size); + if (astring == NULL) { + return 0; // exception already set. + } + if (strlen(astring) != (size_t)_size) { + PyErr_Format(PyExc_ValueError, + "A null character was found in the text."); + return 0; + } + } + + else if (PyBytes_Check(text)) { + /* Bytes_AsStringAndSize with NULL arg for length emits + ValueError if internal NULL bytes are present */ + if (PyBytes_AsStringAndSize(text, (char **)&astring, NULL) == -1) { + return 0; // exception already set. + } + } + + if (!_create_font_surface(font, astring, antialias, foreg, backg, + draw_backg, wraplength, &surf)) { return NULL; } SDL_Surface *filled_with_outline_surf = NULL; - if (outline_size > 0) { - TTF_SetFontOutline(font, outline_size); + if (outline_width > 0) { + TTF_SetFontOutline(font, outline_width); - if (!_create_font_surface(font, text, antialias, outline_color, backg, - draw_backg, wraplength, &outline_surf)) { + if (!_create_font_surface(font, astring, antialias, outline_color, + backg, draw_backg, wraplength, + &outline_surf)) { return NULL; } TTF_SetFontOutline(font, 0); + // Order for rendering the outline and the actual font matters. + // Blitting foreground over outline results in smaller outline than + // otherwise. filled_with_outline_surf = PG_CreateSurface( outline_surf->w, outline_surf->h, SDL_PIXELFORMAT_RGBA32); @@ -894,13 +897,13 @@ font_getter_style_name(PyObject *self, void *closure) } static PyObject * -font_getter_outline_size(PyFontObject *self, void *closure) +font_getter_outline_width(PyFontObject *self, void *closure) { if (!PgFont_GenerationCheck(self)) return RAISE_FONT_QUIT_ERROR(); #if SDL_TTF_VERSION_ATLEAST(2, 0, 12) - return PyLong_FromLong(self->outline_size); + return PyLong_FromLong(self->outline_width); #else return RAISE(pgExc_SDLError, "Incorrect SDL_TTF version (requires 2.0.12)"); @@ -908,7 +911,7 @@ font_getter_outline_size(PyFontObject *self, void *closure) } static int -font_setter_outline_size(PyFontObject *self, PyObject *value, void *closure) +font_setter_outline_width(PyFontObject *self, PyObject *value, void *closure) { if (!PgFont_GenerationCheck(self)) { RAISE_FONT_QUIT_ERROR_RETURN(-1); @@ -920,13 +923,18 @@ font_setter_outline_size(PyFontObject *self, PyObject *value, void *closure) if (PyErr_Occurred() && val == -1) return -1; + if (!PyLong_Check(value)) { + PyErr_SetString(PyExc_TypeError, "outline_width cannot be a float"); + return -1; + } + if (val < 0) { PyErr_SetString(PyExc_ValueError, - "outline_size cannot be less than 0"); + "outline_width cannot be less than 0"); return -1; } - self->outline_size = val; + self->outline_width = val; return 0; #else @@ -1211,8 +1219,8 @@ static PyGetSetDef font_getsets[] = { (setter)font_setter_point_size, DOC_FONT_FONT_POINTSIZE, NULL}, {"outline_color", (getter)font_getter_outline_color, (setter)font_setter_outline_color, DOC_FONT_FONT_OUTLINECOLOR, NULL}, - {"outline_size", (getter)font_getter_outline_size, - (setter)font_setter_outline_size, DOC_FONT_FONT_OUTLINESIZE, NULL}, + {"outline_width", (getter)font_getter_outline_width, + (setter)font_setter_outline_width, DOC_FONT_FONT_OUTLINEWIDTH, NULL}, {NULL, NULL, NULL, NULL, NULL}}; static PyMethodDef font_methods[] = { @@ -1356,7 +1364,7 @@ font_init(PyFontObject *self, PyObject *args, PyObject *kwds) Py_DECREF(obj); self->font = font; self->ptsize = fontsize; - self->outline_size = 0; + self->outline_width = 0; SDL_Color init_color = {0, 0, 0, SDL_ALPHA_OPAQUE}; self->outline_color = init_color; self->ttf_init_generation = current_ttf_generation; diff --git a/src_c/include/pygame_font.h b/src_c/include/pygame_font.h index 2237a71873..4aee7cbb05 100644 --- a/src_c/include/pygame_font.h +++ b/src_c/include/pygame_font.h @@ -29,10 +29,8 @@ typedef struct { PyObject_HEAD TTF_Font *font; PyObject *weakreflist; int ptsize; - SDL_Color outline_color; - int outline_size; - + int outline_width; unsigned int ttf_init_generation; } PyFontObject; #define PyFont_AsFont(x) (((PyFontObject *)x)->font) diff --git a/test/font_test.py b/test/font_test.py index 87b384c2ae..eaafc2e74b 100644 --- a/test/font_test.py +++ b/test/font_test.py @@ -981,7 +981,7 @@ def test_font_property_should_raise_exception_after_quit(self): ("italic", True), ("underline", True), ("strikethrough", True), - ("outline_size", 5), + ("outline_width", 5), ( "outline_color", ( @@ -1066,7 +1066,7 @@ def query( underline=False, strikethrough=False, antialiase=False, - outline_size=0, + outline_width=0, outline_color=(0, 0, 0), ): if self.aborted: @@ -1079,7 +1079,7 @@ def query( screen.fill((255, 255, 255)) pygame.display.flip() if not ( - bold or italic or underline or strikethrough or antialiase or outline_size + bold or italic or underline or strikethrough or antialiase or outline_width ): text = "normal" else: @@ -1094,14 +1094,14 @@ def query( modes.append("strikethrough") if antialiase: modes.append("antialiased") - if outline_size: - modes.append("outline_size") + if outline_width: + modes.append("outline_width") text = f"{'-'.join(modes)} (y/n):" f.set_bold(bold) f.set_italic(italic) f.set_underline(underline) f.set_strikethrough(strikethrough) - f.outline_size = outline_size + f.outline_width = outline_width f.outline_color = outline_color s = f.render(text, antialiase, (0, 0, 0)) screen.blit(s, (offset, y)) @@ -1142,11 +1142,11 @@ def test_strikethrough(self): def test_antialiase(self): self.assertTrue(self.query(antialiase=True)) - def test_outline_size(self): - self.assertTrue(self.query(outline_size=1, outline_color=(255, 0, 255))) + def test_outline_width(self): + self.assertTrue(self.query(outline_width=1, outline_color=(255, 0, 255))) - def test_outline_size_huge(self): - self.assertTrue(self.query(outline_size=10, outline_color=(0, 0, 255))) + def test_outline_width_huge(self): + self.assertTrue(self.query(outline_width=10, outline_color=(0, 0, 255))) def test_bold_antialiase(self): self.assertTrue(self.query(bold=True, antialiase=True))