From 488b82b6ff10e01ee8c19a5c466eb3e2c10d0c35 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 28 Oct 2012 10:25:58 +0530 Subject: [PATCH] Add CSS3 Fonts support to the fontconfig plugin --- src/calibre/utils/fonts/fc.py | 79 +++++++++++++++++++++++++--- src/calibre/utils/fonts/fontconfig.c | 77 ++++++++++++++++++--------- 2 files changed, 123 insertions(+), 33 deletions(-) diff --git a/src/calibre/utils/fonts/fc.py b/src/calibre/utils/fonts/fc.py index 73800d1193..3e6ae844bc 100644 --- a/src/calibre/utils/fonts/fc.py +++ b/src/calibre/utils/fonts/fc.py @@ -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)) diff --git a/src/calibre/utils/fonts/fontconfig.c b/src/calibre/utils/fonts/fontconfig.c index be1cbdce13..b66aba4b08 100644 --- a/src/calibre/utils/fonts/fontconfig.c +++ b/src/calibre/utils/fonts/fontconfig.c @@ -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); + +# }