From 8624efd283ca2f1df3eb2f780361520d86617525 Mon Sep 17 00:00:00 2001 From: Ben Yang Date: Tue, 5 Mar 2019 20:11:42 -0800 Subject: [PATCH 01/12] added ability to set language for text rendering --- Tests/images/test_language.png | Bin 0 -> 837 bytes Tests/test_imagefontctl.py | 13 +++++++++++ docs/reference/ImageDraw.rst | 22 ++++++++++++++++-- docs/reference/ImageFont.rst | 14 +++++++++-- src/PIL/ImageFont.py | 14 +++++------ src/_imagingft.c | 41 ++++++++++++++++++++++++--------- 6 files changed, 82 insertions(+), 22 deletions(-) create mode 100644 Tests/images/test_language.png diff --git a/Tests/images/test_language.png b/Tests/images/test_language.png new file mode 100644 index 0000000000000000000000000000000000000000..e55a93c746187206f8d88fbdf766217320fcbd48 GIT binary patch literal 837 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!Q#hD_=D5XZr9h~%1r{Hr??mrh zqH@)QBl1IQsehF2`GYwhsttH*FL1vOw^m(Qxrb?a)BL8H30gnwZiH*U5fJr0c>7?) z21D@;%~IOObbgqG+h4!0o>q{$f6C2WKT{R=~Mcxy|8o7qqy(yZe8<`A*N)E7AC|{iV!oz0f4~@U}TS9i5h2DLX z$sqPh?$cI=9dV`gD@{ckav5a&W7=~*hR*o6NLTZx!_(Z!+5vazDZUKxcw>OvB3dvoTpp>krpF z-S;!5Kzopr04NiA ADF6Tf literal 0 HcmV?d00001 diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index d23f6d86f..7953b917d 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -130,3 +130,16 @@ class TestImagecomplextext(PillowTestCase): target_img = Image.open(target) self.assert_image_similar(im, target_img, .5) + + def test_language(self): + ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) + + im = Image.new(mode='RGB', size=(300, 100)) + draw = ImageDraw.Draw(im) + draw.text((0, 0), 'абвг', font=ttf, fill=500, + language='sr') + + target = 'Tests/images/test_language.png' + target_img = Image.open(target) + + self.assert_image_similar(im, target_img, .5) \ No newline at end of file diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 7c24bae93..8cafc5e26 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -255,7 +255,7 @@ Methods Draw a shape. -.. py:method:: PIL.ImageDraw.ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None) +.. py:method:: PIL.ImageDraw.ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, language=None, features=None) Draws the string at the given position. @@ -274,6 +274,16 @@ Methods .. versionadded:: 4.2.0 + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP47 language code + ` + Requires libraqm. + + .. versionadded:: 6.0.0 + :param features: A list of OpenType font features to be used during text layout. This is usually used to turn on optional font features that are not enabled by default, @@ -316,7 +326,7 @@ Methods .. versionadded:: 4.2.0 -.. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None) +.. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, language=None, features=None) Return the size of the given string, in pixels. @@ -330,7 +340,15 @@ Methods Requires libraqm. .. versionadded:: 4.2.0 + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP47 language code + ` + Requires libraqm. + .. versionadded:: 6.0.0 :param features: A list of OpenType font features to be used during text layout. This is usually used to turn on optional font features that are not enabled by default, diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index 55ce3d382..99eb0f1ad 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -47,11 +47,11 @@ Functions Methods ------- -.. py:method:: PIL.ImageFont.ImageFont.getsize(text) +.. py:method:: PIL.ImageFont.ImageFont.getsize(text, direction=None, language=None, features=[]) :return: (width, height) -.. py:method:: PIL.ImageFont.ImageFont.getmask(text, mode='', direction=None, features=[]) +.. py:method:: PIL.ImageFont.ImageFont.getmask(text, mode='', direction=None, language=None, features=[]) Create a bitmap for the text. @@ -72,6 +72,16 @@ Methods .. versionadded:: 4.2.0 + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP47 language code + ` + Requires libraqm. + + .. versionadded:: 6.0.0 + :param features: A list of OpenType font features to be used during text layout. This is usually used to turn on optional font features that are not enabled by default, diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 7454b4413..7b253717a 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -158,17 +158,17 @@ class FreeTypeFont(object): def getmetrics(self): return self.font.ascent, self.font.descent - def getsize(self, text, direction=None, features=None): - size, offset = self.font.getsize(text, direction, features) + def getsize(self, text, direction=None, language=None, features=None): + size, offset = self.font.getsize(text, direction, language, features) return (size[0] + offset[0], size[1] + offset[1]) - def getsize_multiline(self, text, direction=None, + def getsize_multiline(self, text, direction=None, language=None, spacing=4, features=None): max_width = 0 lines = self._multiline_split(text) line_spacing = self.getsize('A')[1] + spacing for line in lines: - line_width, line_height = self.getsize(line, direction, features) + line_width, line_height = self.getsize(line, direction, language, features) max_width = max(max_width, line_width) return max_width, len(lines)*line_spacing - spacing @@ -181,10 +181,10 @@ class FreeTypeFont(object): features=features)[0] def getmask2(self, text, mode="", fill=Image.core.fill, direction=None, - features=None, *args, **kwargs): - size, offset = self.font.getsize(text, direction, features) + language=None, features=None, *args, **kwargs): + size, offset = self.font.getsize(text, direction, language, features) im = fill("L", size, 0) - self.font.render(text, im.id, mode == "1", direction, features) + self.font.render(text, im.id, mode == "1", direction, language, features) return im, offset def font_variant(self, font=None, size=None, index=None, encoding=None, diff --git a/src/_imagingft.c b/src/_imagingft.c index f94e55803..e6b2b1cc5 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -87,6 +87,10 @@ typedef bool (*t_raqm_set_text_utf8) (raqm_t *rq, size_t len); typedef bool (*t_raqm_set_par_direction) (raqm_t *rq, raqm_direction_t dir); +typedef bool (*t_raqm_set_language) (raqm_t *rq, + const char *lang, + size_t start, + size_t len); typedef bool (*t_raqm_add_font_feature) (raqm_t *rq, const char *feature, int len); @@ -106,6 +110,7 @@ typedef struct { t_raqm_set_text set_text; t_raqm_set_text_utf8 set_text_utf8; t_raqm_set_par_direction set_par_direction; + t_raqm_set_language set_language; t_raqm_add_font_feature add_font_feature; t_raqm_set_freetype_face set_freetype_face; t_raqm_layout layout; @@ -160,6 +165,7 @@ setraqm(void) p_raqm.set_text = (t_raqm_set_text)dlsym(p_raqm.raqm, "raqm_set_text"); p_raqm.set_text_utf8 = (t_raqm_set_text_utf8)dlsym(p_raqm.raqm, "raqm_set_text_utf8"); p_raqm.set_par_direction = (t_raqm_set_par_direction)dlsym(p_raqm.raqm, "raqm_set_par_direction"); + p_raqm.set_language = (t_raqm_set_language)dlsym(p_raqm.raqm, "raqm_set_language"); p_raqm.add_font_feature = (t_raqm_add_font_feature)dlsym(p_raqm.raqm, "raqm_add_font_feature"); p_raqm.set_freetype_face = (t_raqm_set_freetype_face)dlsym(p_raqm.raqm, "raqm_set_freetype_face"); p_raqm.layout = (t_raqm_layout)dlsym(p_raqm.raqm, "raqm_layout"); @@ -176,6 +182,7 @@ setraqm(void) p_raqm.set_text && p_raqm.set_text_utf8 && p_raqm.set_par_direction && + p_raqm.set_language && p_raqm.add_font_feature && p_raqm.set_freetype_face && p_raqm.layout && @@ -190,6 +197,7 @@ setraqm(void) p_raqm.set_text = (t_raqm_set_text)GetProcAddress(p_raqm.raqm, "raqm_set_text"); p_raqm.set_text_utf8 = (t_raqm_set_text_utf8)GetProcAddress(p_raqm.raqm, "raqm_set_text_utf8"); p_raqm.set_par_direction = (t_raqm_set_par_direction)GetProcAddress(p_raqm.raqm, "raqm_set_par_direction"); + p_raqm.set_language = (t_raqm_set_language)GetProcAddress(p_raqm.raqm, "raqm_set_language"); p_raqm.add_font_feature = (t_raqm_add_font_feature)GetProcAddress(p_raqm.raqm, "raqm_add_font_feature"); p_raqm.set_freetype_face = (t_raqm_set_freetype_face)GetProcAddress(p_raqm.raqm, "raqm_set_freetype_face"); p_raqm.layout = (t_raqm_layout)GetProcAddress(p_raqm.raqm, "raqm_layout"); @@ -205,6 +213,7 @@ setraqm(void) p_raqm.set_text && p_raqm.set_text_utf8 && p_raqm.set_par_direction && + p_raqm.set_language && p_raqm.add_font_feature && p_raqm.set_freetype_face && p_raqm.layout && @@ -332,7 +341,7 @@ font_getchar(PyObject* string, int index, FT_ULong* char_out) } static size_t -text_layout_raqm(PyObject* string, FontObject* self, const char* dir, +text_layout_raqm(PyObject* string, FontObject* self, const char* dir, const char* lang, PyObject *features ,GlyphInfo **glyph_info, int mask) { int i = 0; @@ -341,6 +350,7 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, raqm_glyph_t *glyphs = NULL; raqm_glyph_t_01 *glyphs_01 = NULL; raqm_direction_t direction; + size_t start = 0; rq = (*p_raqm.create)(); if (rq == NULL) { @@ -360,6 +370,13 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, 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; + } + } + } #if PY_VERSION_HEX < 0x03000000 else if (PyString_Check(string)) { @@ -498,7 +515,7 @@ failed: } static size_t -text_layout_fallback(PyObject* string, FontObject* self, const char* dir, +text_layout_fallback(PyObject* string, FontObject* self, const char* dir, const char* lang, PyObject *features ,GlyphInfo **glyph_info, int mask) { int error, load_flags; @@ -509,8 +526,8 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir, FT_UInt last_index = 0; int i; - if (features != Py_None || dir != NULL) { - PyErr_SetString(PyExc_KeyError, "setting text direction or font features is not supported without libraqm"); + if (features != Py_None || dir != NULL || lang != NULL) { + PyErr_SetString(PyExc_KeyError, "setting text direction, language or font features is not supported without libraqm"); } #if PY_VERSION_HEX >= 0x03000000 if (!PyUnicode_Check(string)) { @@ -564,15 +581,15 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir, } static size_t -text_layout(PyObject* string, FontObject* self, const char* dir, +text_layout(PyObject* string, FontObject* self, const char* dir, const char* lang, PyObject *features, GlyphInfo **glyph_info, int mask) { size_t count; if (p_raqm.raqm && self->layout_engine == LAYOUT_RAQM) { - count = text_layout_raqm(string, self, dir, features, glyph_info, mask); + count = text_layout_raqm(string, self, dir, lang, features, glyph_info, mask); } else { - count = text_layout_fallback(string, self, dir, features, glyph_info, mask); + count = text_layout_fallback(string, self, dir, lang, features, glyph_info, mask); } return count; } @@ -584,6 +601,7 @@ font_getsize(FontObject* self, PyObject* args) FT_Face face; int xoffset, yoffset; const char *dir = NULL; + const char *lang = NULL; size_t count; GlyphInfo *glyph_info = NULL; PyObject *features = Py_None; @@ -591,14 +609,14 @@ font_getsize(FontObject* self, PyObject* args) /* calculate size and bearing for a given string */ PyObject* string; - if (!PyArg_ParseTuple(args, "O|zO:getsize", &string, &dir, &features)) + if (!PyArg_ParseTuple(args, "O|zzO:getsize", &string, &dir, &lang, &features)) return NULL; face = NULL; xoffset = yoffset = 0; y_max = y_min = 0; - count = text_layout(string, self, dir, features, &glyph_info, 0); + count = text_layout(string, self, dir, lang, features, &glyph_info, 0); if (PyErr_Occurred()) { return NULL; } @@ -691,16 +709,17 @@ font_render(FontObject* self, PyObject* args) int temp; int xx, x0, x1; const char *dir = NULL; + const char *lang = NULL; size_t count; GlyphInfo *glyph_info; PyObject *features = NULL; - if (!PyArg_ParseTuple(args, "On|izO:render", &string, &id, &mask, &dir, &features)) { + if (!PyArg_ParseTuple(args, "On|izzO:render", &string, &id, &mask, &dir, &lang, &features)) { return NULL; } glyph_info = NULL; - count = text_layout(string, self, dir, features, &glyph_info, mask); + count = text_layout(string, self, dir, lang, features, &glyph_info, mask); if (PyErr_Occurred()) { return NULL; } From 1a075bed52f71bb6a7123348bc724e6396bfefc9 Mon Sep 17 00:00:00 2001 From: Ben Yang Date: Tue, 5 Mar 2019 20:19:27 -0800 Subject: [PATCH 02/12] added language parameter to ImageFont.FreeTypeFont.getmask() --- src/PIL/ImageFont.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 7b253717a..665735e63 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -176,8 +176,8 @@ class FreeTypeFont(object): def getoffset(self, text): return self.font.getsize(text)[1] - def getmask(self, text, mode="", direction=None, features=None): - return self.getmask2(text, mode, direction=direction, + def getmask(self, text, mode="", direction=None, language=None, features=None): + return self.getmask2(text, mode, direction=direction, language=language, features=features)[0] def getmask2(self, text, mode="", fill=Image.core.fill, direction=None, From 7324a32443878880dda14eb859f2427072b76d92 Mon Sep 17 00:00:00 2001 From: Ben Yang Date: Tue, 5 Mar 2019 20:38:05 -0800 Subject: [PATCH 03/12] fixed some whitespace to pass linting --- Tests/test_imagefontctl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 7953b917d..a00971058 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -138,8 +138,8 @@ class TestImagecomplextext(PillowTestCase): draw = ImageDraw.Draw(im) draw.text((0, 0), 'абвг', font=ttf, fill=500, language='sr') - + target = 'Tests/images/test_language.png' target_img = Image.open(target) - self.assert_image_similar(im, target_img, .5) \ No newline at end of file + self.assert_image_similar(im, target_img, .5) From 386492ff547d7d7b0a1074747bdc2c2c291a29a9 Mon Sep 17 00:00:00 2001 From: Ben Yang Date: Tue, 5 Mar 2019 21:05:40 -0800 Subject: [PATCH 04/12] wrong test_language.png committed, whoops! fixed --- Tests/images/test_language.png | Bin 837 -> 777 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Tests/images/test_language.png b/Tests/images/test_language.png index e55a93c746187206f8d88fbdf766217320fcbd48..8daf007b02c47c110684d4039ad93f8443411712 100644 GIT binary patch delta 656 zcmV;B0&o4r28jlcBYy(WNkl9_gxyT9|hAW4!W{Ui3kyC5mMB#l|^pGTL#9=g2`;+s zNfTuudhC_M;5ATqKZ6a+))A%bu(KFoq3gz%vjgv{MNIJOsLD^sd>Bt zr-Gk?nP4jTKG;_)><|@8!P~)DurU{~gBmNwEci56KiA(UW^iS{qV$+5uL-YKPBM4G z&R{fgx-xoq0Dt@fUncItKyUzF4&F}Oc_isdO=~|3qDzVhD(+3%#!QIryvV{$i*DNN~qRVD1E$}xCoQ*7Zl-ZIGI=nZ{|Yx2f*iv zySc{C5(y4AGGyr){`;ZQmYPR-I=U5(2JgTYDhl1RxqtJQ0Qj-8{~vG*dSC<0)i>lb z08U?}vNb?2TnRdAU2e~vm!_u0s+ei$ZsZ|+9)O#TWGOYV0x%MEzy)|C(Us^(Jel|? z{Xeu+yEawb;%NXD>K^j70JvUtf2lPUfNcOAPh5v=@l}}qsvlQ=1>n`n#xCfFX_&2h zyaD(JzJGh*l%?iU0R9AEG|CIY7vXHsUlsaI0vHL}a*fA=Hn>#ZU{An_U@KgJsfW#6 zY9g=0eC59RTyQ4Hg2TD6fXhJ^d>gzEyWw<@1vi4u#h9hHD2lcA=<~<8@5HI~6o6|m zkeGl4n1^wAFEL)VW=k-fI0w7nd~iAV5U#-9R4MOVRMPV3fIfH&wlvtW7<_#Ht6y0O z)shwGopaLcs^eFrbBxI(X5O~ Date: Tue, 5 Mar 2019 22:26:05 -0800 Subject: [PATCH 05/12] fixed for python2 --- src/_imagingft.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/_imagingft.c b/src/_imagingft.c index e6b2b1cc5..16af78b79 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -389,6 +389,12 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, const char PyErr_SetString(PyExc_ValueError, "raqm_set_text_utf8() 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 { From d5bbf012541f92912152784122390ec15991b1ee Mon Sep 17 00:00:00 2001 From: Ben Yang Date: Wed, 6 Mar 2019 03:01:25 -0800 Subject: [PATCH 06/12] moved 'language' parameter to last parameter in relevant functions --- docs/reference/ImageDraw.rst | 20 ++++++++++---------- docs/reference/ImageFont.rst | 24 ++++++++++++------------ src/PIL/ImageFont.py | 22 +++++++++++----------- src/_imagingft.c | 24 ++++++++++++------------ 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 8cafc5e26..eca32e8ca 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -326,7 +326,7 @@ Methods .. versionadded:: 4.2.0 -.. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, language=None, features=None) +.. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None) Return the size of the given string, in pixels. @@ -340,15 +340,6 @@ Methods Requires libraqm. .. versionadded:: 4.2.0 - :param language: Language of the text. Different languages may use - different glyph shapes or ligatures. This parameter tells - the font which language the text is in, and to apply the - correct substitutions as appropriate, if available. - It should be a `BCP47 language code - ` - Requires libraqm. - - .. versionadded:: 6.0.0 :param features: A list of OpenType font features to be used during text layout. This is usually used to turn on optional font features that are not enabled by default, @@ -361,6 +352,15 @@ Methods Requires libraqm. .. versionadded:: 4.2.0 + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP47 language code + ` + Requires libraqm. + + .. versionadded:: 6.0.0 .. py:method:: PIL.ImageDraw.ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None) diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index 99eb0f1ad..41b6de10a 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -47,11 +47,11 @@ Functions Methods ------- -.. py:method:: PIL.ImageFont.ImageFont.getsize(text, direction=None, language=None, features=[]) +.. py:method:: PIL.ImageFont.ImageFont.getsize(text, direction=None, features=[], language=None) :return: (width, height) -.. py:method:: PIL.ImageFont.ImageFont.getmask(text, mode='', direction=None, language=None, features=[]) +.. py:method:: PIL.ImageFont.ImageFont.getmask(text, mode='', direction=None, features=[], language=None) Create a bitmap for the text. @@ -72,16 +72,6 @@ Methods .. versionadded:: 4.2.0 - :param language: Language of the text. Different languages may use - different glyph shapes or ligatures. This parameter tells - the font which language the text is in, and to apply the - correct substitutions as appropriate, if available. - It should be a `BCP47 language code - ` - Requires libraqm. - - .. versionadded:: 6.0.0 - :param features: A list of OpenType font features to be used during text layout. This is usually used to turn on optional font features that are not enabled by default, @@ -95,5 +85,15 @@ Methods .. versionadded:: 4.2.0 + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP47 language code + ` + Requires libraqm. + + .. versionadded:: 6.0.0 + :return: An internal PIL storage memory instance as defined by the :py:mod:`PIL.Image.core` interface module. diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 665735e63..580aa8744 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -158,17 +158,17 @@ class FreeTypeFont(object): def getmetrics(self): return self.font.ascent, self.font.descent - def getsize(self, text, direction=None, language=None, features=None): - size, offset = self.font.getsize(text, direction, language, features) + def getsize(self, text, direction=None, features=None, language=None): + size, offset = self.font.getsize(text, direction, features, language) return (size[0] + offset[0], size[1] + offset[1]) - def getsize_multiline(self, text, direction=None, language=None, - spacing=4, features=None): + def getsize_multiline(self, text, direction=None, spacing=4, + features=None, language=None): max_width = 0 lines = self._multiline_split(text) line_spacing = self.getsize('A')[1] + spacing for line in lines: - line_width, line_height = self.getsize(line, direction, language, features) + line_width, line_height = self.getsize(line, direction, features, language) max_width = max(max_width, line_width) return max_width, len(lines)*line_spacing - spacing @@ -176,15 +176,15 @@ class FreeTypeFont(object): def getoffset(self, text): return self.font.getsize(text)[1] - def getmask(self, text, mode="", direction=None, language=None, features=None): - return self.getmask2(text, mode, direction=direction, language=language, - features=features)[0] + def getmask(self, text, mode="", direction=None, features=None, language=None): + return self.getmask2(text, mode, direction=direction, features=features, + language=language)[0] def getmask2(self, text, mode="", fill=Image.core.fill, direction=None, - language=None, features=None, *args, **kwargs): - size, offset = self.font.getsize(text, direction, language, features) + features=None, language=None, *args, **kwargs): + size, offset = self.font.getsize(text, direction, features, language) im = fill("L", size, 0) - self.font.render(text, im.id, mode == "1", direction, language, features) + self.font.render(text, im.id, mode == "1", direction, features, language) return im, offset def font_variant(self, font=None, size=None, index=None, encoding=None, diff --git a/src/_imagingft.c b/src/_imagingft.c index 16af78b79..b13c4030b 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -341,8 +341,8 @@ font_getchar(PyObject* string, int index, FT_ULong* char_out) } static size_t -text_layout_raqm(PyObject* string, FontObject* self, const char* dir, const char* lang, - PyObject *features ,GlyphInfo **glyph_info, int mask) +text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject *features, + const char* lang, GlyphInfo **glyph_info, int mask) { int i = 0; raqm_t *rq; @@ -521,8 +521,8 @@ failed: } static size_t -text_layout_fallback(PyObject* string, FontObject* self, const char* dir, const char* lang, - PyObject *features ,GlyphInfo **glyph_info, int mask) +text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObject *features, + const char* lang, GlyphInfo **glyph_info, int mask) { int error, load_flags; FT_ULong ch; @@ -587,15 +587,15 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir, const } static size_t -text_layout(PyObject* string, FontObject* self, const char* dir, const char* lang, - PyObject *features, GlyphInfo **glyph_info, int mask) +text_layout(PyObject* string, FontObject* self, const char* dir, PyObject *features, + const char* lang, GlyphInfo **glyph_info, int mask) { size_t count; if (p_raqm.raqm && self->layout_engine == LAYOUT_RAQM) { - count = text_layout_raqm(string, self, dir, lang, features, glyph_info, mask); + count = text_layout_raqm(string, self, dir, features, lang, glyph_info, mask); } else { - count = text_layout_fallback(string, self, dir, lang, features, glyph_info, mask); + count = text_layout_fallback(string, self, dir, features, lang, glyph_info, mask); } return count; } @@ -615,14 +615,14 @@ font_getsize(FontObject* self, PyObject* args) /* calculate size and bearing for a given string */ PyObject* string; - if (!PyArg_ParseTuple(args, "O|zzO:getsize", &string, &dir, &lang, &features)) + if (!PyArg_ParseTuple(args, "O|zOz:getsize", &string, &dir, &features, &lang)) return NULL; face = NULL; xoffset = yoffset = 0; y_max = y_min = 0; - count = text_layout(string, self, dir, lang, features, &glyph_info, 0); + count = text_layout(string, self, dir, features, lang, &glyph_info, 0); if (PyErr_Occurred()) { return NULL; } @@ -720,12 +720,12 @@ font_render(FontObject* self, PyObject* args) GlyphInfo *glyph_info; PyObject *features = NULL; - if (!PyArg_ParseTuple(args, "On|izzO:render", &string, &id, &mask, &dir, &lang, &features)) { + if (!PyArg_ParseTuple(args, "On|izOz:render", &string, &id, &mask, &dir, &features, &lang)) { return NULL; } glyph_info = NULL; - count = text_layout(string, self, dir, lang, features, &glyph_info, mask); + count = text_layout(string, self, dir, features, lang, &glyph_info, mask); if (PyErr_Occurred()) { return NULL; } From c6ad86717888c99a009694ec4ebeb06565abe4b5 Mon Sep 17 00:00:00 2001 From: Ben Yang Date: Wed, 6 Mar 2019 03:14:09 -0800 Subject: [PATCH 07/12] added proper documentation for ImageFont.getsize() --- docs/reference/ImageFont.rst | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index 41b6de10a..b30bdac03 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -49,6 +49,40 @@ Methods .. py:method:: PIL.ImageFont.ImageFont.getsize(text, direction=None, features=[], language=None) + Returns width and height (in pixels) of given text if rendered in font with + provided direction, features, and language. + + :param text: Text to measure. + + :param direction: Direction of the text. It can be 'rtl' (right to + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. + + .. versionadded:: 4.2.0 + + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example 'dlig' or 'ss01', but can be also + used to turn off default font features for + example '-liga' to disable ligatures or '-kern' + to disable kerning. To get all supported + features, see + https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + Requires libraqm. + + .. versionadded:: 4.2.0 + + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP47 language code + ` + Requires libraqm. + + .. versionadded:: 6.0.0 + :return: (width, height) .. py:method:: PIL.ImageFont.ImageFont.getmask(text, mode='', direction=None, features=[], language=None) From 515244b672524a247815448bc972b87b2eb1ae01 Mon Sep 17 00:00:00 2001 From: Ben Yang Date: Wed, 6 Mar 2019 14:07:30 -0800 Subject: [PATCH 08/12] moved language parameter in ImageDraw documentation --- docs/reference/ImageDraw.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index eca32e8ca..a01908e1a 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -255,7 +255,7 @@ Methods Draw a shape. -.. py:method:: PIL.ImageDraw.ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, language=None, features=None) +.. py:method:: PIL.ImageDraw.ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None, language=None) Draws the string at the given position. @@ -274,16 +274,6 @@ Methods .. versionadded:: 4.2.0 - :param language: Language of the text. Different languages may use - different glyph shapes or ligatures. This parameter tells - the font which language the text is in, and to apply the - correct substitutions as appropriate, if available. - It should be a `BCP47 language code - ` - Requires libraqm. - - .. versionadded:: 6.0.0 - :param features: A list of OpenType font features to be used during text layout. This is usually used to turn on optional font features that are not enabled by default, @@ -297,6 +287,16 @@ Methods .. versionadded:: 4.2.0 + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP47 language code + ` + Requires libraqm. + + .. versionadded:: 6.0.0 + .. py:method:: PIL.ImageDraw.ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None) Draws the string at the given position. From 5cdbec0cfeca59bc1cde1680044b0d16be9875e4 Mon Sep 17 00:00:00 2001 From: Ben Yang Date: Wed, 6 Mar 2019 17:04:48 -0800 Subject: [PATCH 09/12] added test for attempting to apply complex settings to fonts when using basic layout engine --- Tests/test_imagefont.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index be8667211..aaf657ff3 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -525,6 +525,14 @@ class TestImageFont(PillowTestCase): self.assertEqual(t.getsize_multiline('ABC\nA'), (36, 36)) self.assertEqual(t.getsize_multiline('ABC\nAaaa'), (48, 36)) + # def test_complex_font_settings(self): + # # Arrange + # t = self.get_font() + # # Act / Assert + # if t.layout_engine == ImageFont.LAYOUT_BASIC: + # self.assertRaises(KeyError, t.getmask, 'абвг', direction='rtl') + # self.assertRaises(KeyError, t.getmask, 'абвг', features=['-kern']) + # self.assertRaises(KeyError, t.getmask, 'абвг', language='sr') @unittest.skipUnless(HAS_RAQM, "Raqm not Available") class TestImageFont_RaqmLayout(TestImageFont): From 9f390a51925ab5ab9d1f386b65d44ddaf5c146f6 Mon Sep 17 00:00:00 2001 From: Ben Yang Date: Wed, 6 Mar 2019 17:20:12 -0800 Subject: [PATCH 10/12] uncommented test --- Tests/test_imagefont.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index aaf657ff3..ba5821c36 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -525,14 +525,15 @@ class TestImageFont(PillowTestCase): self.assertEqual(t.getsize_multiline('ABC\nA'), (36, 36)) self.assertEqual(t.getsize_multiline('ABC\nAaaa'), (48, 36)) - # def test_complex_font_settings(self): - # # Arrange - # t = self.get_font() - # # Act / Assert - # if t.layout_engine == ImageFont.LAYOUT_BASIC: - # self.assertRaises(KeyError, t.getmask, 'абвг', direction='rtl') - # self.assertRaises(KeyError, t.getmask, 'абвг', features=['-kern']) - # self.assertRaises(KeyError, t.getmask, 'абвг', language='sr') + def test_complex_font_settings(self): + # Arrange + t = self.get_font() + # Act / Assert + if t.layout_engine == ImageFont.LAYOUT_BASIC: + self.assertRaises(KeyError, t.getmask, 'абвг', direction='rtl') + self.assertRaises(KeyError, t.getmask, 'абвг', features=['-kern']) + self.assertRaises(KeyError, t.getmask, 'абвг', language='sr') + @unittest.skipUnless(HAS_RAQM, "Raqm not Available") class TestImageFont_RaqmLayout(TestImageFont): From 8bd4bbb808ddacc6a5bb4b3429981dc73953c791 Mon Sep 17 00:00:00 2001 From: Ben Yang Date: Mon, 11 Mar 2019 20:21:52 -0700 Subject: [PATCH 11/12] implemented language parameter for multiline ImageDraw methods, updated release notes --- docs/reference/ImageDraw.rst | 24 ++++++++++++++++++++++-- docs/releasenotes/6.0.0.rst | 15 +++++++++++++++ src/PIL/ImageDraw.py | 21 +++++++++++++-------- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index a01908e1a..b50b770d0 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -297,7 +297,7 @@ Methods .. versionadded:: 6.0.0 -.. py:method:: PIL.ImageDraw.ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None) +.. py:method:: PIL.ImageDraw.ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None, language=None) Draws the string at the given position. @@ -326,6 +326,16 @@ Methods .. versionadded:: 4.2.0 + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP47 language code + ` + Requires libraqm. + + .. versionadded:: 6.0.0 + .. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None) Return the size of the given string, in pixels. @@ -362,7 +372,7 @@ Methods .. versionadded:: 6.0.0 -.. py:method:: PIL.ImageDraw.ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None) +.. py:method:: PIL.ImageDraw.ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None) Return the size of the given string, in pixels. @@ -388,6 +398,16 @@ Methods .. versionadded:: 4.2.0 + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP47 language code + ` + Requires libraqm. + + .. versionadded:: 6.0.0 + .. py:method:: PIL.ImageDraw.getdraw(im=None, hints=None) .. warning:: This method is experimental. diff --git a/docs/releasenotes/6.0.0.rst b/docs/releasenotes/6.0.0.rst index 8894fd99f..caf501014 100644 --- a/docs/releasenotes/6.0.0.rst +++ b/docs/releasenotes/6.0.0.rst @@ -99,6 +99,21 @@ version. Use ``PIL.__version__`` instead. +New ``language`` parameter +^^^^^^^^^^^^^^^^^^^^^^^^ + +Text rendering functions now accept a ``language`` parameter, to request language-specific glyphs and ligatures from the font + +The following functions accept the new parameter: + +* ``PIL.ImageDraw.ImageDraw.text()`` +* ``PIL.ImageDraw.ImageDraw.multiline_text()`` +* ``PIL.ImageDraw.ImageDraw.textsize()`` +* ``PIL.ImageDraw.ImageDraw.multiline_textsize()`` +* ``PIL.ImageFont.ImageFont.getsize()`` +* ``PIL.ImageFont.ImageFont.getsize_multiline()`` +* ``PIL.ImageFont.ImageFont.getmask()`` + API Additions ============= diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index ac549790a..86512bb82 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -282,13 +282,17 @@ class ImageDraw(object): self.draw.draw_bitmap(xy, mask, ink) def multiline_text(self, xy, text, fill=None, font=None, anchor=None, - spacing=4, align="left", direction=None, features=None): + spacing=4, align="left", direction=None, features=None, + language=None): widths = [] max_width = 0 lines = self._multiline_split(text) line_spacing = self.textsize('A', font=font)[1] + spacing for line in lines: - line_width, line_height = self.textsize(line, font) + line_width, line_height = self.textsize(line, font, + direction=direction, + features=features, + language=language) widths.append(line_width) max_width = max(max_width, line_width) left, top = xy @@ -302,29 +306,30 @@ class ImageDraw(object): else: raise ValueError('align must be "left", "center" or "right"') self.text((left, top), line, fill, font, anchor, - direction=direction, features=features) + direction=direction, features=features, language=language) top += line_spacing left = xy[0] def textsize(self, text, font=None, spacing=4, direction=None, - features=None): + features=None, language=None): """Get the size of a given string, in pixels.""" if self._multiline_check(text): return self.multiline_textsize(text, font, spacing, - direction, features) + direction, features, language) if font is None: font = self.getfont() - return font.getsize(text, direction, features) + return font.getsize(text, direction, features, language) def multiline_textsize(self, text, font=None, spacing=4, direction=None, - features=None): + features=None, language=None): max_width = 0 lines = self._multiline_split(text) line_spacing = self.textsize('A', font=font)[1] + spacing for line in lines: line_width, line_height = self.textsize(line, font, spacing, - direction, features) + direction, features, + language) max_width = max(max_width, line_width) return max_width, len(lines)*line_spacing - spacing From 33d207b3f01067ddd638b050ded6228657b42838 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 12 Mar 2019 11:12:19 +0200 Subject: [PATCH 12/12] Move to API Additions, not Deprecations [CI skip] And some minor editing to make more concise/consistent. --- docs/releasenotes/6.0.0.rst | 41 +++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/docs/releasenotes/6.0.0.rst b/docs/releasenotes/6.0.0.rst index caf501014..87fcce3ae 100644 --- a/docs/releasenotes/6.0.0.rst +++ b/docs/releasenotes/6.0.0.rst @@ -99,35 +99,35 @@ version. Use ``PIL.__version__`` instead. -New ``language`` parameter -^^^^^^^^^^^^^^^^^^^^^^^^ - -Text rendering functions now accept a ``language`` parameter, to request language-specific glyphs and ligatures from the font - -The following functions accept the new parameter: - -* ``PIL.ImageDraw.ImageDraw.text()`` -* ``PIL.ImageDraw.ImageDraw.multiline_text()`` -* ``PIL.ImageDraw.ImageDraw.textsize()`` -* ``PIL.ImageDraw.ImageDraw.multiline_textsize()`` -* ``PIL.ImageFont.ImageFont.getsize()`` -* ``PIL.ImageFont.ImageFont.getsize_multiline()`` -* ``PIL.ImageFont.ImageFont.getmask()`` - API Additions ============= -DIB File Format +DIB file format ^^^^^^^^^^^^^^^ -Pillow now supports reading and writing the DIB "Device Independent Bitmap" file format. +Pillow now supports reading and writing the Device Independent Bitmap file format. Image.quantize ^^^^^^^^^^^^^^ -The `dither` option is now a customisable parameter (was previously hardcoded to `1`). This parameter takes the same values used in `Image.convert` +The ``dither`` option is now a customisable parameter (was previously hardcoded to ``1``). +This parameter takes the same values used in ``Image.convert``. -PNG EXIF Data +New language parameter +^^^^^^^^^^^^^^^^^^^^^^ + +These text-rendering functions now accept a ``language`` parameter to request +language-specific glyphs and ligatures from the font: + +* ``ImageDraw.ImageDraw.multiline_text()`` +* ``ImageDraw.ImageDraw.multiline_textsize()`` +* ``ImageDraw.ImageDraw.text()`` +* ``ImageDraw.ImageDraw.textsize()`` +* ``ImageFont.ImageFont.getmask()`` +* ``ImageFont.ImageFont.getsize_multiline()`` +* ``ImageFont.ImageFont.getsize()`` + +PNG EXIF data ^^^^^^^^^^^^^ EXIF data can now be read from and saved to PNG images. However, unlike other image @@ -145,4 +145,5 @@ Pillow can now read uncompressed RGB data from DDS images. Reading TIFF with old-style JPEG compression ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Added support reading TIFF files with old-style JPEG compression through LibTIFF. All YCbCr TIFF images are now always read as RGB. +Added support reading TIFF files with old-style JPEG compression through LibTIFF. All YCbCr +TIFF images are now always read as RGB.