Merge 81dfbc7f97
into d3727b523e
|
@ -18,9 +18,13 @@ python:
|
||||||
- 3.4
|
- 3.4
|
||||||
- nightly
|
- nightly
|
||||||
|
|
||||||
|
sudo: required # needed for trusty beta
|
||||||
|
dist: trusty # needed for HarfBuzz
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
- "travis_retry sudo add-apt-repository -y ppa:as-bahanta/raqm"
|
||||||
- "travis_retry sudo apt-get update"
|
- "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 cffi"
|
||||||
- "travis_retry pip install nose"
|
- "travis_retry pip install nose"
|
||||||
- "travis_retry pip install check-manifest"
|
- "travis_retry pip install check-manifest"
|
||||||
|
|
|
@ -252,17 +252,17 @@ class ImageDraw(object):
|
||||||
ink = fill
|
ink = fill
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
try:
|
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]
|
xy = xy[0] + offset[0], xy[1] + offset[1]
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
try:
|
try:
|
||||||
mask = font.getmask(text, self.fontmode)
|
mask = font.getmask(text, self.fontmode, *args, **kwargs)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
mask = font.getmask(text)
|
mask = font.getmask(text)
|
||||||
self.draw.draw_bitmap(xy, mask, ink)
|
self.draw.draw_bitmap(xy, mask, ink)
|
||||||
|
|
||||||
def multiline_text(self, xy, text, fill=None, font=None, anchor=None,
|
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 = []
|
widths = []
|
||||||
max_width = 0
|
max_width = 0
|
||||||
lines = self._multiline_split(text)
|
lines = self._multiline_split(text)
|
||||||
|
@ -281,7 +281,7 @@ class ImageDraw(object):
|
||||||
left += (max_width - widths[idx])
|
left += (max_width - widths[idx])
|
||||||
else:
|
else:
|
||||||
assert False, 'align must be "left", "center" or "right"'
|
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
|
top += line_spacing
|
||||||
left = xy[0]
|
left = xy[0]
|
||||||
|
|
||||||
|
|
|
@ -137,20 +137,20 @@ class FreeTypeFont(object):
|
||||||
def getmetrics(self):
|
def getmetrics(self):
|
||||||
return self.font.ascent, self.font.descent
|
return self.font.ascent, self.font.descent
|
||||||
|
|
||||||
def getsize(self, text):
|
def getsize(self, text, direction=None, features=None):
|
||||||
size, offset = self.font.getsize(text)
|
size, offset = self.font.getsize(text, direction, features)
|
||||||
return (size[0] + offset[0], size[1] + offset[1])
|
return (size[0] + offset[0], size[1] + offset[1])
|
||||||
|
|
||||||
def getoffset(self, text):
|
def getoffset(self, text):
|
||||||
return self.font.getsize(text)[1]
|
return self.font.getsize(text)[1]
|
||||||
|
|
||||||
def getmask(self, text, mode=""):
|
def getmask(self, text, mode="", direction=None, features=None):
|
||||||
return self.getmask2(text, mode)[0]
|
return self.getmask2(text, mode, direction=direction, features=features)[0]
|
||||||
|
|
||||||
def getmask2(self, text, mode="", fill=Image.core.fill):
|
def getmask2(self, text, mode="", fill=Image.core.fill, direction=None, features=None):
|
||||||
size, offset = self.font.getsize(text)
|
size, offset = self.font.getsize(text, direction, features)
|
||||||
im = fill("L", size, 0)
|
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
|
return im, offset
|
||||||
|
|
||||||
def font_variant(self, font=None, size=None, index=None, encoding=None):
|
def font_variant(self, font=None, size=None, index=None, encoding=None):
|
||||||
|
|
|
@ -5,6 +5,7 @@ modules = {
|
||||||
"tkinter": "PIL._imagingtk",
|
"tkinter": "PIL._imagingtk",
|
||||||
"freetype2": "PIL._imagingft",
|
"freetype2": "PIL._imagingft",
|
||||||
"littlecms2": "PIL._imagingcms",
|
"littlecms2": "PIL._imagingcms",
|
||||||
|
"raqm": "PIL._imagingft",
|
||||||
"webp": "PIL._webp",
|
"webp": "PIL._webp",
|
||||||
"transp_webp": ("WEBP", "WebPDecoderBuggyAlpha")
|
"transp_webp": ("WEBP", "WebPDecoderBuggyAlpha")
|
||||||
}
|
}
|
||||||
|
|
BIN
Tests/fonts/NotoNastaliqUrdu-Regular.ttf
Normal file
BIN
Tests/images/test_Nastalifont_text.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
Tests/images/test_arabictext_features.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/test_complex_unicode_text.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/test_direction_ltr.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
Tests/images/test_direction_rtl.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
Tests/images/test_kerning_features.png
Normal file
After Width: | Height: | Size: 963 B |
BIN
Tests/images/test_ligature_features.png
Normal file
After Width: | Height: | Size: 605 B |
BIN
Tests/images/test_text.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Tests/images/test_y_offset.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
136
Tests/test_imagefontctl.py
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from helper import unittest, PillowTestCase
|
||||||
|
from PIL import Image
|
||||||
|
from PIL import ImageDraw
|
||||||
|
|
||||||
|
FONT_SIZE = 20
|
||||||
|
FONT_PATH = "Tests/fonts/DejaVuSans.ttf"
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PIL import ImageFont
|
||||||
|
|
||||||
|
# check if raqm is available
|
||||||
|
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')
|
||||||
|
|
||||||
|
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 (KeyError, ImportError):
|
||||||
|
class TestImagecomplextext(PillowTestCase):
|
||||||
|
def test_skip(self):
|
||||||
|
self.skipTest("KeyError")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
|
||||||
|
# End of file
|
347
_imagingft.c
|
@ -41,6 +41,23 @@
|
||||||
#define FT_ERRORDEF( e, v, s ) { e, s },
|
#define FT_ERRORDEF( e, v, s ) { e, s },
|
||||||
#define FT_ERROR_START_LIST {
|
#define FT_ERROR_START_LIST {
|
||||||
#define FT_ERROR_END_LIST { 0, 0 } };
|
#define FT_ERROR_END_LIST { 0, 0 } };
|
||||||
|
#ifdef HAVE_RAQM
|
||||||
|
#include <raqm.h>
|
||||||
|
#else
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
RAQM_DIRECTION_DEFAULT,
|
||||||
|
RAQM_DIRECTION_RTL,
|
||||||
|
RAQM_DIRECTION_LTR,
|
||||||
|
RAQM_DIRECTION_TTB
|
||||||
|
} raqm_direction_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int index, x_offset, x_advance, y_offset;
|
||||||
|
unsigned int cluster;
|
||||||
|
} GlyphInfo;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
int code;
|
int code;
|
||||||
|
@ -186,86 +203,88 @@ font_getchar(PyObject* string, int index, FT_ULong* char_out)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
text_layout(PyObject* string, FontObject* self, const char* dir,
|
||||||
|
PyObject *features ,GlyphInfo **glyph_info, int mask);
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
font_getsize(FontObject* self, PyObject* args)
|
font_getsize(FontObject* self, PyObject* args)
|
||||||
{
|
{
|
||||||
int i, x, y_max, y_min;
|
int i, x, y_max, y_min;
|
||||||
FT_ULong ch;
|
|
||||||
FT_Face face;
|
FT_Face face;
|
||||||
int xoffset, yoffset;
|
int xoffset, yoffset;
|
||||||
FT_Bool kerning = FT_HAS_KERNING(self->face);
|
const char *dir = NULL;
|
||||||
FT_UInt last_index = 0;
|
size_t count;
|
||||||
|
GlyphInfo *glyph_info = NULL;;
|
||||||
|
PyObject *features = Py_None;
|
||||||
|
|
||||||
/* calculate size and bearing for a given string */
|
/* calculate size and bearing for a given string */
|
||||||
|
|
||||||
PyObject* string;
|
PyObject* string;
|
||||||
if (!PyArg_ParseTuple(args, "O:getsize", &string))
|
if (!PyArg_ParseTuple(args, "O|zO:getsize", &string, &dir, &features))
|
||||||
return NULL;
|
return 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");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
face = NULL;
|
face = NULL;
|
||||||
xoffset = yoffset = 0;
|
xoffset = yoffset = 0;
|
||||||
y_max = y_min = 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;
|
int index, error;
|
||||||
FT_BBox bbox;
|
FT_BBox bbox;
|
||||||
FT_Glyph glyph;
|
FT_Glyph glyph;
|
||||||
face = self->face;
|
face = self->face;
|
||||||
index = FT_Get_Char_Index(face, ch);
|
index = glyph_info[i].index;
|
||||||
if (kerning && last_index && index) {
|
/* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960
|
||||||
FT_Vector delta;
|
* Yifu Yu<root@jackyyf.com>, 2014-10-15
|
||||||
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
|
|
||||||
*/
|
|
||||||
error = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT|FT_LOAD_NO_BITMAP);
|
error = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT|FT_LOAD_NO_BITMAP);
|
||||||
if (error)
|
if (error)
|
||||||
return geterror(error);
|
return geterror(error);
|
||||||
if (i == 0)
|
|
||||||
|
if (i == 0 && face->glyph->metrics.horiBearingX < 0) {
|
||||||
xoffset = face->glyph->metrics.horiBearingX;
|
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_Get_Glyph(face->glyph, &glyph);
|
||||||
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_SUBPIXELS, &bbox);
|
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)
|
if (bbox.yMax > y_max)
|
||||||
y_max = bbox.yMax;
|
y_max = bbox.yMax;
|
||||||
if (bbox.yMin < y_min)
|
if (bbox.yMin < y_min)
|
||||||
y_min = bbox.yMin;
|
y_min = bbox.yMin;
|
||||||
|
|
||||||
/* find max distance of baseline from top */
|
/* find max distance of baseline from top */
|
||||||
if (face->glyph->metrics.horiBearingY > yoffset)
|
if (face->glyph->metrics.horiBearingY > yoffset)
|
||||||
yoffset = face->glyph->metrics.horiBearingY;
|
yoffset = face->glyph->metrics.horiBearingY;
|
||||||
|
|
||||||
last_index = index;
|
|
||||||
FT_Done_Glyph(glyph);
|
FT_Done_Glyph(glyph);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (face) {
|
if (face) {
|
||||||
int offset;
|
|
||||||
/* left bearing */
|
/* left bearing */
|
||||||
if (xoffset < 0)
|
if (xoffset < 0)
|
||||||
x -= xoffset;
|
x -= xoffset;
|
||||||
else
|
else
|
||||||
xoffset = 0;
|
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
|
/* difference between the font ascender and the distance of
|
||||||
* the baseline from the top */
|
* the baseline from the top */
|
||||||
yoffset = PIXEL(self->face->size->metrics.ascender - yoffset);
|
yoffset = PIXEL(self->face->size->metrics.ascender - yoffset);
|
||||||
|
@ -304,7 +323,7 @@ font_getabc(FontObject* self, PyObject* args)
|
||||||
int index, error;
|
int index, error;
|
||||||
face = self->face;
|
face = self->face;
|
||||||
index = FT_Get_Char_Index(face, ch);
|
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);
|
error = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT|FT_LOAD_NO_BITMAP);
|
||||||
if (error)
|
if (error)
|
||||||
return geterror(error);
|
return geterror(error);
|
||||||
|
@ -319,6 +338,205 @@ font_getabc(FontObject* self, PyObject* args)
|
||||||
return Py_BuildValue("ddd", a, b, c);
|
return Py_BuildValue("ddd", a, b, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
text_layout(PyObject* string, FontObject* self, const char* dir,
|
||||||
|
PyObject *features ,GlyphInfo **glyph_info, int mask)
|
||||||
|
{
|
||||||
|
#ifdef HAVE_RAQM
|
||||||
|
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;
|
||||||
|
|
||||||
|
#else
|
||||||
|
if (features != Py_None || dir != NULL)
|
||||||
|
PyErr_SetString(PyExc_KeyError, "Raqm is missing.");
|
||||||
|
|
||||||
|
int error, load_flags;
|
||||||
|
FT_ULong ch;
|
||||||
|
Py_ssize_t count;
|
||||||
|
FT_GlyphSlot glyph;
|
||||||
|
FT_Bool kerning = FT_HAS_KERNING(self->face);
|
||||||
|
FT_UInt last_index = 0;
|
||||||
|
#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 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;
|
||||||
|
int i;
|
||||||
|
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;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
font_render(FontObject* self, PyObject* args)
|
font_render(FontObject* self, PyObject* args)
|
||||||
{
|
{
|
||||||
|
@ -327,11 +545,7 @@ font_render(FontObject* self, PyObject* args)
|
||||||
int index, error, ascender;
|
int index, error, ascender;
|
||||||
int load_flags;
|
int load_flags;
|
||||||
unsigned char *source;
|
unsigned char *source;
|
||||||
FT_ULong ch;
|
|
||||||
FT_GlyphSlot glyph;
|
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
|
/* render string into given buffer (the buffer *must* have
|
||||||
the right size, or this will crash) */
|
the right size, or this will crash) */
|
||||||
PyObject* string;
|
PyObject* string;
|
||||||
|
@ -339,17 +553,18 @@ font_render(FontObject* self, PyObject* args)
|
||||||
int mask = 0;
|
int mask = 0;
|
||||||
int temp;
|
int temp;
|
||||||
int xx, x0, x1;
|
int xx, x0, x1;
|
||||||
if (!PyArg_ParseTuple(args, "On|i:render", &string, &id, &mask))
|
const char *dir = NULL;
|
||||||
|
size_t count;
|
||||||
|
GlyphInfo *glyph_info;
|
||||||
|
PyObject *features = NULL;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "On|izO:render", &string, &id, &mask, &dir, &features))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
#if PY_VERSION_HEX >= 0x03000000
|
glyph_info = NULL;
|
||||||
if (!PyUnicode_Check(string)) {
|
count = text_layout(string, self, dir, features, &glyph_info, mask);
|
||||||
#else
|
if (count == 0)
|
||||||
if (!PyUnicode_Check(string) && !PyString_Check(string)) {
|
|
||||||
#endif
|
|
||||||
PyErr_SetString(PyExc_TypeError, "expected string");
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
|
||||||
|
|
||||||
im = (Imaging) id;
|
im = (Imaging) id;
|
||||||
/* 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 */
|
||||||
|
@ -358,36 +573,37 @@ font_render(FontObject* self, PyObject* args)
|
||||||
load_flags |= FT_LOAD_TARGET_MONO;
|
load_flags |= FT_LOAD_TARGET_MONO;
|
||||||
|
|
||||||
ascender = 0;
|
ascender = 0;
|
||||||
for (i = 0; font_getchar(string, i, &ch); i++) {
|
for (i = 0; i < count; i++) {
|
||||||
index = FT_Get_Char_Index(self->face, ch);
|
index = glyph_info[i].index;
|
||||||
error = FT_Load_Glyph(self->face, index, load_flags);
|
error = FT_Load_Glyph(self->face, index, load_flags);
|
||||||
if (error)
|
if (error)
|
||||||
return geterror(error);
|
return geterror(error);
|
||||||
|
|
||||||
glyph = self->face->glyph;
|
glyph = self->face->glyph;
|
||||||
temp = (glyph->bitmap.rows - glyph->bitmap_top);
|
temp = (glyph->bitmap.rows - glyph->bitmap_top);
|
||||||
|
temp -= PIXEL(glyph_info[i].y_offset);
|
||||||
if (temp > ascender)
|
if (temp > ascender)
|
||||||
ascender = temp;
|
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)
|
if (i == 0 && self->face->glyph->metrics.horiBearingX < 0)
|
||||||
x = -PIXEL(self->face->glyph->metrics.horiBearingX);
|
x = -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
index = glyph_info[i].index;
|
||||||
error = FT_Load_Glyph(self->face, index, load_flags);
|
error = FT_Load_Glyph(self->face, index, load_flags);
|
||||||
if (error)
|
if (error)
|
||||||
return geterror(error);
|
return geterror(error);
|
||||||
|
|
||||||
|
if (i == 0 && self->face->glyph->metrics.horiBearingX < 0) {
|
||||||
|
x = -self->face->glyph->metrics.horiBearingX;
|
||||||
|
}
|
||||||
|
|
||||||
glyph = self->face->glyph;
|
glyph = self->face->glyph;
|
||||||
|
|
||||||
source = (unsigned char*) glyph->bitmap.buffer;
|
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;
|
x0 = 0;
|
||||||
x1 = glyph->bitmap.width;
|
x1 = glyph->bitmap.width;
|
||||||
if (xx < 0)
|
if (xx < 0)
|
||||||
|
@ -399,6 +615,7 @@ font_render(FontObject* self, PyObject* args)
|
||||||
/* use monochrome mask (on palette images, etc) */
|
/* use monochrome mask (on palette images, etc) */
|
||||||
for (y = 0; y < glyph->bitmap.rows; y++) {
|
for (y = 0; y < glyph->bitmap.rows; y++) {
|
||||||
int yy = y + im->ysize - (PIXEL(glyph->metrics.horiBearingY) + ascender);
|
int yy = y + im->ysize - (PIXEL(glyph->metrics.horiBearingY) + ascender);
|
||||||
|
yy -= PIXEL(glyph_info[i].y_offset);
|
||||||
if (yy >= 0 && yy < im->ysize) {
|
if (yy >= 0 && yy < im->ysize) {
|
||||||
/* blend this glyph into the buffer */
|
/* blend this glyph into the buffer */
|
||||||
unsigned char *target = im->image8[yy] + xx;
|
unsigned char *target = im->image8[yy] + xx;
|
||||||
|
@ -418,8 +635,10 @@ font_render(FontObject* self, PyObject* args)
|
||||||
/* use antialiased rendering */
|
/* use antialiased rendering */
|
||||||
for (y = 0; y < glyph->bitmap.rows; y++) {
|
for (y = 0; y < glyph->bitmap.rows; y++) {
|
||||||
int yy = y + im->ysize - (PIXEL(glyph->metrics.horiBearingY) + ascender);
|
int yy = y + im->ysize - (PIXEL(glyph->metrics.horiBearingY) + ascender);
|
||||||
|
yy -= PIXEL(glyph_info[i].y_offset);
|
||||||
if (yy >= 0 && yy < im->ysize) {
|
if (yy >= 0 && yy < im->ysize) {
|
||||||
/* blend this glyph into the buffer */
|
/* blend this glyph into the buffer */
|
||||||
|
|
||||||
int i;
|
int i;
|
||||||
unsigned char *target = im->image8[yy] + xx;
|
unsigned char *target = im->image8[yy] + xx;
|
||||||
for (i = x0; i < x1; i++) {
|
for (i = x0; i < x1; i++) {
|
||||||
|
@ -430,10 +649,10 @@ font_render(FontObject* self, PyObject* args)
|
||||||
source += glyph->bitmap.pitch;
|
source += glyph->bitmap.pitch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
x += PIXEL(glyph->metrics.horiAdvance);
|
x += glyph_info[i].x_advance;
|
||||||
last_index = index;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyMem_Del(glyph_info);
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -138,6 +138,8 @@ Many of Pillow's features require external libraries:
|
||||||
* Pillow does **not** support the earlier **1.5** series which ships
|
* Pillow does **not** support the earlier **1.5** series which ships
|
||||||
with Ubuntu and Debian.
|
with Ubuntu and Debian.
|
||||||
|
|
||||||
|
* **libraqm** provides complex text layout support.
|
||||||
|
|
||||||
Once you have installed the prerequisites, run::
|
Once you have installed the prerequisites, run::
|
||||||
|
|
||||||
$ pip install Pillow
|
$ pip install Pillow
|
||||||
|
@ -168,14 +170,14 @@ Build Options
|
||||||
|
|
||||||
* Build flags: ``--disable-zlib``, ``--disable-jpeg``,
|
* Build flags: ``--disable-zlib``, ``--disable-jpeg``,
|
||||||
``--disable-tiff``, ``--disable-freetype``, ``--disable-tcl``,
|
``--disable-tiff``, ``--disable-freetype``, ``--disable-tcl``,
|
||||||
``--disable-tk``, ``--disable-lcms``, ``--disable-webp``,
|
``--disable-tk``, ``--disable-lcms``, ``--disable-raqm``, ``--disable-webp``,
|
||||||
``--disable-webpmux``, ``--disable-jpeg2000``. Disable building the
|
``--disable-webpmux``, ``--disable-jpeg2000``. Disable building the
|
||||||
corresponding feature even if the development libraries are present
|
corresponding feature even if the development libraries are present
|
||||||
on the building machine.
|
on the building machine.
|
||||||
|
|
||||||
* Build flags: ``--enable-zlib``, ``--enable-jpeg``,
|
* Build flags: ``--enable-zlib``, ``--enable-jpeg``,
|
||||||
``--enable-tiff``, ``--enable-freetype``, ``--enable-tcl``,
|
``--enable-tiff``, ``--enable-freetype``, ``--enable-tcl``,
|
||||||
``--enable-tk``, ``--enable-lcms``, ``--enable-webp``,
|
``--enable-tk``, ``--enable-lcms``, ``--enable-raqm``, ``--enable-webp``,
|
||||||
``--enable-webpmux``, ``--enable-jpeg2000``. Require that the
|
``--enable-webpmux``, ``--enable-jpeg2000``. Require that the
|
||||||
corresponding feature is built. The build will raise an exception if
|
corresponding feature is built. The build will raise an exception if
|
||||||
the libraries are not found. Webpmux (WebP metadata) relies on WebP
|
the libraries are not found. Webpmux (WebP metadata) relies on WebP
|
||||||
|
|
|
@ -227,7 +227,7 @@ Methods
|
||||||
|
|
||||||
Draw a shape.
|
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.
|
Draws the string at the given position.
|
||||||
|
|
||||||
|
@ -240,9 +240,11 @@ Methods
|
||||||
the number of pixels between lines.
|
the number of pixels between lines.
|
||||||
:param align: If the text is passed on to multiline_text(),
|
:param align: If the text is passed on to multiline_text(),
|
||||||
"left", "center" or "right".
|
"left", "center" or "right".
|
||||||
|
:param direction: Direction of the text. It can be 'rtl', 'ltr', 'ttb' or 'btt.
|
||||||
|
:param features: A list of font features used for text layout. For example, 'ligature, kerning, Medial ...etc.
|
||||||
|
|
||||||
|
.. 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.
|
Draws the string at the given position.
|
||||||
|
|
||||||
|
@ -252,6 +254,8 @@ Methods
|
||||||
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
|
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
|
||||||
:param spacing: The number of pixels between lines.
|
:param spacing: The number of pixels between lines.
|
||||||
:param align: "left", "center" or "right".
|
:param align: "left", "center" or "right".
|
||||||
|
:param direction: Direction of the text.
|
||||||
|
:param features: Font features used for text layout.
|
||||||
|
|
||||||
.. py:method:: PIL.ImageDraw.Draw.textsize(text, font=None, spacing=0)
|
.. py:method:: PIL.ImageDraw.Draw.textsize(text, font=None, spacing=0)
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ Methods
|
||||||
|
|
||||||
:return: (width, height)
|
: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.
|
Create a bitmap for the text.
|
||||||
|
|
||||||
|
@ -63,6 +63,8 @@ Methods
|
||||||
driver prefers; if empty, the renderer may return either
|
driver prefers; if empty, the renderer may return either
|
||||||
mode. Note that the mode is always a string, to simplify
|
mode. Note that the mode is always a string, to simplify
|
||||||
C-level implementations.
|
C-level implementations.
|
||||||
|
:param direction: Direction of the text.
|
||||||
|
:param features: A list of font features used for text layout.
|
||||||
|
|
||||||
.. versionadded:: 1.1.5
|
.. versionadded:: 1.1.5
|
||||||
:return: An internal PIL storage memory instance as defined by the
|
:return: An internal PIL storage memory instance as defined by the
|
||||||
|
|
|
@ -180,7 +180,8 @@ if __name__ == "__main__":
|
||||||
("freetype2", "FREETYPE2"),
|
("freetype2", "FREETYPE2"),
|
||||||
("littlecms2", "LITTLECMS2"),
|
("littlecms2", "LITTLECMS2"),
|
||||||
("webp", "WEBP"),
|
("webp", "WEBP"),
|
||||||
("transp_webp", "Transparent WEBP")
|
("transp_webp", "Transparent WEBP"),
|
||||||
|
("raqm", "RAQM")
|
||||||
]:
|
]:
|
||||||
supported = features.check_module(name)
|
supported = features.check_module(name)
|
||||||
|
|
||||||
|
|
22
setup.py
|
@ -99,11 +99,12 @@ TIFF_ROOT = None
|
||||||
FREETYPE_ROOT = None
|
FREETYPE_ROOT = None
|
||||||
LCMS_ROOT = None
|
LCMS_ROOT = None
|
||||||
|
|
||||||
|
RAQM_ROOT = None
|
||||||
|
|
||||||
class pil_build_ext(build_ext):
|
class pil_build_ext(build_ext):
|
||||||
|
|
||||||
class feature:
|
class feature:
|
||||||
zlib = jpeg = tiff = freetype = tcl = tk = lcms = webp = webpmux = None
|
zlib = jpeg = tiff = freetype = raqm = tcl = tk = lcms = webp = webpmux = None
|
||||||
jpeg2000 = None
|
jpeg2000 = None
|
||||||
required = set(['jpeg', 'zlib'])
|
required = set(['jpeg', 'zlib'])
|
||||||
|
|
||||||
|
@ -160,7 +161,7 @@ class pil_build_ext(build_ext):
|
||||||
# add configured kits
|
# add configured kits
|
||||||
|
|
||||||
for root in (TCL_ROOT, JPEG_ROOT, JPEG2K_ROOT, TIFF_ROOT, ZLIB_ROOT,
|
for root in (TCL_ROOT, JPEG_ROOT, JPEG2K_ROOT, TIFF_ROOT, ZLIB_ROOT,
|
||||||
FREETYPE_ROOT, LCMS_ROOT):
|
FREETYPE_ROOT, LCMS_ROOT, RAQM_ROOT):
|
||||||
if isinstance(root, type(())):
|
if isinstance(root, type(())):
|
||||||
lib_root, include_root = root
|
lib_root, include_root = root
|
||||||
else:
|
else:
|
||||||
|
@ -467,6 +468,13 @@ class pil_build_ext(build_ext):
|
||||||
if dir:
|
if dir:
|
||||||
_add_directory(self.compiler.include_dirs, dir, 0)
|
_add_directory(self.compiler.include_dirs, dir, 0)
|
||||||
|
|
||||||
|
if feature.want('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'):
|
if feature.want('lcms'):
|
||||||
if _find_include_file(self, "lcms2.h"):
|
if _find_include_file(self, "lcms2.h"):
|
||||||
if _find_library_file(self, "lcms2"):
|
if _find_library_file(self, "lcms2"):
|
||||||
|
@ -555,8 +563,15 @@ class pil_build_ext(build_ext):
|
||||||
# additional libraries
|
# additional libraries
|
||||||
|
|
||||||
if feature.freetype:
|
if feature.freetype:
|
||||||
|
libs = ["freetype"]
|
||||||
|
defs = []
|
||||||
|
if feature.raqm:
|
||||||
|
libs.extend(feature.raqm)
|
||||||
|
defs.append(('HAVE_RAQM', None))
|
||||||
|
|
||||||
exts.append(Extension(
|
exts.append(Extension(
|
||||||
"PIL._imagingft", ["_imagingft.c"], libraries=["freetype"]))
|
"PIL._imagingft", ["_imagingft.c"], libraries=libs,
|
||||||
|
define_macros=defs))
|
||||||
|
|
||||||
if os.path.isfile("_imagingcms.c") and feature.lcms:
|
if os.path.isfile("_imagingcms.c") and feature.lcms:
|
||||||
extra = []
|
extra = []
|
||||||
|
@ -647,6 +662,7 @@ class pil_build_ext(build_ext):
|
||||||
(feature.zlib, "ZLIB (PNG/ZIP)"),
|
(feature.zlib, "ZLIB (PNG/ZIP)"),
|
||||||
(feature.tiff, "LIBTIFF"),
|
(feature.tiff, "LIBTIFF"),
|
||||||
(feature.freetype, "FREETYPE2"),
|
(feature.freetype, "FREETYPE2"),
|
||||||
|
(feature.raqm, "RAQM"),
|
||||||
(feature.lcms, "LITTLECMS2"),
|
(feature.lcms, "LITTLECMS2"),
|
||||||
(feature.webp, "WEBP"),
|
(feature.webp, "WEBP"),
|
||||||
(feature.webpmux, "WEBPMUX"), ]
|
(feature.webpmux, "WEBPMUX"), ]
|
||||||
|
|