Pillow/_imagingft.c
shamsa 0b178edbc8 Add complex text support.
This pull request adds support for languages that require complex text layout.

We are using the Raqm library, that wraps FriBidi (for bidirectional
text support) and HarfBuzz (for text shaping), and does proper BiDi and script
itemization:
https://github.com/HOST-Oman/libraqm

This should fix #1089.
2017-06-21 14:00:13 -07:00

741 lines
21 KiB
C

/*
* PIL FreeType Driver
*
* a FreeType 2.X driver for PIL
*
* history:
* 2001-02-17 fl Created (based on old experimental freetype 1.0 code)
* 2001-04-18 fl Fixed some egcs compiler nits
* 2002-11-08 fl Added unicode support; more font metrics, etc
* 2003-05-20 fl Fixed compilation under 1.5.2 and newer non-unicode builds
* 2003-09-27 fl Added charmap encoding support
* 2004-05-15 fl Fixed compilation for FreeType 2.1.8
* 2004-09-10 fl Added support for monochrome bitmaps
* 2006-06-18 fl Fixed glyph bearing calculation
* 2007-12-23 fl Fixed crash in family/style attribute fetch
* 2008-01-02 fl Handle Unicode filenames properly
*
* Copyright (c) 1998-2007 by Secret Labs AB
*/
#include "Python.h"
#include "Imaging.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#define KEEP_PY_UNICODE
#include "py3.h"
#if !defined(FT_LOAD_TARGET_MONO)
#define FT_LOAD_TARGET_MONO FT_LOAD_MONOCHROME
#endif
/* -------------------------------------------------------------------- */
/* error table */
#undef FTERRORS_H
#undef __FTERRORS_H__
#define FT_ERRORDEF( e, v, s ) { e, s },
#define FT_ERROR_START_LIST {
#define FT_ERROR_END_LIST { 0, 0 } };
struct {
int code;
const char* message;
} ft_errors[] =
#include FT_ERRORS_H
#include "raqm.h"
/* -------------------------------------------------------------------- */
/* font objects */
static FT_Library library;
typedef struct {
PyObject_HEAD
FT_Face face;
unsigned char *font_bytes;
} FontObject;
static PyTypeObject Font_Type;
/* round a 26.6 pixel coordinate to the nearest larger integer */
#define PIXEL(x) ((((x)+63) & -64)>>6)
static PyObject*
geterror(int code)
{
int i;
for (i = 0; ft_errors[i].message; i++)
if (ft_errors[i].code == code) {
PyErr_SetString(PyExc_IOError, ft_errors[i].message);
return NULL;
}
PyErr_SetString(PyExc_IOError, "unknown freetype error");
return NULL;
}
static PyObject*
getfont(PyObject* self_, PyObject* args, PyObject* kw)
{
/* create a font object from a file name and a size (in pixels) */
FontObject* self;
int error = 0;
char* filename = NULL;
int size;
int index = 0;
unsigned char* encoding;
unsigned char* font_bytes;
int font_bytes_size = 0;
static char* kwlist[] = {
"filename", "size", "index", "encoding", "font_bytes", NULL
};
if (!library) {
PyErr_SetString(
PyExc_IOError,
"failed to initialize FreeType library"
);
return NULL;
}
if (!PyArg_ParseTupleAndKeywords(args, kw, "eti|iss#", kwlist,
Py_FileSystemDefaultEncoding, &filename,
&size, &index, &encoding, &font_bytes,
&font_bytes_size)) {
return NULL;
}
self = PyObject_New(FontObject, &Font_Type);
if (!self) {
if (filename)
PyMem_Free(filename);
return NULL;
}
self->face = NULL;
if (filename && font_bytes_size <= 0) {
self->font_bytes = NULL;
error = FT_New_Face(library, filename, index, &self->face);
} else {
/* need to have allocated storage for font_bytes for the life of the object.*/
/* Don't free this before FT_Done_Face */
self->font_bytes = PyMem_Malloc(font_bytes_size);
if (!self->font_bytes) {
error = 65; // Out of Memory in Freetype.
}
if (!error) {
memcpy(self->font_bytes, font_bytes, (size_t)font_bytes_size);
error = FT_New_Memory_Face(library, (FT_Byte*)self->font_bytes,
font_bytes_size, index, &self->face);
}
}
if (!error)
error = FT_Set_Pixel_Sizes(self->face, 0, size);
if (!error && encoding && strlen((char*) encoding) == 4) {
FT_Encoding encoding_tag = FT_MAKE_TAG(
encoding[0], encoding[1], encoding[2], encoding[3]
);
error = FT_Select_Charmap(self->face, encoding_tag);
}
if (filename)
PyMem_Free(filename);
if (error) {
if (self->font_bytes) {
PyMem_Free(self->font_bytes);
}
Py_DECREF(self);
return geterror(error);
}
return (PyObject*) self;
}
static int
font_getchar(PyObject* string, int index, FT_ULong* char_out)
{
if (PyUnicode_Check(string)) {
Py_UNICODE* p = PyUnicode_AS_UNICODE(string);
int size = PyUnicode_GET_SIZE(string);
if (index >= size)
return 0;
*char_out = p[index];
return 1;
}
#if PY_VERSION_HEX < 0x03000000
if (PyString_Check(string)) {
unsigned char* p = (unsigned char*) PyString_AS_STRING(string);
int size = PyString_GET_SIZE(string);
if (index >= size)
return 0;
*char_out = (unsigned char) p[index];
return 1;
}
#endif
return 0;
}
static PyObject*
font_getsize(FontObject* self, PyObject* args)
{
int i, x, y_max, y_min;
FT_ULong ch;
FT_Face face;
int xoffset, yoffset;
FT_Bool kerning = FT_HAS_KERNING(self->face);
FT_UInt last_index = 0;
/* calculate size and bearing for a given string */
PyObject* string;
if (!PyArg_ParseTuple(args, "O:getsize", &string))
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;
xoffset = yoffset = 0;
y_max = y_min = 0;
for (x = i = 0; font_getchar(string, i, &ch); 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
*/
error = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT|FT_LOAD_NO_BITMAP);
if (error)
return geterror(error);
if (i == 0)
xoffset = face->glyph->metrics.horiBearingX;
x += face->glyph->metrics.horiAdvance;
FT_Get_Glyph(face->glyph, &glyph);
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_SUBPIXELS, &bbox);
if (bbox.yMax > y_max)
y_max = bbox.yMax;
if (bbox.yMin < y_min)
y_min = bbox.yMin;
/* find max distance of baseline from top */
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);
}
return Py_BuildValue(
"(ii)(ii)",
PIXEL(x), PIXEL(y_max - y_min),
PIXEL(xoffset), yoffset
);
}
static PyObject*
font_getabc(FontObject* self, PyObject* args)
{
FT_ULong ch;
FT_Face face;
double a, b, c;
/* calculate ABC values for a given string */
PyObject* string;
if (!PyArg_ParseTuple(args, "O:getabc", &string))
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;
}
if (font_getchar(string, 0, &ch)) {
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 */
error = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT|FT_LOAD_NO_BITMAP);
if (error)
return geterror(error);
a = face->glyph->metrics.horiBearingX / 64.0;
b = face->glyph->metrics.width / 64.0;
c = (face->glyph->metrics.horiAdvance -
face->glyph->metrics.horiBearingX -
face->glyph->metrics.width) / 64.0;
} else
a = b = c = 0.0;
return Py_BuildValue("ddd", a, b, c);
}
static PyObject*
font_render(FontObject* self, PyObject* args)
{
int i, x, y;
Imaging im;
int index, error, ascender;
int load_flags;
unsigned char *source;
FT_GlyphSlot glyph;
/* render string into given buffer (the buffer *must* have
the right size, or this will crash) */
PyObject* string;
Py_ssize_t id;
int mask = 0;
int temp;
int xx, x0, x1;
const char *dir = NULL;
raqm_direction_t direction;
raqm_t *rq;
size_t count;
raqm_glyph_t *glyph_info;
PyObject *features = NULL;
if (!PyArg_ParseTuple(args, "On|izO:render", &string, &id, &mask, &dir, &features))
return NULL;
rq = raqm_create();
if (rq == NULL) {
PyErr_SetString(PyExc_ValueError, "raqm_create() failed.");
goto failed;
}
#if PY_VERSION_HEX >= 0x03000000
if (PyUnicode_Check(string)) {
Py_ssize_t size = PyUnicode_GET_LENGTH(string);
Py_UCS4 text[size];
PyUnicode_READY(string);
if (!PyUnicode_AsUCS4(string, text, size, 0))
goto failed;
if (!raqm_set_text(rq, text, size)) {
PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
goto failed;
}
}
#else
if (PyUnicode_Check(string)) {
Py_UNICODE *text = PyUnicode_AS_UNICODE(string);
Py_ssize_t size = PyUnicode_GET_SIZE(string);
if (!raqm_set_text(rq, text, size)) {
PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
goto failed;
}
}
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) {
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;
#if PY_VERSION_HEX >= 0x03000000
if (!PyUnicode_Check(item) ||
PyUnicode_READY(string) != 0 ||
PyUnicode_KIND(item) != PyUnicode_1BYTE_KIND) {
PyErr_SetString(PyExc_TypeError, "expected an ASCII string");
goto failed;
}
feature = PyUnicode_1BYTE_DATA(item);
size = PyUnicode_GET_LENGTH(item);
#else
if (!PyString_Check(item)) {
PyErr_SetString(PyExc_TypeError, "expected a string");
goto failed;
}
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;
}
glyph_info = raqm_get_glyphs(rq, &count);
if (glyph_info == NULL) {
PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed.");
goto failed;
}
im = (Imaging) id;
/* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 */
load_flags = FT_LOAD_RENDER|FT_LOAD_NO_BITMAP;
if (mask)
load_flags |= FT_LOAD_TARGET_MONO;
ascender = 0;
for (i = 0; i < count; i++) {
index = glyph_info[i].index;
error = FT_Load_Glyph(self->face, index, load_flags);
if (error) {
geterror(error);
goto failed;
}
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; i < count; i++) {
if (i == 0 && self->face->glyph->metrics.horiBearingX < 0)
x = -PIXEL(self->face->glyph->metrics.horiBearingX);
index = glyph_info[i].index;
error = FT_Load_Glyph(self->face, index, load_flags);
if (error){
geterror(error);
goto failed;
}
glyph = self->face->glyph;
source = (unsigned char*) glyph->bitmap.buffer;
xx = x + glyph->bitmap_left;
xx += PIXEL(glyph_info[i].x_offset);
x0 = 0;
x1 = glyph->bitmap.width;
if (xx < 0)
x0 = -xx;
if (xx + x1 > im->xsize)
x1 = im->xsize - xx;
if (mask) {
/* 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;
int i, j, m = 128;
for (i = j = 0; j < x1; j++) {
if (j >= x0 && (source[i] & m))
target[j] = 255;
if (!(m >>= 1)) {
m = 128;
i++;
}
}
}
source += glyph->bitmap.pitch;
}
} else {
/* 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++) {
if (target[i] < source[i])
target[i] = source[i];
}
}
source += glyph->bitmap.pitch;
}
}
x += PIXEL(glyph_info[i].x_advance);
}
Py_RETURN_NONE;
failed:
raqm_destroy(rq);
return NULL;
}
static void
font_dealloc(FontObject* self)
{
if (self->face) {
FT_Done_Face(self->face);
}
if (self->font_bytes) {
PyMem_Free(self->font_bytes);
}
PyObject_Del(self);
}
static PyMethodDef font_methods[] = {
{"render", (PyCFunction) font_render, METH_VARARGS},
{"getsize", (PyCFunction) font_getsize, METH_VARARGS},
{"getabc", (PyCFunction) font_getabc, METH_VARARGS},
{NULL, NULL}
};
static PyObject*
font_getattr_family(FontObject* self, void* closure)
{
#if PY_VERSION_HEX >= 0x03000000
if (self->face->family_name)
return PyUnicode_FromString(self->face->family_name);
#else
if (self->face->family_name)
return PyString_FromString(self->face->family_name);
#endif
Py_RETURN_NONE;
}
static PyObject*
font_getattr_style(FontObject* self, void* closure)
{
#if PY_VERSION_HEX >= 0x03000000
if (self->face->style_name)
return PyUnicode_FromString(self->face->style_name);
#else
if (self->face->style_name)
return PyString_FromString(self->face->style_name);
#endif
Py_RETURN_NONE;
}
static PyObject*
font_getattr_ascent(FontObject* self, void* closure)
{
return PyInt_FromLong(PIXEL(self->face->size->metrics.ascender));
}
static PyObject*
font_getattr_descent(FontObject* self, void* closure)
{
return PyInt_FromLong(-PIXEL(self->face->size->metrics.descender));
}
static PyObject*
font_getattr_height(FontObject* self, void* closure)
{
return PyInt_FromLong(PIXEL(self->face->size->metrics.height));
}
static PyObject*
font_getattr_x_ppem(FontObject* self, void* closure)
{
return PyInt_FromLong(self->face->size->metrics.x_ppem);
}
static PyObject*
font_getattr_y_ppem(FontObject* self, void* closure)
{
return PyInt_FromLong(self->face->size->metrics.y_ppem);
}
static PyObject*
font_getattr_glyphs(FontObject* self, void* closure)
{
return PyInt_FromLong(self->face->num_glyphs);
}
static struct PyGetSetDef font_getsetters[] = {
{ "family", (getter) font_getattr_family },
{ "style", (getter) font_getattr_style },
{ "ascent", (getter) font_getattr_ascent },
{ "descent", (getter) font_getattr_descent },
{ "height", (getter) font_getattr_height },
{ "x_ppem", (getter) font_getattr_x_ppem },
{ "y_ppem", (getter) font_getattr_y_ppem },
{ "glyphs", (getter) font_getattr_glyphs },
{ NULL }
};
static PyTypeObject Font_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"Font", sizeof(FontObject), 0,
/* methods */
(destructor)font_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number */
0, /*tp_as_sequence */
0, /*tp_as_mapping */
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
font_methods, /*tp_methods*/
0, /*tp_members*/
font_getsetters, /*tp_getset*/
};
static PyMethodDef _functions[] = {
{"getfont", (PyCFunction) getfont, METH_VARARGS|METH_KEYWORDS},
{NULL, NULL}
};
static int
setup_module(PyObject* m) {
PyObject* d;
PyObject* v;
int major, minor, patch;
d = PyModule_GetDict(m);
/* Ready object type */
PyType_Ready(&Font_Type);
if (FT_Init_FreeType(&library))
return 0; /* leave it uninitialized */
FT_Library_Version(library, &major, &minor, &patch);
#if PY_VERSION_HEX >= 0x03000000
v = PyUnicode_FromFormat("%d.%d.%d", major, minor, patch);
#else
v = PyString_FromFormat("%d.%d.%d", major, minor, patch);
#endif
PyDict_SetItemString(d, "freetype2_version", v);
return 0;
}
#if PY_VERSION_HEX >= 0x03000000
PyMODINIT_FUNC
PyInit__imagingft(void) {
PyObject* m;
static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT,
"_imagingft", /* m_name */
NULL, /* m_doc */
-1, /* m_size */
_functions, /* m_methods */
};
m = PyModule_Create(&module_def);
if (setup_module(m) < 0)
return NULL;
return m;
}
#else
PyMODINIT_FUNC
init_imagingft(void)
{
PyObject* m = Py_InitModule("_imagingft", _functions);
setup_module(m);
}
#endif