This commit is contained in:
Fahad Al-Saidi 2017-01-03 07:41:38 +00:00 committed by GitHub
commit 6a168ad9a4
26 changed files with 549 additions and 96 deletions

View File

@ -20,8 +20,9 @@ python:
dist: trusty
install:
- "travis_retry sudo add-apt-repository -y ppa:as-bahanta/raqm"
- "travis_retry sudo apt-get update"
- "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick"
- "travis_retry sudo apt-get -qq install libfreetype6-dev libharfbuzz-dev libfribidi-dev libraqm-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick"
- "travis_retry pip install cffi"
- "travis_retry pip install nose"
- "travis_retry pip install check-manifest"

View File

@ -222,7 +222,6 @@ class ImageDraw(object):
if self._multiline_check(text):
return self.multiline_text(xy, text, fill, font, anchor,
*args, **kwargs)
ink, fill = self._getink(fill)
if font is None:
font = self.getfont()
@ -230,17 +229,17 @@ class ImageDraw(object):
ink = fill
if ink is not None:
try:
mask, offset = font.getmask2(text, self.fontmode)
mask, offset = font.getmask2(text, self.fontmode, *args, **kwargs)
xy = xy[0] + offset[0], xy[1] + offset[1]
except AttributeError:
try:
mask = font.getmask(text, self.fontmode)
mask = font.getmask(text, self.fontmode, *args, **kwargs)
except TypeError:
mask = font.getmask(text)
self.draw.draw_bitmap(xy, mask, ink)
def multiline_text(self, xy, text, fill=None, font=None, anchor=None,
spacing=4, align="left"):
spacing=4, align="left", direction=None, features=None):
widths = []
max_width = 0
lines = self._multiline_split(text)
@ -259,7 +258,7 @@ class ImageDraw(object):
left += (max_width - widths[idx])
else:
assert False, 'align must be "left", "center" or "right"'
self.text((left, top), line, fill, font, anchor)
self.text((left, top), line, fill, font, anchor, direction=direction, features=features)
top += line_spacing
left = xy[0]

View File

@ -136,20 +136,20 @@ class FreeTypeFont(object):
def getmetrics(self):
return self.font.ascent, self.font.descent
def getsize(self, text):
size, offset = self.font.getsize(text)
def getsize(self, text, direction=None, features=None):
size, offset = self.font.getsize(text, direction, features)
return (size[0] + offset[0], size[1] + offset[1])
def getoffset(self, text):
return self.font.getsize(text)[1]
def getmask(self, text, mode=""):
return self.getmask2(text, mode)[0]
def getmask(self, text, mode="", direction=None, features=None):
return self.getmask2(text, mode, direction=direction, features=features)[0]
def getmask2(self, text, mode="", fill=Image.core.fill):
size, offset = self.font.getsize(text)
def getmask2(self, text, mode="", fill=Image.core.fill, direction=None, features=None):
size, offset = self.font.getsize(text, direction, features)
im = fill("L", size, 0)
self.font.render(text, im.id, mode == "1")
self.font.render(text, im.id, mode == "1", direction, features)
return im, offset
def font_variant(self, font=None, size=None, index=None, encoding=None):

View File

@ -4,6 +4,7 @@ modules = {
"pil": "PIL._imaging",
"tkinter": "PIL._imagingtk",
"freetype2": "PIL._imagingft",
"raqm": "PIL._imagingft",
"littlecms2": "PIL._imagingcms",
"webp": "PIL._webp",
"transp_webp": ("WEBP", "WebPDecoderBuggyAlpha")

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 963 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

BIN
Tests/images/test_text.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

141
Tests/test_imagefontctl.py Normal file
View File

@ -0,0 +1,141 @@
# -*- coding: utf-8 -*-
from helper import unittest, PillowTestCase
from PIL import Image
from PIL import ImageDraw, ImageFont
#check if raqm installed
have_raqm = ImageFont.core.have_raqm
FONT_SIZE = 20
FONT_PATH = "Tests/fonts/DejaVuSans.ttf"
try:
from PIL import ImageFont
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
im = Image.new(mode='RGB', size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), 'TEST', font=ttf, fill=500, direction='ltr')
@unittest.skipIf(not have_raqm, "Raqm Library is not installed !")
class TestImagecomplextext(PillowTestCase):
def test_complex_text(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)
target = 'Tests/images/test_text.png'
target_img = Image.open(target)
self.assert_image_similar(im, target_img, .5)
def test_y_offset(self):
ttf = ImageFont.truetype("Tests/fonts/NotoNastaliqUrdu-Regular.ttf", FONT_SIZE)
im = Image.new(mode='RGB', size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), 'العالم العربي', font=ttf, fill=500)
target = 'Tests/images/test_y_offset.png'
target_img = Image.open(target)
self.assert_image_similar(im, target_img, .5)
def test_complex_unicode_text(self):
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
im = Image.new(mode='RGB', size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), u'السلام عليكم', font=ttf, fill=500)
target = 'Tests/images/test_complex_unicode_text.png'
target_img = Image.open(target)
self.assert_image_similar(im, target_img, .5)
def test_text_direction_rtl(self):
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
im = Image.new(mode='RGB', size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), 'English عربي', font=ttf, fill=500, direction='rtl')
target = 'Tests/images/test_direction_rtl.png'
target_img = Image.open(target)
self.assert_image_similar(im, target_img, .5)
def test_text_direction_ltr(self):
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
im = Image.new(mode='RGB', size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), 'سلطنة عمان Oman', font=ttf, fill=500, direction='ltr')
target = 'Tests/images/test_direction_ltr.png'
target_img = Image.open(target)
self.assert_image_similar(im, target_img, .5)
def test_text_direction_rtl2(self):
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
im = Image.new(mode='RGB', size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), 'Oman سلطنة عمان', font=ttf, fill=500, direction='rtl')
target = 'Tests/images/test_direction_ltr.png'
target_img = Image.open(target)
self.assert_image_similar(im, target_img, .5)
def test_ligature_features(self):
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
im = Image.new(mode='RGB', size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), 'filling', font=ttf, fill=500, features=['-liga'])
target = 'Tests/images/test_ligature_features.png'
target_img = Image.open(target)
self.assert_image_similar(im, target_img, .5)
def test_kerning_features(self):
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
im = Image.new(mode='RGB', size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), 'TeToAV', font=ttf, fill=500, features=['-kern'])
target = 'Tests/images/test_kerning_features.png'
target_img = Image.open(target)
self.assert_image_similar(im, target_img, .5)
def test_arabictext_features(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, features=['-fina','-init','-medi'])
target = 'Tests/images/test_arabictext_features.png'
target_img = Image.open(target)
self.assert_image_similar(im, target_img, .5)
except ImportError:
class TestImagecomplextext(PillowTestCase):
def test_skip(self):
self.skipTest("ImportError")
except KeyError:
class TestImagecomplextext(PillowTestCase):
def test_skip(self):
self.skipTest("KeyError")
if __name__ == '__main__':
unittest.main()
# End of file

View File

@ -41,6 +41,15 @@
#define FT_ERRORDEF( e, v, s ) { e, s },
#define FT_ERROR_START_LIST {
#define FT_ERROR_END_LIST { 0, 0 } };
#ifdef HAVE_RAQM
#include <raqm.h>
#endif
typedef struct
{
int index, x_offset, x_advance, y_offset;
unsigned int cluster;
} GlyphInfo;
struct {
int code;
@ -188,60 +197,284 @@ font_getchar(PyObject* string, int index, FT_ULong* char_out)
return 0;
}
static PyObject*
font_getsize(FontObject* self, PyObject* args)
#ifdef HAVE_RAQM
static size_t
text_layout_raqm(PyObject* string, FontObject* self, const char* dir,
PyObject *features ,GlyphInfo **glyph_info, int mask)
{
int i, x, y_max, y_min;
int i = 0;
raqm_t *rq;
size_t count = 0;
raqm_glyph_t *glyphs;
raqm_direction_t direction;
rq = raqm_create();
if (rq == NULL) {
PyErr_SetString(PyExc_ValueError, "raqm_create() failed.");
goto failed;
}
if (PyUnicode_Check(string)) {
Py_UNICODE *text = PyUnicode_AS_UNICODE(string);
Py_ssize_t size = PyUnicode_GET_SIZE(string);
if (!raqm_set_text(rq, (const uint32_t *)(text), size)) {
PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
goto failed;
}
}
#if PY_VERSION_HEX < 0x03000000
else if (PyString_Check(string)) {
char *text = PyString_AS_STRING(string);
int size = PyString_GET_SIZE(string);
if (!raqm_set_text_utf8(rq, text, size)) {
PyErr_SetString(PyExc_ValueError, "raqm_set_text_utf8() failed");
goto failed;
}
}
#endif
else {
PyErr_SetString(PyExc_TypeError, "expected string");
goto failed;
}
direction = RAQM_DIRECTION_DEFAULT;
if (dir) {
if (strcmp(dir, "rtl") == 0)
direction = RAQM_DIRECTION_RTL;
else if (strcmp(dir, "ltr") == 0)
direction = RAQM_DIRECTION_LTR;
else if (strcmp(dir, "ttb") == 0)
direction = RAQM_DIRECTION_TTB;
else {
PyErr_SetString(PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'");
goto failed;
}
}
if (!raqm_set_par_direction(rq, direction)) {
PyErr_SetString(PyExc_ValueError, "raqm_set_par_direction() failed");
goto failed;
}
if (features != Py_None) {
int len;
PyObject *seq = PySequence_Fast(features, "expected a sequence");
if (!seq) {
goto failed;
}
len = PySequence_Size(seq);
for (i = 0; i < len; i++) {
PyObject *item = PySequence_Fast_GET_ITEM(seq, i);
char *feature = NULL;
Py_ssize_t size = 0;
PyObject *bytes;
#if PY_VERSION_HEX >= 0x03000000
if (!PyUnicode_Check(item)) {
#else
if (!PyUnicode_Check(item) && !PyString_Check(item)) {
#endif
PyErr_SetString(PyExc_TypeError, "expected a string");
goto failed;
}
if (PyUnicode_Check(item)) {
bytes = PyUnicode_AsUTF8String(item);
if (bytes == NULL)
goto failed;
feature = PyBytes_AS_STRING(bytes);
size = PyBytes_GET_SIZE(bytes);
}
#if PY_VERSION_HEX < 0x03000000
else {
feature = PyString_AsString(item);
size = PyString_GET_SIZE(item);
}
#endif
if (!raqm_add_font_feature(rq, feature, size)) {
PyErr_SetString(PyExc_ValueError, "raqm_add_font_feature() failed");
goto failed;
}
}
}
if (!raqm_set_freetype_face(rq, self->face)) {
PyErr_SetString(PyExc_RuntimeError, "raqm_set_freetype_face() failed.");
goto failed;
}
if (!raqm_layout (rq)) {
PyErr_SetString(PyExc_RuntimeError, "raqm_layout() failed.");
goto failed;
}
glyphs = raqm_get_glyphs(rq, &count);
if (glyphs == NULL) {
PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed.");
count = 0;
goto failed;
}
(*glyph_info) = PyMem_New(GlyphInfo, count);
if ((*glyph_info) == NULL) {
PyErr_SetString(PyExc_MemoryError, "PyMem_New() failed");
count = 0;
goto failed;
}
for (i = 0; i < count; i++) {
(*glyph_info)[i].index = glyphs[i].index;
(*glyph_info)[i].x_offset = glyphs[i].x_offset;
(*glyph_info)[i].x_advance = glyphs[i].x_advance;
(*glyph_info)[i].y_offset = glyphs[i].y_offset;
(*glyph_info)[i].cluster = glyphs[i].cluster;
}
failed:
raqm_destroy (rq);
return count;
}
#endif
static size_t
text_layout_fallback(PyObject* string, FontObject* self, const char* dir,
PyObject *features ,GlyphInfo **glyph_info, int mask)
{
int error, load_flags;
FT_ULong ch;
FT_Face face;
int xoffset, yoffset;
Py_ssize_t count;
FT_GlyphSlot glyph;
FT_Bool kerning = FT_HAS_KERNING(self->face);
FT_UInt last_index = 0;
int i;
/* calculate size and bearing for a given string */
PyObject* string;
if (!PyArg_ParseTuple(args, "O:getsize", &string))
return NULL;
if (features != Py_None || dir != NULL) {
PyErr_SetString(PyExc_KeyError, "setting text direction or font features is not supported without libraqm");
}
#if PY_VERSION_HEX >= 0x03000000
if (!PyUnicode_Check(string)) {
#else
if (!PyUnicode_Check(string) && !PyString_Check(string)) {
#endif
PyErr_SetString(PyExc_TypeError, "expected string");
return NULL;
return 0;
}
count = 0;
while (font_getchar(string, count, &ch)) {
count++;
}
if (count == 0) {
return 0;
}
(*glyph_info) = PyMem_New(GlyphInfo, count);
if ((*glyph_info) == NULL) {
PyErr_SetString(PyExc_MemoryError, "PyMem_New() failed");
return 0;
}
load_flags = FT_LOAD_RENDER|FT_LOAD_NO_BITMAP;
if (mask) {
load_flags |= FT_LOAD_TARGET_MONO;
}
for (i = 0; font_getchar(string, i, &ch); i++) {
(*glyph_info)[i].index = FT_Get_Char_Index(self->face, ch);
error = FT_Load_Glyph(self->face, (*glyph_info)[i].index, load_flags);
if (error) {
geterror(error);
return 0;
}
glyph = self->face->glyph;
(*glyph_info)[i].x_offset=0;
(*glyph_info)[i].y_offset=0;
if (kerning && last_index && (*glyph_info)[i].index) {
FT_Vector delta;
if (FT_Get_Kerning(self->face, last_index, (*glyph_info)[i].index,
ft_kerning_default,&delta) == 0)
(*glyph_info)[i-1].x_advance += PIXEL(delta.x);
}
(*glyph_info)[i].x_advance = glyph->metrics.horiAdvance;
last_index = (*glyph_info)[i].index;
(*glyph_info)[i].cluster = ch;
}
return count;
}
static size_t
text_layout(PyObject* string, FontObject* self, const char* dir,
PyObject *features, GlyphInfo **glyph_info, int mask)
{
size_t count;
#ifdef HAVE_RAQM
count = text_layout_raqm(string, self, dir, features, glyph_info, mask);
#else
count = text_layout_fallback(string, self, dir, features, glyph_info, mask);
#endif
return count;
}
static PyObject*
font_getsize(FontObject* self, PyObject* args)
{
int i, x, y_max, y_min;
FT_Face face;
int xoffset, yoffset;
const char *dir = NULL;
size_t count;
GlyphInfo *glyph_info = NULL;
PyObject *features = Py_None;
/* calculate size and bearing for a given string */
PyObject* string;
if (!PyArg_ParseTuple(args, "O|zO:getsize", &string, &dir, &features))
return NULL;
face = NULL;
xoffset = yoffset = 0;
y_max = y_min = 0;
for (x = i = 0; font_getchar(string, i, &ch); i++) {
count = text_layout(string, self, dir, features, &glyph_info, 0);
if (count == 0)
return NULL;
for (x = i = 0; i < count; i++) {
int index, error;
FT_BBox bbox;
FT_Glyph glyph;
face = self->face;
index = FT_Get_Char_Index(face, ch);
if (kerning && last_index && index) {
FT_Vector delta;
FT_Get_Kerning(self->face, last_index, index, ft_kerning_default,
&delta);
x += delta.x;
}
/* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960
* Yifu Yu<root@jackyyf.com>, 2014-10-15
*/
index = glyph_info[i].index;
/* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960
* Yifu Yu<root@jackyyf.com>, 2014-10-15
*/
error = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT|FT_LOAD_NO_BITMAP);
if (error)
return geterror(error);
if (i == 0)
if (i == 0 && face->glyph->metrics.horiBearingX < 0) {
xoffset = face->glyph->metrics.horiBearingX;
x += face->glyph->metrics.horiAdvance;
x -= xoffset;
}
x += glyph_info[i].x_advance;
if (i == count - 1)
{
int offset;
offset = glyph_info[i].x_advance -
face->glyph->metrics.width -
face->glyph->metrics.horiBearingX;
if (offset < 0)
x -= offset;
}
FT_Get_Glyph(face->glyph, &glyph);
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_SUBPIXELS, &bbox);
bbox.yMax -= glyph_info[i].y_offset;
bbox.yMin -= glyph_info[i].y_offset;
if (bbox.yMax > y_max)
y_max = bbox.yMax;
if (bbox.yMin < y_min)
@ -251,23 +484,16 @@ font_getsize(FontObject* self, PyObject* args)
if (face->glyph->metrics.horiBearingY > yoffset)
yoffset = face->glyph->metrics.horiBearingY;
last_index = index;
FT_Done_Glyph(glyph);
}
if (face) {
int offset;
/* left bearing */
if (xoffset < 0)
x -= xoffset;
else
xoffset = 0;
/* right bearing */
offset = face->glyph->metrics.horiAdvance -
face->glyph->metrics.width -
face->glyph->metrics.horiBearingX;
if (offset < 0)
x -= offset;
/* difference between the font ascender and the distance of
* the baseline from the top */
yoffset = PIXEL(self->face->size->metrics.ascender - yoffset);
@ -306,7 +532,7 @@ font_getabc(FontObject* self, PyObject* args)
int index, error;
face = self->face;
index = FT_Get_Char_Index(face, ch);
/* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 */
/* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 */
error = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT|FT_LOAD_NO_BITMAP);
if (error)
return geterror(error);
@ -329,11 +555,7 @@ font_render(FontObject* self, PyObject* args)
int index, error, ascender;
int load_flags;
unsigned char *source;
FT_ULong ch;
FT_GlyphSlot glyph;
FT_Bool kerning = FT_HAS_KERNING(self->face);
FT_UInt last_index = 0;
/* render string into given buffer (the buffer *must* have
the right size, or this will crash) */
PyObject* string;
@ -341,15 +563,18 @@ font_render(FontObject* self, PyObject* args)
int mask = 0;
int temp;
int xx, x0, x1;
if (!PyArg_ParseTuple(args, "On|i:render", &string, &id, &mask))
return NULL;
const char *dir = NULL;
size_t count;
GlyphInfo *glyph_info;
PyObject *features = NULL;
#if PY_VERSION_HEX >= 0x03000000
if (!PyUnicode_Check(string)) {
#else
if (!PyUnicode_Check(string) && !PyString_Check(string)) {
#endif
PyErr_SetString(PyExc_TypeError, "expected string");
if (!PyArg_ParseTuple(args, "On|izO:render", &string, &id, &mask, &dir, &features)) {
return NULL;
}
glyph_info = NULL;
count = text_layout(string, self, dir, features, &glyph_info, mask);
if (count == 0) {
return NULL;
}
@ -360,36 +585,37 @@ font_render(FontObject* self, PyObject* args)
load_flags |= FT_LOAD_TARGET_MONO;
ascender = 0;
for (i = 0; font_getchar(string, i, &ch); i++) {
index = FT_Get_Char_Index(self->face, ch);
for (i = 0; i < count; i++) {
index = glyph_info[i].index;
error = FT_Load_Glyph(self->face, index, load_flags);
if (error)
return geterror(error);
glyph = self->face->glyph;
temp = (glyph->bitmap.rows - glyph->bitmap_top);
temp -= PIXEL(glyph_info[i].y_offset);
if (temp > ascender)
ascender = temp;
}
for (x = i = 0; font_getchar(string, i, &ch); i++) {
for (x = i = 0; i < count; i++) {
if (i == 0 && self->face->glyph->metrics.horiBearingX < 0)
x = -PIXEL(self->face->glyph->metrics.horiBearingX);
index = FT_Get_Char_Index(self->face, ch);
if (kerning && last_index && index) {
FT_Vector delta;
FT_Get_Kerning(self->face, last_index, index, ft_kerning_default,
&delta);
x += delta.x >> 6;
}
x = -self->face->glyph->metrics.horiBearingX;
index = glyph_info[i].index;
error = FT_Load_Glyph(self->face, index, load_flags);
if (error)
return geterror(error);
if (i == 0 && self->face->glyph->metrics.horiBearingX < 0) {
x = -self->face->glyph->metrics.horiBearingX;
}
glyph = self->face->glyph;
source = (unsigned char*) glyph->bitmap.buffer;
xx = x + glyph->bitmap_left;
xx = PIXEL(x) + glyph->bitmap_left;
xx += PIXEL(glyph_info[i].x_offset);
x0 = 0;
x1 = glyph->bitmap.width;
if (xx < 0)
@ -401,6 +627,7 @@ font_render(FontObject* self, PyObject* args)
/* use monochrome mask (on palette images, etc) */
for (y = 0; y < glyph->bitmap.rows; y++) {
int yy = y + im->ysize - (PIXEL(glyph->metrics.horiBearingY) + ascender);
yy -= PIXEL(glyph_info[i].y_offset);
if (yy >= 0 && yy < im->ysize) {
/* blend this glyph into the buffer */
unsigned char *target = im->image8[yy] + xx;
@ -420,8 +647,10 @@ font_render(FontObject* self, PyObject* args)
/* use antialiased rendering */
for (y = 0; y < glyph->bitmap.rows; y++) {
int yy = y + im->ysize - (PIXEL(glyph->metrics.horiBearingY) + ascender);
yy -= PIXEL(glyph_info[i].y_offset);
if (yy >= 0 && yy < im->ysize) {
/* blend this glyph into the buffer */
int i;
unsigned char *target = im->image8[yy] + xx;
for (i = x0; i < x1; i++) {
@ -432,10 +661,10 @@ font_render(FontObject* self, PyObject* args)
source += glyph->bitmap.pitch;
}
}
x += PIXEL(glyph->metrics.horiAdvance);
last_index = index;
x += glyph_info[i].x_advance;
}
PyMem_Del(glyph_info);
Py_RETURN_NONE;
}
@ -593,6 +822,14 @@ setup_module(PyObject* m) {
#endif
PyDict_SetItemString(d, "freetype2_version", v);
#ifdef HAVE_RAQM
v = PyBool_FromLong(1);
#else
v = PyBool_FromLong(0);
#endif
PyDict_SetItemString(d, "have_raqm", v);
return 0;
}

View File

@ -11,7 +11,8 @@ sudo apt-get -y install python-dev python-setuptools \
python3-dev python-virtualenv cmake
sudo apt-get -y install libtiff5-dev libjpeg62-turbo-dev zlib1g-dev \
libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev \
python-tk python3-tk
python-tk python3-tk libharfbuzz-dev libfribidi-dev
./install_openjpeg.sh
./install_imagequant.sh
./install_raqm.sh

View File

@ -15,4 +15,4 @@ sudo dnf install python-devel python3-devel python-virtualenv make gcc
sudo dnf install libtiff-devel libjpeg-devel zlib-devel freetype-devel \
lcms2-devel libwebp-devel openjpeg2-devel tkinter python3-tkinter \
tcl-devel tk-devel
tcl-devel tk-devel harfbuzz-devel fribidi-devel libraqm-devel

View File

@ -8,4 +8,6 @@ sudo pkg install python2 python3 py27-pip py27-virtualenv py27-setuptools27
# Openjpeg fails badly using the openjpeg package.
# I can't find a python3.4 version of tkinter
sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 py27-tkinter
sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 harfbuzz fribidi py27-tkinter
./install_raqm.sh

19
depends/install_raqm.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
# install raqm
if [ ! -f raqm-0.2.0.tar.gz ]; then
wget -O 'raqm-0.2.0.tar.gz' 'https://github.com/HOST-Oman/libraqm/releases/download/v0.2.0/raqm-0.2.0.tar.gz?raw=true'
fi
rm -r raqm-0.2.0
tar -xvzf raqm-0.2.0.tar.gz
pushd raqm-0.2.0
./configure --prefix=/usr && make -j4 && sudo make -j4 install
popd

View File

@ -4,12 +4,13 @@
# Installs all of the dependencies for Pillow for Ubuntu 14.04
# for both system Pythons 2.7 and 3.4
#
sudo add-apt-repository -y ppa:as-bahanta/raqm
sudo apt-get update
sudo apt-get -y install python-dev python-setuptools \
python3-dev python-virtualenv cmake
sudo apt-get -y install libtiff5-dev libjpeg8-dev zlib1g-dev \
libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev \
python-tk python3-tk
python-tk python3-tk libharfbuzz-dev libfribidi-dev libraqm-dev
./install_openjpeg.sh
./install_imagequant.sh

View File

@ -170,6 +170,11 @@ Many of Pillow's features require external libraries:
* Windows support: Libimagequant requires VS2013/MSVC 18 to compile,
so it is unlikely to work with any Python prior to 3.5 on Windows.
* **libraqm** provides complex text layout support.
* libraqm provides bidirectional text support (using FriBiDi), shaping (using HarfBuzz), and proper script itemization. As a result, Raqm can support most writing systems covered by Unicode.
* libraqm depends on the following libraries: FreeType, HarfBuzz, FriBiDi, make sure that install them before install libraqm if not available as package in your system.
* setting text direction or font features is not supported without libraqm.
Once you have installed the prerequisites, run::
$ pip install Pillow
@ -201,14 +206,16 @@ Build Options
* Build flags: ``--disable-zlib``, ``--disable-jpeg``,
``--disable-tiff``, ``--disable-freetype``, ``--disable-tcl``,
``--disable-tk``, ``--disable-lcms``, ``--disable-webp``,
``--disable-webpmux``, ``--disable-jpeg2000``, ``--disable-imagequant``.
``--disable-webpmux``, ``--disable-jpeg2000``,
``--disable-imagequant``, ``--disable-raqm``.
Disable building the corresponding feature even if the development
libraries are present on the building machine.
* Build flags: ``--enable-zlib``, ``--enable-jpeg``,
``--enable-tiff``, ``--enable-freetype``, ``--enable-tcl``,
``--enable-tk``, ``--enable-lcms``, ``--enable-webp``,
``--enable-webpmux``, ``--enable-jpeg2000``, ``--enable-imagequant``.
``--enable-webpmux``, ``--enable-jpeg2000``,
``--enable-imagequant``, ``--enable-raqm``.
Require that the corresponding feature is built. The build will raise
an exception if the libraries are not found. Webpmux (WebP metadata)
relies on WebP support. Tcl and Tk also must be used together.
@ -222,7 +229,6 @@ Build Options
library search process to dump all paths searched for and found to
stdout.
Sample Usage::
$ MAX_CONCURRENCY=1 python setup.py build_ext --enable-[feature] install
@ -247,7 +253,16 @@ The easiest way to install external libraries is via `Homebrew
$ brew install libtiff libjpeg webp little-cms2
Install Pillow with::
To install libraqm on MaxOS use Homebrew::
$ brew install freetype harfbuzz fribidi
Once you have `libraqm source code <https://github.com/HOST-Oman/libraqm>`_ and the dependencies , run the customary sequence of commands in the source code
directory::
$ ./configure
$ make
$ make install
Now install Pillow with::
$ pip install Pillow
@ -277,7 +292,7 @@ Or for Python 3::
Prerequisites are installed on **FreeBSD 10** with::
$ sudo pkg install jpeg tiff webp lcms2 freetype2
$ sudo pkg install jpeg tiff webp lcms2 freetype2 harfbuzz fribidi
Building on Linux
@ -318,7 +333,7 @@ Prerequisites are installed on **Ubuntu 14.04 LTS** with::
Prerequisites are installed on **Fedora 23** with::
$ sudo dnf install libtiff-devel libjpeg-devel zlib-devel freetype-devel \
lcms2-devel libwebp-devel tcl-devel tk-devel
lcms2-devel libwebp-devel tcl-devel tk-devel libraqm-devel

View File

@ -227,7 +227,7 @@ Methods
Draw a shape.
.. py:method:: PIL.ImageDraw.Draw.text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left")
.. py:method:: PIL.ImageDraw.Draw.text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None)
Draws the string at the given position.
@ -240,9 +240,16 @@ Methods
the number of pixels between lines.
:param align: If the text is passed on to multiline_text(),
"left", "center" or "right".
:param direction: Direction of the text. It can be 'rtl', 'ltr', 'ttb' or 'btt'. Requires libraqm
:param features: A list of font feature 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://www.microsoft.com/typography/otspec/featurelist.htm
Requires libraqm.
.. py:method:: PIL.ImageDraw.Draw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left")
.. py:method:: PIL.ImageDraw.Draw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left",
direction=None, features=[])
Draws the string at the given position.
@ -252,6 +259,13 @@ Methods
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
:param spacing: The number of pixels between lines.
:param align: "left", "center" or "right".
:param direction: Direction of the text. It can be 'rtl', 'ltr', 'ttb' or 'btt'. Requires libraqm.
:param features: A list of font feature 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://www.microsoft.com/typography/otspec/featurelist.htm
Requires libraqm.
.. py:method:: PIL.ImageDraw.Draw.textsize(text, font=None, spacing=0)

View File

@ -51,7 +51,7 @@ Methods
:return: (width, height)
.. py:method:: PIL.ImageFont.ImageFont.getmask(text, mode='')
.. py:method:: PIL.ImageFont.ImageFont.getmask(text, mode='', direction=None, features=[])
Create a bitmap for the text.
@ -63,6 +63,13 @@ Methods
driver prefers; if empty, the renderer may return either
mode. Note that the mode is always a string, to simplify
C-level implementations.
:param direction: Direction of the text. It can be 'rtl', 'ltr', 'ttb' or 'btt'. Requires libraqm
:param features: A list of font feature 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://www.microsoft.com/typography/otspec/featurelist.htm
Requires libraqm
.. versionadded:: 1.1.5
:return: An internal PIL storage memory instance as defined by the

View File

@ -178,7 +178,8 @@ if __name__ == "__main__":
("freetype2", "FREETYPE2"),
("littlecms2", "LITTLECMS2"),
("webp", "WEBP"),
("transp_webp", "Transparent WEBP")
("transp_webp", "Transparent WEBP"),
("raqm", "RAQM")
]:
supported = features.check_module(name)

View File

@ -114,7 +114,7 @@ IMAGEQUANT_ROOT = None
TIFF_ROOT = None
FREETYPE_ROOT = None
LCMS_ROOT = None
RAQM_ROOT = None
def _pkg_config(name):
try:
@ -132,7 +132,7 @@ def _pkg_config(name):
class pil_build_ext(build_ext):
class feature:
features = ['zlib', 'jpeg', 'tiff', 'freetype', 'lcms', 'webp',
features = ['zlib', 'jpeg', 'tiff', 'freetype', 'raqm', 'lcms', 'webp',
'webpmux', 'jpeg2000', 'imagequant']
required = {'jpeg', 'zlib'}
@ -513,6 +513,14 @@ class pil_build_ext(build_ext):
if subdir:
_add_directory(self.compiler.include_dirs, subdir, 0)
if feature.want('raqm'):
_dbg('Looking for raqm')
if _find_include_file(self, "raqm.h"):
if _find_library_file(self, "raqm") and \
_find_library_file(self, "harfbuzz") and \
_find_library_file(self, "fribidi"):
feature.raqm = ["raqm", "harfbuzz", "fribidi"]
if feature.want('lcms'):
_dbg('Looking for lcms')
if _find_include_file(self, "lcms2.h"):
@ -591,9 +599,14 @@ class pil_build_ext(build_ext):
# additional libraries
if feature.freetype:
exts.append(Extension("PIL._imagingft",
["_imagingft.c"],
libraries=["freetype"]))
libs = ["freetype"]
defs = []
if feature.raqm:
libs.extend(feature.raqm)
defs.append(('HAVE_RAQM', None))
exts.append(Extension(
"PIL._imagingft", ["_imagingft.c"], libraries=libs,
define_macros=defs))
if feature.lcms:
extra = []
@ -660,6 +673,7 @@ class pil_build_ext(build_ext):
(feature.imagequant, "LIBIMAGEQUANT"),
(feature.tiff, "LIBTIFF"),
(feature.freetype, "FREETYPE2"),
(feature.raqm, "RAQM"),
(feature.lcms, "LITTLECMS2"),
(feature.webp, "WEBP"),
(feature.webpmux, "WEBPMUX"),