diff --git a/Tests/images/test_language.png b/Tests/images/test_language.png
new file mode 100644
index 000000000..e55a93c74
Binary files /dev/null and b/Tests/images/test_language.png differ
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;
}