Start a python interface to FreeType

This commit is contained in:
Kovid Goyal 2012-10-20 13:28:05 +05:30
parent ade7c9280a
commit 42f2f945e7
8 changed files with 383 additions and 3 deletions

View File

@ -8,6 +8,7 @@ let g:syntastic_cpp_include_dirs = [
\'/usr/include/qt4/QtCore', \'/usr/include/qt4/QtCore',
\'/usr/include/qt4/QtGui', \'/usr/include/qt4/QtGui',
\'/usr/include/qt4', \'/usr/include/qt4',
\'/usr/include/freetype2',
\'src/qtcurve/common', 'src/qtcurve', \'src/qtcurve/common', 'src/qtcurve',
\'/usr/include/ImageMagick', \'/usr/include/ImageMagick',
\] \]

View File

@ -84,6 +84,7 @@ qt_inc = pyqt.qt_inc_dir
qt_lib = pyqt.qt_lib_dir qt_lib = pyqt.qt_lib_dir
ft_lib_dirs = [] ft_lib_dirs = []
ft_libs = [] ft_libs = []
ft_inc_dirs = []
jpg_libs = [] jpg_libs = []
jpg_lib_dirs = [] jpg_lib_dirs = []
fc_inc = '/usr/include/fontconfig' fc_inc = '/usr/include/fontconfig'
@ -116,6 +117,7 @@ if iswindows:
jpg_libs = ['jpeg'] jpg_libs = ['jpeg']
ft_lib_dirs = [sw_lib_dir] ft_lib_dirs = [sw_lib_dir]
ft_libs = ['freetype'] ft_libs = ['freetype']
ft_inc_dirs = [sw_inc_dir]
magick_inc_dirs = [os.path.join(prefix, 'build', 'ImageMagick-6.7.6')] magick_inc_dirs = [os.path.join(prefix, 'build', 'ImageMagick-6.7.6')]
magick_lib_dirs = [os.path.join(magick_inc_dirs[0], 'VisualMagick', 'lib')] magick_lib_dirs = [os.path.join(magick_inc_dirs[0], 'VisualMagick', 'lib')]
@ -135,6 +137,8 @@ elif isosx:
png_inc_dirs = consolidate('PNG_INC_DIR', '/sw/include') png_inc_dirs = consolidate('PNG_INC_DIR', '/sw/include')
png_lib_dirs = consolidate('PNG_LIB_DIR', '/sw/lib') png_lib_dirs = consolidate('PNG_LIB_DIR', '/sw/lib')
png_libs = ['png12'] png_libs = ['png12']
ft_libs = ['freetype']
ft_inc_dirs = ['/sw/include/freetype2']
else: else:
# Include directories # Include directories
png_inc_dirs = pkgconfig_include_dirs('libpng', 'PNG_INC_DIR', png_inc_dirs = pkgconfig_include_dirs('libpng', 'PNG_INC_DIR',
@ -150,6 +154,10 @@ else:
if not magick_libs: if not magick_libs:
magick_libs = ['MagickWand', 'MagickCore'] magick_libs = ['MagickWand', 'MagickCore']
png_libs = ['png'] png_libs = ['png']
ft_inc_dirs = pkgconfig_include_dirs('freetype2', 'FT_INC_DIR',
'/usr/include/freetype2')
ft_lib_dirs = pkgconfig_lib_dirs('freetype2', 'FT_LIB_DIR', '/usr/lib')
ft_libs = pkgconfig_libs('freetype2', '', '')
fc_inc = os.environ.get('FC_INC_DIR', fc_inc) fc_inc = os.environ.get('FC_INC_DIR', fc_inc)

View File

@ -17,7 +17,7 @@ from setup.build_environment import (fc_inc, fc_lib, chmlib_inc_dirs, fc_error,
podofo_inc, podofo_lib, podofo_error, pyqt, OSX_SDK, NMAKE, QMAKE, podofo_inc, podofo_lib, podofo_error, pyqt, OSX_SDK, NMAKE, QMAKE,
msvc, MT, win_inc, win_lib, win_ddk, magick_inc_dirs, magick_lib_dirs, msvc, MT, win_inc, win_lib, win_ddk, magick_inc_dirs, magick_lib_dirs,
magick_libs, chmlib_lib_dirs, sqlite_inc_dirs, icu_inc_dirs, magick_libs, chmlib_lib_dirs, sqlite_inc_dirs, icu_inc_dirs,
icu_lib_dirs, win_ddk_lib_dirs) icu_lib_dirs, win_ddk_lib_dirs, ft_libs, ft_lib_dirs, ft_inc_dirs)
MT MT
isunix = islinux or isosx or isbsd isunix = islinux or isosx or isbsd
@ -119,6 +119,12 @@ extensions = [
'calibre/utils/lzx/mspack.h'], 'calibre/utils/lzx/mspack.h'],
inc_dirs=['calibre/utils/lzx']), inc_dirs=['calibre/utils/lzx']),
Extension('freetype',
['calibre/utils/fonts/freetype.cpp'],
inc_dirs = ft_inc_dirs,
libraries=ft_libs,
lib_dirs=ft_lib_dirs),
Extension('fontconfig', Extension('fontconfig',
['calibre/utils/fonts/fontconfig.c'], ['calibre/utils/fonts/fontconfig.c'],
inc_dirs = [fc_inc], inc_dirs = [fc_inc],

View File

@ -89,6 +89,7 @@ class Plugins(collections.Mapping):
'chm_extra', 'chm_extra',
'icu', 'icu',
'speedup', 'speedup',
'freetype',
] ]
if iswindows: if iswindows:
plugins.extend(['winutil', 'wpd', 'winfonts']) plugins.extend(['winutil', 'wpd', 'winfonts'])

View File

@ -32,6 +32,11 @@ def test_lxml():
else: else:
raise RuntimeError('lxml failed') raise RuntimeError('lxml failed')
def test_freetype():
from calibre.utils.fonts.freetype import test
test()
print ('FreeType OK!')
def test_fontconfig(): def test_fontconfig():
from calibre.utils.fonts import fontconfig from calibre.utils.fonts import fontconfig
families = fontconfig.find_font_families() families = fontconfig.find_font_families()
@ -103,7 +108,7 @@ def test_icu():
def test_wpd(): def test_wpd():
wpd = plugins['wpd'][0] wpd = plugins['wpd'][0]
try: try:
wpd.init() wpd.init('calibre', 1, 1, 1)
except wpd.NoWPD: except wpd.NoWPD:
print ('This computer does not have WPD') print ('This computer does not have WPD')
else: else:
@ -112,6 +117,7 @@ def test_wpd():
def test(): def test():
test_plugins() test_plugins()
test_lxml() test_lxml()
test_freetype()
test_fontconfig() test_fontconfig()
test_sqlite() test_sqlite()
test_qt() test_qt()

View File

@ -55,7 +55,7 @@ class Fonts(object):
files = self.backend.files_for_family(family, normalize=normalize) files = self.backend.files_for_family(family, normalize=normalize)
ans = {} ans = {}
for ft, val in files.iteritems(): for ft, val in files.iteritems():
name, f = val f, name = val
ext = f.rpartition('.')[-1].lower() ext = f.rpartition('.')[-1].lower()
ans[ft] = (ext, name, open(f, 'rb').read()) ans[ft] = (ext, name, open(f, 'rb').read())
return ans return ans

View File

@ -0,0 +1,289 @@
/*
* freetype.cpp
* Copyright (C) 2012 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#define _UNICODE
#define UNICODE
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <ft2build.h>
#include FT_FREETYPE_H
static PyObject *FreeTypeError = NULL;
typedef struct {
PyObject_HEAD
FT_Face *face;
// Every face must keep a reference to the FreeType library object to
// ensure it is garbage collected before the library object, to prevent
// segfaults.
PyObject *library;
} Face;
typedef struct {
PyObject_HEAD
FT_Library *library;
} FreeType;
// Face.__init__() {{{
static void
Face_dealloc(Face* self)
{
if (self->face != NULL) {
Py_BEGIN_ALLOW_THREADS;
FT_Done_Face(*(self->face));
free(self->face);
Py_END_ALLOW_THREADS;
}
self->face = NULL;
Py_DECREF(self->library);
self->library = NULL;
self->ob_type->tp_free((PyObject*)self);
}
static int
Face_init(Face *self, PyObject *args, PyObject *kwds)
{
FT_Error error = 0;
char *data;
Py_ssize_t sz;
PyObject *ft;
if (!PyArg_ParseTuple(args, "Os#", &ft, &data, &sz)) return -1;
self->face = (FT_Face*)calloc(1, sizeof(FT_Face));
if (self->face == NULL) { PyErr_NoMemory(); return -1; }
self->library = ft;
Py_XINCREF(ft);
Py_BEGIN_ALLOW_THREADS;
error = FT_New_Memory_Face( *(( (FreeType*)ft )->library),
(const FT_Byte*)data, (FT_Long)sz, 0, self->face);
Py_END_ALLOW_THREADS;
if (error) {
free(self->face); self->face = NULL;
if ( error == FT_Err_Unknown_File_Format || error == FT_Err_Invalid_Stream_Operation)
PyErr_SetString(FreeTypeError, "Not a supported font format");
else
PyErr_Format(FreeTypeError, "Failed to initialize the Font with error: 0x%x", error);
return -1;
}
return 0;
}
// }}}
static PyObject*
supports_text(Face *self, PyObject *args) {
PyObject *chars, *fast, *ret = Py_True;
Py_ssize_t sz, i;
FT_ULong code;
FT_Face face;
if (!PyArg_ParseTuple(args, "O", &chars)) return NULL;
fast = PySequence_Fast(chars, "List of chars is not a sequence");
if (fast == NULL) return NULL;
sz = PySequence_Fast_GET_SIZE(fast);
face = *(self->face);
for (i = 0; i < sz; i++) {
code = (FT_ULong)PyNumber_AsSsize_t(PySequence_Fast_GET_ITEM(fast, i), NULL);
if (FT_Get_Char_Index(face, code) == 0) {
ret = Py_False;
break;
}
}
Py_DECREF(fast);
Py_XINCREF(ret);
return ret;
}
static PyMethodDef Face_methods[] = {
{"supports_text", (PyCFunction)supports_text, METH_VARARGS,
"supports_text(sequence of unicode character codes) -> Return True iff this font has glyphs for all the specified characters."
},
{NULL} /* Sentinel */
};
// FreeType.__init__() {{{
static void
dealloc(FreeType* self)
{
if (self->library != NULL) {
Py_BEGIN_ALLOW_THREADS;
FT_Done_FreeType(*(self->library));
free(self->library);
Py_END_ALLOW_THREADS;
}
self->library = NULL;
self->ob_type->tp_free((PyObject*)self);
}
static int
init(FreeType *self, PyObject *args, PyObject *kwds)
{
FT_Error error = 0;
self->library = (FT_Library*)calloc(1, sizeof(FT_Library));
if (self->library == NULL) { PyErr_NoMemory(); return -1; }
Py_BEGIN_ALLOW_THREADS;
error = FT_Init_FreeType(self->library);
Py_END_ALLOW_THREADS;
if (error) {
free(self->library);
self->library = NULL;
PyErr_Format(FreeTypeError, "Failed to initialize the FreeType library with error: %d", error);
return -1;
}
return 0;
}
// }}}
static PyTypeObject FaceType = { // {{{
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"freetype.Face", /*tp_name*/
sizeof(Face), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor)Face_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|Py_TPFLAGS_BASETYPE, /*tp_flags*/
"Face", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
Face_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)Face_init, /* tp_init */
0, /* tp_alloc */
0, /* tp_new */
}; // }}}
static PyObject*
load_font(FreeType *self, PyObject *args) {
PyObject *ret, *arg_list, *bytes;
if (!PyArg_ParseTuple(args, "O", &bytes)) return NULL;
arg_list = Py_BuildValue("OO", self, bytes);
if (arg_list == NULL) return NULL;
ret = PyObject_CallObject((PyObject *) &FaceType, arg_list);
Py_DECREF(arg_list);
return ret;
}
static PyMethodDef FreeType_methods[] = {
{"load_font", (PyCFunction)load_font, METH_VARARGS,
"load_font(bytestring) -> Load a font from font data."
},
{NULL} /* Sentinel */
};
static PyTypeObject FreeTypeType = { // {{{
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"freetype.FreeType", /*tp_name*/
sizeof(FreeType), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor)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|Py_TPFLAGS_BASETYPE, /*tp_flags*/
"FreeType", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
FreeType_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)init, /* tp_init */
0, /* tp_alloc */
0, /* tp_new */
}; // }}}
static
PyMethodDef methods[] = {
{NULL, NULL, 0, NULL}
};
PyMODINIT_FUNC
initfreetype(void) {
PyObject *m;
FreeTypeType.tp_new = PyType_GenericNew;
if (PyType_Ready(&FreeTypeType) < 0)
return;
FaceType.tp_new = PyType_GenericNew;
if (PyType_Ready(&FaceType) < 0)
return;
m = Py_InitModule3(
"freetype", methods,
"FreeType API"
);
if (m == NULL) return;
FreeTypeError = PyErr_NewException((char*)"freetype.FreeTypeError", NULL, NULL);
if (FreeTypeError == NULL) return;
PyModule_AddObject(m, "FreeTypeError", FreeTypeError);
Py_INCREF(&FreeTypeType);
PyModule_AddObject(m, "FreeType", (PyObject *)&FreeTypeType);
PyModule_AddObject(m, "Face", (PyObject *)&FaceType);
}

View File

@ -0,0 +1,69 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import threading
from functools import wraps
from future_builtins import map
from calibre.constants import plugins
class ThreadingViolation(Exception):
def __init__(self):
Exception.__init__(self,
'You cannot use the MTP driver from a thread other than the '
' thread in which startup() was called')
def same_thread(func):
@wraps(func)
def check_thread(self, *args, **kwargs):
if self.start_thread is not threading.current_thread():
raise ThreadingViolation()
return func(self, *args, **kwargs)
return check_thread
class Face(object):
def __init__(self, face):
self.start_thread = threading.current_thread()
self.face = face
@same_thread
def supports_text(self, text):
if not isinstance(text, unicode):
raise TypeError('%r is not a unicode object'%text)
chars = tuple(frozenset(map(ord, text)))
return self.face.supports_text(chars)
class FreeType(object):
def __init__(self):
self.start_thread = threading.current_thread()
ft, ft_err = plugins['freetype']
if ft_err:
raise RuntimeError('Failed to load FreeType module with error: %s'
% ft_err)
self.ft = ft.FreeType()
@same_thread
def load_font(self, data):
return Face(self.ft.load_font(data))
def test():
data = P('fonts/calibreSymbols.otf', data=True)
ft = FreeType()
font = ft.load_font(data)
if not font.supports_text('.\u2605'):
raise RuntimeError('Incorrectly returning that text is not supported')
if font.supports_text('abc'):
raise RuntimeError('Incorrectly claiming that text is supported')
if __name__ == '__main__':
test()