Add CSS3 Fonts support to the fontconfig plugin

This commit is contained in:
Kovid Goyal 2012-10-28 10:25:58 +05:30
parent 6deb138320
commit 488b82b6ff
2 changed files with 123 additions and 33 deletions

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
import os, sys
from calibre.constants import plugins, islinux, isbsd, isosx
from calibre.constants import plugins, islinux, isbsd, isosx, preferred_encoding
_fc, _fc_err = plugins['fontconfig']
@ -26,6 +26,33 @@ class FontConfig(Thread):
Thread.__init__(self)
self.daemon = True
self.failed = False
self.css_weight_map = {
_fc.FC_WEIGHT_THIN : u'100',
_fc.FC_WEIGHT_EXTRALIGHT : u'200', _fc.FC_WEIGHT_ULTRALIGHT : u'200',
_fc.FC_WEIGHT_LIGHT : u'300',
_fc.FC_WEIGHT_BOOK : u'normal', _fc.FC_WEIGHT_BOOK : u'normal', _fc.FC_WEIGHT_REGULAR : u'normal',
_fc.FC_WEIGHT_MEDIUM : u'500',
_fc.FC_WEIGHT_DEMIBOLD : u'600', _fc.FC_WEIGHT_SEMIBOLD : u'600',
_fc.FC_WEIGHT_BOLD : u'bold',
_fc.FC_WEIGHT_EXTRABOLD : u'800', _fc.FC_WEIGHT_ULTRABOLD : u'800',
_fc.FC_WEIGHT_HEAVY : u'900', _fc.FC_WEIGHT_BLACK : u'900', _fc.FC_WEIGHT_EXTRABLACK : u'900', _fc.FC_WEIGHT_ULTRABLACK : u'900'
}
self.css_slant_map = {
_fc.FC_SLANT_ROMAN : u'normal',
_fc.FC_SLANT_ITALIC : u'italic',
_fc.FC_SLANT_OBLIQUE : u'oblique'
}
self.css_stretch_map = {
_fc.FC_WIDTH_ULTRACONDENSED: u'ultra-condensed',
_fc.FC_WIDTH_EXTRACONDENSED : u'extra-condensed',
_fc.FC_WIDTH_CONDENSED: u'condensed',
_fc.FC_WIDTH_SEMICONDENSED: u'semi-condensed',
_fc.FC_WIDTH_NORMAL: u'normal',
_fc.FC_WIDTH_SEMIEXPANDED: u'semi-expanded',
_fc.FC_WIDTH_EXPANDED: u'expanded',
_fc.FC_WIDTH_EXTRAEXPANDED: u'extra-expanded',
_fc.FC_WIDTH_ULTRAEXPANDED: u'ultra-expanded',
}
def run(self):
config = None
@ -83,12 +110,13 @@ class FontConfig(Thread):
`('normal', 'bold', 'italic', 'bi', 'light', 'li')`
'''
self.wait()
if isinstance(family, unicode):
family = family.encode('utf-8')
if not isinstance(family, unicode):
family = family.decode(preferred_encoding)
fonts = {}
ofamily = str(family).decode('utf-8')
for fullname, path, style, nfamily, weight, slant in \
_fc.files_for_family(str(family)):
for entry in _fc.files_for_family(family):
slant, weight = entry['slant'], entry['weight']
fullname, path = entry['fullname'], entry['path']
nfamily = entry['family']
style = (slant, weight)
if normalize:
italic = slant > 0
@ -104,7 +132,7 @@ class FontConfig(Thread):
except UnicodeDecodeError:
continue
if style in fonts:
if nfamily.lower().strip() == ofamily.lower().strip() \
if nfamily.lower().strip() == family.lower().strip() \
and 'Condensed' not in fullname and 'ExtraLight' not in fullname:
fonts[style] = (path, fullname)
else:
@ -112,6 +140,41 @@ class FontConfig(Thread):
return fonts
def faces_for_family(self, family):
'''
Return all the faces in a font family in a manner suitable for CSS 3.
'''
self.wait()
if not isinstance(family, unicode):
family = family.decode(preferred_encoding)
seen = set()
for entry in _fc.files_for_family(family):
slant, weight = entry['slant'], entry['weight']
fullname, path = entry['fullname'], entry['path']
nfamily, width = entry['family'], entry['width']
fingerprint = (slant, weight, width, nfamily)
if fingerprint in seen:
# Fontconfig returns the same font multiple times if it is
# present in multiple locations
continue
seen.add(fingerprint)
try:
nfamily = nfamily.decode('UTF-8')
fullname = fullname.decode('utf-8')
path = path.decode('utf-8')
except UnicodeDecodeError:
continue
face = {
'font-weight': self.css_weight_map[weight],
'font-style': self.css_slant_map[slant],
'font-stretch': self.css_stretch_map[width],
'font-family': nfamily,
'fullname': fullname, 'path':path
}
yield face
def match(self, name, all=False, verbose=False):
'''
Find the system font that most closely matches `name`, where `name` is a specification
@ -162,7 +225,7 @@ else:
def test():
from pprint import pprint;
pprint(fontconfig.find_font_families())
pprint(fontconfig.files_for_family('liberation serif'))
pprint(tuple(fontconfig.faces_for_family('liberation serif')))
m = 'liberation serif'
pprint(fontconfig.match(m+':slant=italic:weight=bold', verbose=True))

View File

@ -141,10 +141,10 @@ fontconfig_files_for_family(PyObject *self, PyObject *args) {
FcPattern *pat, *tp;
FcObjectSet *oset;
FcFontSet *fs;
FcValue file, weight, fullname, style, slant, family2;
PyObject *ans, *temp, *t;
FcValue file, weight, fullname, style, slant, family2, width;
PyObject *ans, *temp;
if (!PyArg_ParseTuple(args, "s", &family))
if (!PyArg_ParseTuple(args, "es", "UTF-8", &family))
return NULL;
ans = PyList_New(0);
@ -154,6 +154,7 @@ fontconfig_files_for_family(PyObject *self, PyObject *args) {
pat = FcPatternBuild(0, FC_FAMILY, FcTypeString, family, (char *) 0);
if (pat == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); }
PyMem_Free(family); family = NULL;
oset = FcObjectSetCreate();
if (oset == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); }
@ -161,8 +162,9 @@ fontconfig_files_for_family(PyObject *self, PyObject *args) {
if (!FcObjectSetAdd(oset, FC_STYLE)) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); }
if (!FcObjectSetAdd(oset, FC_SLANT)) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); }
if (!FcObjectSetAdd(oset, FC_WEIGHT)) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); }
if (!FcObjectSetAdd(oset, FC_WIDTH)) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); }
if (!FcObjectSetAdd(oset, FC_FAMILY)) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); }
if (!FcObjectSetAdd(oset, "fullname")) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); }
if (!FcObjectSetAdd(oset, FC_FULLNAME)) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); }
fs = FcFontList(FcConfigGetCurrent(), pat, oset);
if (fs == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); }
@ -174,30 +176,21 @@ fontconfig_files_for_family(PyObject *self, PyObject *args) {
if (FcPatternGet(tp, FC_FILE, 0, &file) != FcResultMatch) continue;
if (FcPatternGet(tp, FC_STYLE, 0, &style) != FcResultMatch) continue;
if (FcPatternGet(tp, FC_WEIGHT, 0, &weight) != FcResultMatch) continue;
if (FcPatternGet(tp, FC_WIDTH, 0, &width) != FcResultMatch) continue;
if (FcPatternGet(tp, FC_SLANT, 0, &slant) != FcResultMatch) continue;
if (FcPatternGet(tp, FC_FAMILY, 0, &family2) != FcResultMatch) continue;
if (FcPatternGet(tp, "fullname", 0, &fullname) != FcResultMatch) continue;
if (FcPatternGet(tp, FC_FULLNAME, 0, &fullname) != FcResultMatch) continue;
temp = PyTuple_New(6);
if(temp == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); }
t = PyBytes_FromString((char *)fullname.u.s);
if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); }
PyTuple_SET_ITEM(temp, 0, t);
t = PyBytes_FromString((char *)file.u.s);
if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); }
PyTuple_SET_ITEM(temp, 1, t);
t = PyBytes_FromString((char *)style.u.s);
if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); }
PyTuple_SET_ITEM(temp, 2, t);
t = PyBytes_FromString((char *)family2.u.s);
if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); }
PyTuple_SET_ITEM(temp, 3, t);
t = PyInt_FromLong((long)weight.u.i);
if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); }
PyTuple_SET_ITEM(temp, 4, t);
t = PyInt_FromLong((long)slant.u.i);
if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); }
PyTuple_SET_ITEM(temp, 5, t);
temp = Py_BuildValue("{s:s, s:s, s:s, s:s, s:l, s:l, s:l}",
"fullname", (char*)fullname.u.s,
"path", (char*)file.u.s,
"style", (char*)style.u.s,
"family", (char*)family2.u.s,
"weight", (long)weight.u.i,
"slant", (long)slant.u.i,
"width", (long)width.u.i
);
if (temp == NULL) { fontconfig_cleanup_find(pat, oset, fs); return NULL; }
if (PyList_Append(ans, temp) != 0)
{ fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); }
}
@ -343,5 +336,39 @@ initfontconfig(void) {
"Find fonts."
);
if (m == NULL) return;
PyModule_AddIntMacro(m, FC_WEIGHT_THIN);
PyModule_AddIntMacro(m, FC_WEIGHT_EXTRALIGHT);
PyModule_AddIntMacro(m, FC_WEIGHT_ULTRALIGHT);
PyModule_AddIntMacro(m, FC_WEIGHT_LIGHT);
PyModule_AddIntMacro(m, FC_WEIGHT_BOOK);
PyModule_AddIntMacro(m, FC_WEIGHT_REGULAR);
PyModule_AddIntMacro(m, FC_WEIGHT_NORMAL);
PyModule_AddIntMacro(m, FC_WEIGHT_MEDIUM);
PyModule_AddIntMacro(m, FC_WEIGHT_DEMIBOLD);
PyModule_AddIntMacro(m, FC_WEIGHT_SEMIBOLD);
PyModule_AddIntMacro(m, FC_WEIGHT_BOLD);
PyModule_AddIntMacro(m, FC_WEIGHT_EXTRABOLD);
PyModule_AddIntMacro(m, FC_WEIGHT_ULTRABOLD);
PyModule_AddIntMacro(m, FC_WEIGHT_BLACK);
PyModule_AddIntMacro(m, FC_WEIGHT_HEAVY);
PyModule_AddIntMacro(m, FC_WEIGHT_EXTRABLACK);
PyModule_AddIntMacro(m, FC_WEIGHT_ULTRABLACK);
PyModule_AddIntMacro(m, FC_SLANT_ROMAN);
PyModule_AddIntMacro(m, FC_SLANT_ITALIC);
PyModule_AddIntMacro(m, FC_SLANT_OBLIQUE);
PyModule_AddIntMacro(m, FC_WIDTH_ULTRACONDENSED);
PyModule_AddIntMacro(m, FC_WIDTH_EXTRACONDENSED);
PyModule_AddIntMacro(m, FC_WIDTH_CONDENSED);
PyModule_AddIntMacro(m, FC_WIDTH_SEMICONDENSED);
PyModule_AddIntMacro(m, FC_WIDTH_NORMAL);
PyModule_AddIntMacro(m, FC_WIDTH_SEMIEXPANDED);
PyModule_AddIntMacro(m, FC_WIDTH_EXPANDED);
PyModule_AddIntMacro(m, FC_WIDTH_EXTRAEXPANDED);
PyModule_AddIntMacro(m, FC_WIDTH_ULTRAEXPANDED);
#
}