Skip to content
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

Remove restriction on rendering 4-byte characters on pygame.font part 2 of 2 #2746

Merged
merged 3 commits into from
Oct 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 14 additions & 7 deletions docs/reST/ref/font.rst
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,15 @@ loaded instead.

The text can only be a single line: newline characters are not rendered.
Null characters ('\x00') raise a TypeError. Both Unicode and char (byte)
strings are accepted. For Unicode strings only UCS-2 characters ('\u0001'
to '\uFFFF') are recognized. Anything greater raises a UnicodeError. For
char strings a ``LATIN1`` encoding is assumed. The antialias argument is
a boolean: if true the characters will have smooth edges. The color
argument is the color of the text [e.g.: (0,0,255) for blue]. The
optional background argument is a color to use for the text background.
If no background is passed the area outside the text will be transparent.
strings are accepted. For Unicode strings only UCS-2 characters
('\u0001' to '\uFFFF') were previously supported and any greater unicode
codepoint would raise a UnicodeError. Now, characters in the UCS-4 range
are supported. For char strings a ``LATIN1`` encoding is assumed. The
antialias argument is a boolean: if true the characters will have smooth
edges. The color argument is the color of the text
[e.g.: (0,0,255) for blue]. The optional background argument is a color
to use for the text background. If no background is passed the area
outside the text will be transparent.

The Surface returned will be of the dimensions required to hold the text.
(the same as those returned by Font.size()). If an empty string is passed
Expand All @@ -246,6 +248,11 @@ loaded instead.
Font rendering is not thread safe: only a single thread can render text
at any time.

.. versionchanged:: 2.0.3 Rendering UCS_4 unicode works and does not
raise an exception. Use `if hasattr(pygame.font, 'UCS_4'):` to see if
pygame supports rendering UCS_4 unicode including more languages and
emoji.

.. ## Font.render ##

.. method:: size
Expand Down
19 changes: 18 additions & 1 deletion src_c/font.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@
#define FONT_HAVE_RWOPS 0
#endif

#ifndef SDL_TTF_VERSION_ATLEAST
#define SDL_TTF_COMPILEDVERSION \
SDL_VERSIONNUM(SDL_TTF_MAJOR_VERSION, SDL_TTF_MINOR_VERSION, SDL_TTF_PATCHLEVEL)
#define SDL_TTF_VERSION_ATLEAST(X, Y, Z) \
(SDL_TTF_COMPILEDVERSION >= SDL_VERSIONNUM(X, Y, Z))
#endif

#if PY3
#define RAISE_TEXT_TYPE_ERROR() \
RAISE(PyExc_TypeError, "text must be a unicode or bytes");
Expand Down Expand Up @@ -511,12 +518,14 @@ font_render(PyObject *self, PyObject *args)
return RAISE(PyExc_ValueError,
"A null character was found in the text");
}
#if !SDL_TTF_VERSION_ATLEAST(2, 0, 15)
if (utf_8_needs_UCS_4(astring)) {
Py_DECREF(bytes);
return RAISE(PyExc_UnicodeError,
"A Unicode character above '\\uFFFF' was found;"
" not supported");
" not supported with SDL_ttf version below 2.0.15");
}
#endif
if (aa) {
if (bg_rgba_obj == NULL) {
surf = TTF_RenderUTF8_Blended(font, astring, foreg);
Expand Down Expand Up @@ -1045,6 +1054,14 @@ MODINIT_DEFINE(font)
MODINIT_ERROR;
}

#if SDL_TTF_VERSION_ATLEAST(2, 0, 15)
/* So people can check for UCS4 support. */
if (PyModule_AddIntConstant(module, "UCS4", 1)) {
DECREF_MOD(module);
MODINIT_ERROR;
}
#endif

/* export the c api */
c_api[0] = &PyFont_Type;
c_api[1] = PyFont_New;
Expand Down
22 changes: 12 additions & 10 deletions test/font_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,21 +387,23 @@ def test_render(self):
else:
self.assertFalse(equal_images(su, sb))

# If the font module is SDL_ttf based, then it can only supports UCS-2;
# it will raise an exception for an out-of-range UCS-4 code point.
if UCS_4 and not hasattr(f, "ucs4"):
ucs_2 = as_unicode(r"\uFFEE")
s = f.render(ucs_2, False, [0, 0, 0], [255, 255, 255])
ucs_4 = as_unicode(r"\U00010000")
self.assertRaises(
UnicodeError, f.render, ucs_4, False, [0, 0, 0], [255, 255, 255]
)

b = as_bytes("ab\x00cd")
self.assertRaises(ValueError, f.render, b, 0, [0, 0, 0])
u = as_unicode("ab\x00cd")
self.assertRaises(ValueError, f.render, b, 0, [0, 0, 0])

def test_render_ucs2_ucs4(self):
""" that it renders without raising if there is a new enough SDL_ttf.
"""
f = pygame_font.Font(None, 20)
# If the font module is SDL_ttf < 2.0.15 based, then it only supports UCS-2
# it will raise an exception for an out-of-range UCS-4 code point.
if UCS_4 and hasattr(pygame_font, "UCS_4"):
ucs_2 = as_unicode(r"\uFFEE")
s = f.render(ucs_2, False, [0, 0, 0], [255, 255, 255])
ucs_4 = as_unicode(r"\U00010000")
s = f.render(ucs_4, False, [0, 0, 0], [255, 255, 255])

def test_set_bold(self):
f = pygame_font.Font(None, 20)
self.assertFalse(f.get_bold())
Expand Down