Skip to content

Add outline option for rendering fonts #2828

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions buildconfig/stubs/pygame/font.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
11 changes: 11 additions & 0 deletions docs/reST/ref/font.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
1 change: 1 addition & 0 deletions src_c/doc/font_doc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
30 changes: 30 additions & 0 deletions src_c/font.c
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,34 @@ 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)
{
if (!PgFont_GenerationCheck(self)) {
RAISE_FONT_QUIT_ERROR_RETURN(-1);
}

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)
{
if (!PgFont_GenerationCheck(self)) {
return RAISE_FONT_QUIT_ERROR();
}

return PyLong_FromLong(TTF_GetFontOutline(PyFont_AsFont(self)));
}

static PyObject *
font_metrics(PyObject *self, PyObject *textobj)
{
Expand Down Expand Up @@ -1058,6 +1086,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,
DOC_FONT_FONT_OUTLINE, NULL},
{NULL, NULL, NULL, NULL, NULL}};

static PyMethodDef font_methods[] = {
Expand Down
13 changes: 12 additions & 1 deletion test/font_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -992,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 = []
Expand Down Expand Up @@ -1057,6 +1061,7 @@ def query(
underline=False,
strikethrough=False,
antialiase=False,
outline=False,
):
if self.aborted:
return False
Expand All @@ -1067,7 +1072,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 = []
Expand All @@ -1081,11 +1086,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
Expand Down Expand Up @@ -1125,6 +1133,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))

Expand Down
Loading