diff --git a/session.vim b/session.vim index 6b8878b84d..c60bcbdf4f 100644 --- a/session.vim +++ b/session.vim @@ -8,6 +8,7 @@ let g:syntastic_cpp_include_dirs = [ \'/usr/include/qt4/QtCore', \'/usr/include/qt4/QtGui', \'/usr/include/qt4', + \'/usr/include/freetype2', \'src/qtcurve/common', 'src/qtcurve', \'/usr/include/ImageMagick', \] diff --git a/setup/build_environment.py b/setup/build_environment.py index 58d2006ee7..69b1dc231b 100644 --- a/setup/build_environment.py +++ b/setup/build_environment.py @@ -84,6 +84,7 @@ qt_inc = pyqt.qt_inc_dir qt_lib = pyqt.qt_lib_dir ft_lib_dirs = [] ft_libs = [] +ft_inc_dirs = [] jpg_libs = [] jpg_lib_dirs = [] fc_inc = '/usr/include/fontconfig' @@ -116,6 +117,7 @@ if iswindows: jpg_libs = ['jpeg'] ft_lib_dirs = [sw_lib_dir] ft_libs = ['freetype'] + ft_inc_dirs = [sw_inc_dir] magick_inc_dirs = [os.path.join(prefix, 'build', 'ImageMagick-6.7.6')] 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_lib_dirs = consolidate('PNG_LIB_DIR', '/sw/lib') png_libs = ['png12'] + ft_libs = ['freetype'] + ft_inc_dirs = ['/sw/include/freetype2'] else: # Include directories png_inc_dirs = pkgconfig_include_dirs('libpng', 'PNG_INC_DIR', @@ -150,6 +154,10 @@ else: if not magick_libs: magick_libs = ['MagickWand', 'MagickCore'] 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) diff --git a/setup/extensions.py b/setup/extensions.py index 1827d32f4a..2cae3d857f 100644 --- a/setup/extensions.py +++ b/setup/extensions.py @@ -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, 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, - icu_lib_dirs, win_ddk_lib_dirs) + icu_lib_dirs, win_ddk_lib_dirs, ft_libs, ft_lib_dirs, ft_inc_dirs) MT isunix = islinux or isosx or isbsd @@ -119,6 +119,12 @@ extensions = [ 'calibre/utils/lzx/mspack.h'], 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', ['calibre/utils/fonts/fontconfig.c'], inc_dirs = [fc_inc], diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 35b03f2b2f..fe3c40309e 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -89,6 +89,7 @@ class Plugins(collections.Mapping): 'chm_extra', 'icu', 'speedup', + 'freetype', ] if iswindows: plugins.extend(['winutil', 'wpd', 'winfonts']) diff --git a/src/calibre/test_build.py b/src/calibre/test_build.py index 35e050cf19..e6e8611ac5 100644 --- a/src/calibre/test_build.py +++ b/src/calibre/test_build.py @@ -32,6 +32,11 @@ def test_lxml(): else: raise RuntimeError('lxml failed') +def test_freetype(): + from calibre.utils.fonts.freetype import test + test() + print ('FreeType OK!') + def test_fontconfig(): from calibre.utils.fonts import fontconfig families = fontconfig.find_font_families() @@ -103,7 +108,7 @@ def test_icu(): def test_wpd(): wpd = plugins['wpd'][0] try: - wpd.init() + wpd.init('calibre', 1, 1, 1) except wpd.NoWPD: print ('This computer does not have WPD') else: @@ -112,6 +117,7 @@ def test_wpd(): def test(): test_plugins() test_lxml() + test_freetype() test_fontconfig() test_sqlite() test_qt() diff --git a/src/calibre/utils/fonts/__init__.py b/src/calibre/utils/fonts/__init__.py index a5563acd4e..45a665b75a 100644 --- a/src/calibre/utils/fonts/__init__.py +++ b/src/calibre/utils/fonts/__init__.py @@ -55,7 +55,7 @@ class Fonts(object): files = self.backend.files_for_family(family, normalize=normalize) ans = {} for ft, val in files.iteritems(): - name, f = val + f, name = val ext = f.rpartition('.')[-1].lower() ans[ft] = (ext, name, open(f, 'rb').read()) return ans diff --git a/src/calibre/utils/fonts/freetype.cpp b/src/calibre/utils/fonts/freetype.cpp new file mode 100644 index 0000000000..a5529b3200 --- /dev/null +++ b/src/calibre/utils/fonts/freetype.cpp @@ -0,0 +1,289 @@ +/* + * freetype.cpp + * Copyright (C) 2012 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#define _UNICODE +#define UNICODE +#define PY_SSIZE_T_CLEAN +#include + +#include +#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); +} + diff --git a/src/calibre/utils/fonts/freetype.py b/src/calibre/utils/fonts/freetype.py new file mode 100644 index 0000000000..41e9592971 --- /dev/null +++ b/src/calibre/utils/fonts/freetype.py @@ -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 ' +__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() +