diff --git a/Tests/fonts/LICENSE.txt b/Tests/fonts/LICENSE.txt index 0e0baaabb05..726d5d797fd 100644 --- a/Tests/fonts/LICENSE.txt +++ b/Tests/fonts/LICENSE.txt @@ -1,5 +1,5 @@ -NotoNastaliqUrdu-Regular.ttf, from https://github.com/googlei18n/noto-fonts +NotoNastaliqUrdu-Regular.ttf and NotoSansSymbols-Regular.ttf, from https://github.com/googlei18n/noto-fonts NotoSansJP-Thin.otf, from https://www.google.com/get/noto/help/cjk/ AdobeVFPrototype.ttf, from https://github.com/adobe-fonts/adobe-variable-font-prototype TINY5x3GX.ttf, from http://velvetyne.fr/fonts/tiny diff --git a/Tests/fonts/NotoSansSymbols-Regular.ttf b/Tests/fonts/NotoSansSymbols-Regular.ttf new file mode 100644 index 00000000000..92accef72d4 Binary files /dev/null and b/Tests/fonts/NotoSansSymbols-Regular.ttf differ diff --git a/Tests/images/unicode_extended.png b/Tests/images/unicode_extended.png new file mode 100644 index 00000000000..c0ffad3c69e Binary files /dev/null and b/Tests/images/unicode_extended.png differ diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 0ee3b979e83..5532750feb1 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -463,6 +463,26 @@ def test_unicode_pilfont(self): with self.assertRaises(UnicodeEncodeError): font.getsize(u"’") + @unittest.skipIf( + sys.platform.startswith("win32") and sys.version.startswith("2"), + "requires Python 3.x on Windows", + ) + def test_unicode_extended(self): + # issue #3777 + text = u"A\u278A\U0001F12B" + target = "Tests/images/unicode_extended.png" + + ttf = ImageFont.truetype( + "Tests/fonts/NotoSansSymbols-Regular.ttf", + FONT_SIZE, + layout_engine=self.LAYOUT_ENGINE, + ) + img = Image.new("RGB", (100, 60)) + d = ImageDraw.Draw(img) + d.text((10, 10), text, font=ttf) + + self.assert_image_similar_tofile(img, target, self.metrics["multiline"]) + def _test_fake_loading_font(self, path_to_fake, fontname): # Make a copy of FreeTypeFont so we can patch the original free_type_font = copy.deepcopy(ImageFont.FreeTypeFont) diff --git a/src/_imagingft.c b/src/_imagingft.c index 2567e30d128..4a3b3ccff1e 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -327,6 +327,7 @@ getfont(PyObject* self_, PyObject* args, PyObject* kw) static int font_getchar(PyObject* string, int index, FT_ULong* char_out) { +#if PY_VERSION_HEX < 0x03000000 if (PyUnicode_Check(string)) { Py_UNICODE* p = PyUnicode_AS_UNICODE(string); int size = PyUnicode_GET_SIZE(string); @@ -336,7 +337,6 @@ font_getchar(PyObject* string, int index, FT_ULong* char_out) return 1; } -#if PY_VERSION_HEX < 0x03000000 if (PyString_Check(string)) { unsigned char* p = (unsigned char*) PyString_AS_STRING(string); int size = PyString_GET_SIZE(string); @@ -345,6 +345,13 @@ font_getchar(PyObject* string, int index, FT_ULong* char_out) *char_out = (unsigned char) p[index]; return 1; } +#else + if (PyUnicode_Check(string)) { + if (index >= PyUnicode_GET_LENGTH(string)) + return 0; + *char_out = PyUnicode_READ_CHAR(string, index); + return 1; + } #endif return 0; @@ -366,6 +373,7 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * goto failed; } +#if PY_VERSION_HEX < 0x03000000 if (PyUnicode_Check(string)) { Py_UNICODE *text = PyUnicode_AS_UNICODE(string); Py_ssize_t size = PyUnicode_GET_SIZE(string); @@ -385,9 +393,7 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * } } - } -#if PY_VERSION_HEX < 0x03000000 - else if (PyString_Check(string)) { + } else if (PyString_Check(string)) { char *text = PyString_AS_STRING(string); int size = PyString_GET_SIZE(string); if (! size) { @@ -404,6 +410,28 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * } } } +#else + if (PyUnicode_Check(string)) { + Py_UCS4 *text = PyUnicode_AsUCS4Copy(string); + Py_ssize_t size = PyUnicode_GET_LENGTH(string); + if (!text || !size) { + /* return 0 and clean up, no glyphs==no size, + and raqm fails with empty strings */ + goto failed; + } + int set_text = (*p_raqm.set_text)(rq, (const uint32_t *)(text), size); + PyMem_Free(text); + if (!set_text) { + PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed"); + goto failed; + } + if (lang) { + if (!(*p_raqm.set_language)(rq, lang, start, size)) { + PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed"); + goto failed; + } + } + } #endif else { PyErr_SetString(PyExc_TypeError, "expected string");