mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 17:36:18 +03:00
503 lines
14 KiB
C
503 lines
14 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"
|
|
|
|
#if !defined(USE_FREETYPE_2_0)
|
|
/* undef/comment out to use freetype 2.0 */
|
|
#define USE_FREETYPE_2_1
|
|
#endif
|
|
|
|
#if defined(USE_FREETYPE_2_1)
|
|
/* freetype 2.1 and newer */
|
|
#include <ft2build.h>
|
|
#include FT_FREETYPE_H
|
|
#else
|
|
/* freetype 2.0 */
|
|
#include <freetype/freetype.h>
|
|
#endif
|
|
|
|
#if PY_VERSION_HEX < 0x01060000
|
|
#define PyObject_New PyObject_NEW
|
|
#define PyObject_Del PyMem_DEL
|
|
#endif
|
|
|
|
#if PY_VERSION_HEX >= 0x01060000
|
|
#if PY_VERSION_HEX < 0x02020000 || defined(Py_USING_UNICODE)
|
|
/* defining this enables unicode support (default under 1.6a1 and later) */
|
|
#define HAVE_UNICODE
|
|
#endif
|
|
#endif
|
|
|
|
#if !defined(Py_RETURN_NONE)
|
|
#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
|
|
#endif
|
|
|
|
#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 <freetype/fterrors.h>
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* font objects */
|
|
|
|
static FT_Library library;
|
|
|
|
typedef struct {
|
|
PyObject_HEAD
|
|
FT_Face face;
|
|
} FontObject;
|
|
|
|
staticforward 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;
|
|
|
|
char* filename;
|
|
int size;
|
|
int index = 0;
|
|
unsigned char* encoding = NULL;
|
|
static char* kwlist[] = {
|
|
"filename", "size", "index", "encoding", NULL
|
|
};
|
|
|
|
#if defined(HAVE_UNICODE) && PY_VERSION_HEX >= 0x02020000
|
|
if (!PyArg_ParseTupleAndKeywords(args, kw, "eti|is", kwlist,
|
|
Py_FileSystemDefaultEncoding, &filename,
|
|
&size, &index, &encoding))
|
|
return NULL;
|
|
#else
|
|
if (!PyArg_ParseTupleAndKeywords(args, kw, "si|is", kwlist,
|
|
&filename, &size, &index, &encoding))
|
|
return NULL;
|
|
#endif
|
|
|
|
if (!library) {
|
|
PyErr_SetString(
|
|
PyExc_IOError,
|
|
"failed to initialize FreeType library"
|
|
);
|
|
return NULL;
|
|
}
|
|
|
|
self = PyObject_New(FontObject, &Font_Type);
|
|
if (!self)
|
|
return NULL;
|
|
|
|
error = FT_New_Face(library, filename, 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 (error) {
|
|
PyObject_Del(self);
|
|
return geterror(error);
|
|
}
|
|
|
|
return (PyObject*) self;
|
|
}
|
|
|
|
static int
|
|
font_getchar(PyObject* string, int index, FT_ULong* char_out)
|
|
{
|
|
#if defined(HAVE_UNICODE)
|
|
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;
|
|
}
|
|
#endif
|
|
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;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static PyObject*
|
|
font_getsize(FontObject* self, PyObject* args)
|
|
{
|
|
int i, x;
|
|
FT_ULong ch;
|
|
FT_Face face;
|
|
int xoffset;
|
|
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 defined(HAVE_UNICODE)
|
|
if (!PyUnicode_Check(string) && !PyString_Check(string)) {
|
|
#else
|
|
if (!PyString_Check(string)) {
|
|
#endif
|
|
PyErr_SetString(PyExc_TypeError, "expected string");
|
|
return NULL;
|
|
}
|
|
|
|
face = NULL;
|
|
xoffset = 0;
|
|
|
|
for (x = i = 0; font_getchar(string, i, &ch); i++) {
|
|
int index, error;
|
|
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;
|
|
}
|
|
error = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT);
|
|
if (error)
|
|
return geterror(error);
|
|
if (i == 0)
|
|
xoffset = face->glyph->metrics.horiBearingX;
|
|
x += face->glyph->metrics.horiAdvance;
|
|
last_index = index;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
return Py_BuildValue(
|
|
"(ii)(ii)",
|
|
PIXEL(x), PIXEL(self->face->size->metrics.height),
|
|
PIXEL(xoffset), 0
|
|
);
|
|
}
|
|
|
|
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 defined(HAVE_UNICODE)
|
|
if (!PyUnicode_Check(string) && !PyString_Check(string)) {
|
|
#else
|
|
if (!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);
|
|
error = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT);
|
|
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_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;
|
|
long id;
|
|
int mask = 0;
|
|
if (!PyArg_ParseTuple(args, "Ol|i:render", &string, &id, &mask))
|
|
return NULL;
|
|
|
|
#if defined(HAVE_UNICODE)
|
|
if (!PyUnicode_Check(string) && !PyString_Check(string)) {
|
|
#else
|
|
if (!PyString_Check(string)) {
|
|
#endif
|
|
PyErr_SetString(PyExc_TypeError, "expected string");
|
|
return NULL;
|
|
}
|
|
|
|
im = (Imaging) id;
|
|
|
|
load_flags = FT_LOAD_RENDER;
|
|
if (mask)
|
|
load_flags |= FT_LOAD_TARGET_MONO;
|
|
|
|
for (x = i = 0; font_getchar(string, i, &ch); 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;
|
|
}
|
|
error = FT_Load_Glyph(self->face, index, load_flags);
|
|
if (error)
|
|
return geterror(error);
|
|
glyph = self->face->glyph;
|
|
if (mask) {
|
|
/* use monochrome mask (on palette images, etc) */
|
|
int xx, x0, x1;
|
|
source = (unsigned char*) glyph->bitmap.buffer;
|
|
ascender = PIXEL(self->face->size->metrics.ascender);
|
|
xx = x + glyph->bitmap_left;
|
|
x0 = 0;
|
|
x1 = glyph->bitmap.width;
|
|
if (xx < 0)
|
|
x0 = -xx;
|
|
if (xx + x1 > im->xsize)
|
|
x1 = im->xsize - xx;
|
|
for (y = 0; y < glyph->bitmap.rows; y++) {
|
|
int yy = y + ascender - glyph->bitmap_top;
|
|
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 */
|
|
int xx, x0, x1;
|
|
source = (unsigned char*) glyph->bitmap.buffer;
|
|
ascender = PIXEL(self->face->size->metrics.ascender);
|
|
xx = x + glyph->bitmap_left;
|
|
x0 = 0;
|
|
x1 = glyph->bitmap.width;
|
|
if (xx < 0)
|
|
x0 = -xx;
|
|
if (xx + x1 > im->xsize)
|
|
x1 = im->xsize - xx;
|
|
for (y = 0; y < glyph->bitmap.rows; y++) {
|
|
int yy = y + ascender - glyph->bitmap_top;
|
|
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->metrics.horiAdvance);
|
|
last_index = index;
|
|
}
|
|
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static void
|
|
font_dealloc(FontObject* self)
|
|
{
|
|
FT_Done_Face(self->face);
|
|
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(FontObject* self, char* name)
|
|
{
|
|
PyObject* res;
|
|
|
|
res = Py_FindMethod(font_methods, (PyObject*) self, name);
|
|
|
|
if (res)
|
|
return res;
|
|
|
|
PyErr_Clear();
|
|
|
|
/* attributes */
|
|
if (!strcmp(name, "family")) {
|
|
if (self->face->family_name)
|
|
return PyString_FromString(self->face->family_name);
|
|
Py_RETURN_NONE;
|
|
}
|
|
if (!strcmp(name, "style")) {
|
|
if (self->face->style_name)
|
|
return PyString_FromString(self->face->style_name);
|
|
Py_RETURN_NONE;
|
|
}
|
|
if (!strcmp(name, "ascent"))
|
|
return PyInt_FromLong(PIXEL(self->face->size->metrics.ascender));
|
|
if (!strcmp(name, "descent"))
|
|
return PyInt_FromLong(-PIXEL(self->face->size->metrics.descender));
|
|
|
|
if (!strcmp(name, "glyphs"))
|
|
/* number of glyphs provided by this font */
|
|
return PyInt_FromLong(self->face->num_glyphs);
|
|
|
|
PyErr_SetString(PyExc_AttributeError, name);
|
|
return NULL;
|
|
}
|
|
|
|
statichere PyTypeObject Font_Type = {
|
|
PyObject_HEAD_INIT(NULL)
|
|
0, "Font", sizeof(FontObject), 0,
|
|
/* methods */
|
|
(destructor)font_dealloc, /* tp_dealloc */
|
|
0, /* tp_print */
|
|
(getattrfunc)font_getattr, /* tp_getattr */
|
|
};
|
|
|
|
static PyMethodDef _functions[] = {
|
|
{"getfont", (PyCFunction) getfont, METH_VARARGS|METH_KEYWORDS},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
DL_EXPORT(void)
|
|
init_imagingft(void)
|
|
{
|
|
PyObject* m;
|
|
PyObject* d;
|
|
PyObject* v;
|
|
int major, minor, patch;
|
|
|
|
/* Patch object type */
|
|
Font_Type.ob_type = &PyType_Type;
|
|
|
|
m = Py_InitModule("_imagingft", _functions);
|
|
d = PyModule_GetDict(m);
|
|
|
|
if (FT_Init_FreeType(&library))
|
|
return; /* leave it uninitalized */
|
|
|
|
FT_Library_Version(library, &major, &minor, &patch);
|
|
|
|
#if PY_VERSION_HEX >= 0x02020000
|
|
v = PyString_FromFormat("%d.%d.%d", major, minor, patch);
|
|
#else
|
|
{
|
|
char buffer[100];
|
|
sprintf(buffer, "%d.%d.%d", major, minor, patch);
|
|
v = PyString_FromString(buffer);
|
|
}
|
|
#endif
|
|
PyDict_SetItemString(d, "freetype2_version", v);
|
|
}
|