added ability to set language for text rendering

This commit is contained in:
Ben Yang 2019-03-05 20:11:42 -08:00
parent 906917b748
commit 8624efd283
6 changed files with 82 additions and 22 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 837 B

View File

@ -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)

View File

@ -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
<https://www.w3.org/International/articles/language-tags/>`
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
<https://www.w3.org/International/articles/language-tags/>`
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,

View File

@ -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
<https://www.w3.org/International/articles/language-tags/>`
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,

View File

@ -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,

View File

@ -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;
}