mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Start a python interface to FreeType
This commit is contained in:
parent
ade7c9280a
commit
42f2f945e7
@ -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',
|
||||
\]
|
||||
|
@ -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)
|
||||
|
@ -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],
|
||||
|
@ -89,6 +89,7 @@ class Plugins(collections.Mapping):
|
||||
'chm_extra',
|
||||
'icu',
|
||||
'speedup',
|
||||
'freetype',
|
||||
]
|
||||
if iswindows:
|
||||
plugins.extend(['winutil', 'wpd', 'winfonts'])
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
289
src/calibre/utils/fonts/freetype.cpp
Normal file
289
src/calibre/utils/fonts/freetype.cpp
Normal 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);
|
||||
}
|
||||
|
69
src/calibre/utils/fonts/freetype.py
Normal file
69
src/calibre/utils/fonts/freetype.py
Normal 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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user