diff --git a/kitty/fontconfig.c b/kitty/fontconfig.c index 783ad9339..ff70a5512 100644 --- a/kitty/fontconfig.c +++ b/kitty/fontconfig.c @@ -8,6 +8,97 @@ #include "data-types.h" #include +static inline PyObject* +pybool(FcBool x) { PyObject *ans = x ? Py_True: Py_False; Py_INCREF(ans); return ans; } + +static inline PyObject* +pyspacing(int val) { +#define S(x) case FC_##x: return PyUnicode_FromString(#x) + switch(val) { S(PROPORTIONAL); S(DUAL); S(MONO); S(CHARCELL); default: return PyUnicode_FromString("UNKNOWN"); } +#undef S +} + +static inline PyObject* +pattern_as_dict(FcPattern *pat) { + PyObject *ans = PyDict_New(); + if (ans == NULL) return NULL; +#define PS(x) PyUnicode_FromString((char*)x) +#define G(type, get, which, conv, name) { \ + type out; PyObject *p; \ + if (get(pat, which, 0, &out) == FcResultMatch) { p = conv(out); if (p == NULL) { Py_CLEAR(ans); return NULL; } \ + } else { p = Py_None; Py_INCREF(p); } \ + if (PyDict_SetItemString(ans, #name, p) != 0) { Py_CLEAR(p); Py_CLEAR(ans); return NULL; } \ + Py_CLEAR(p); \ + } +#define S(which, key) G(FcChar8*, FcPatternGetString, which, PS, key) +#define I(which, key) G(int, FcPatternGetInteger, which, PyLong_FromLong, key) +#define B(which, key) G(int, FcPatternGetInteger, which, pybool, key) +#define E(which, key, conv) G(int, FcPatternGetInteger, which, conv, key) + S(FC_FILE, path); + S(FC_FAMILY, family); + S(FC_STYLE, style); + S(FC_FULLNAME, fullname); + S(FC_POSTSCRIPT_NAME, psname); + I(FC_WEIGHT, weight); + I(FC_SLANT, slant); + I(FC_HINT_STYLE, hint_style); + I(FC_INDEX, index); + B(FC_HINTING, hinting); + B(FC_SCALABLE, scalable); + B(FC_OUTLINE, outline); + B(FC_COLOR, color); + E(FC_SPACING, spacing, pyspacing); + + return ans; +#undef PS +#undef S +#undef I +#undef B +#undef E +#undef G +} + +static inline PyObject* +font_set(FcFontSet *fs) { + PyObject *ans = PyTuple_New(fs->nfont); + if (ans == NULL) return NULL; + for (int i = 0; i < fs->nfont; i++) { + PyObject *d = pattern_as_dict(fs->fonts[i]); + if (d == NULL) { Py_CLEAR(ans); break; } + PyTuple_SET_ITEM(ans, i, d); + } + return ans; +} + +static PyObject* +list_fontconfig_fonts(PyObject UNUSED *self, PyObject *args) { + int allow_bitmapped_fonts = 0, only_monospaced_fonts = 1; + PyObject *ans = NULL; + FcObjectSet *os = NULL; + FcPattern *pat = NULL; + FcFontSet *fs = NULL; + if (!PyArg_ParseTuple(args, "|pp", &only_monospaced_fonts, &allow_bitmapped_fonts)) return NULL; + pat = FcPatternCreate(); + if (pat == NULL) return PyErr_NoMemory(); +#define AP(func, which, in, desc) if (!func(pat, which, in)) { PyErr_Format(PyExc_ValueError, "Failed to add %s to fontconfig pattern", desc, NULL); goto end; } + if (!allow_bitmapped_fonts) { + AP(FcPatternAddBool, FC_OUTLINE, true, "outline"); + AP(FcPatternAddBool, FC_SCALABLE, true, "scalable"); + } + if (only_monospaced_fonts) AP(FcPatternAddInteger, FC_SPACING, FC_MONO, "spacing"); +#undef AP + os = FcObjectSetBuild(FC_FILE, FC_POSTSCRIPT_NAME, FC_FAMILY, FC_STYLE, FC_FULLNAME, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_HINT_STYLE, FC_INDEX, FC_HINTING, FC_SCALABLE, FC_OUTLINE, FC_COLOR, FC_SPACING, NULL); + if (!os) { PyErr_SetString(PyExc_ValueError, "Failed to create fontconfig object set"); goto end; } + fs = FcFontList(NULL, pat, os); + if (!fs) { PyErr_SetString(PyExc_ValueError, "Failed to create fontconfig font set"); goto end; } + ans = font_set(fs); +end: + if (pat != NULL) FcPatternDestroy(pat); + if (os != NULL) FcObjectSetDestroy(os); + if (fs != NULL) FcFontSetDestroy(fs); + return ans; +} + static PyObject* get_fontconfig_font(PyObject UNUSED *self, PyObject *args) { char *family; @@ -82,7 +173,8 @@ end: } static PyMethodDef module_methods[] = { - {"get_fontconfig_font", (PyCFunction)get_fontconfig_font, METH_VARARGS, ""}, + METHODB(list_fontconfig_fonts, METH_VARARGS), + METHODB(get_fontconfig_font, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ };