diff --git a/kitty/config.py b/kitty/config.py index 13396f4e8..91c27c775 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -410,6 +410,14 @@ def handle_symbol_map(key, val, ans): ans['symbol_map'].update(parse_symbol_map(val)) +class FontFeature(str): + + def __new__(cls, name, parsed): + ans = str.__new__(cls, name) + ans.parsed = parsed + return ans + + @special_handler def handle_font_features(key, val, ans): if val != 'none': @@ -417,7 +425,16 @@ def handle_font_features(key, val, ans): if len(parts) < 2: log_error("Ignoring invalid font_features {}".format(val)) else: - ans['font_features'][parts[0]] = parts[1:] + features = [] + for feat in parts[1:]: + try: + parsed = defines.parse_font_feature(feat) + except ValueError: + log_error('Ignoring invalid font feature: {}'.format(feat)) + else: + features.append(FontFeature(feat, parsed)) + if features: + ans['font_features'][parts[0]] = tuple(features) @special_handler diff --git a/kitty/fonts.c b/kitty/fonts.c index f4a910c42..d8dee7a1c 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -355,17 +355,17 @@ init_font(Font *f, PyObject *face, bool bold, bool italic, bool emoji_presentati const char *psname = postscript_name_for_face(face); if (font_feature_settings != NULL){ PyObject* o = PyDict_GetItemString(font_feature_settings, psname); - if (o != NULL) { - Py_ssize_t len = PySequence_Size(o); + if (o != NULL && PyTuple_Check(o)) { + Py_ssize_t len = PyTuple_GET_SIZE(o); if (len > 0) { f->num_ffs_hb_features = len + 1; f->ffs_hb_features = calloc(f->num_ffs_hb_features, sizeof(hb_feature_t)); if (!f->ffs_hb_features) return false; for (Py_ssize_t i = 0; i < len; i++) { - PyObject* item = PySequence_GetItem(o, i); - if (!PyUnicode_Check(item)) fatal("A font feature is not a unicode string"); - if (!hb_feature_from_string(PyUnicode_AsUTF8(item), -1, &f->ffs_hb_features[i])) { - fatal("Invalid font feature: %s", PyUnicode_AsUTF8(item)); + PyObject* parsed = PyObject_GetAttrString(PyTuple_GET_ITEM(o, i), "parsed"); + if (parsed) { + memcpy(f->ffs_hb_features + i, PyBytes_AS_STRING(parsed), sizeof(hb_feature_t)); + Py_DECREF(parsed); } } memcpy(f->ffs_hb_features + len, &hb_features[CALT_FEATURE], sizeof(hb_feature_t)); @@ -1455,9 +1455,26 @@ free_font_data(PyObject *self UNUSED, PyObject *args UNUSED) { Py_RETURN_NONE; } +static PyObject* +parse_font_feature(PyObject *self UNUSED, PyObject *feature) { + if (!PyUnicode_Check(feature)) { + PyErr_SetString(PyExc_TypeError, "feature must be a unicode object"); + return NULL; + } + PyObject *ans = PyBytes_FromStringAndSize(NULL, sizeof(hb_feature_t)); + if (!ans) return NULL; + if (!hb_feature_from_string(PyUnicode_AsUTF8(feature), -1, (hb_feature_t*)PyBytes_AS_STRING(ans))) { + Py_CLEAR(ans); + PyErr_Format(PyExc_ValueError, "%U is not a valid font feature", feature); + return NULL; + } + return ans; +} + static PyMethodDef module_methods[] = { METHODB(set_font_data, METH_VARARGS), METHODB(free_font_data, METH_NOARGS), + METHODB(parse_font_feature, METH_O), METHODB(create_test_font_group, METH_VARARGS), METHODB(sprite_map_set_layout, METH_VARARGS), METHODB(test_sprite_position_for, METH_VARARGS),