diff --git a/docs/changelog.rst b/docs/changelog.rst index 46fb3458e..5bb739b7a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,13 @@ Changelog |kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator. To update |kitty|, :doc:`follow the instructions `. +0.15.12 [future] +-------------------- + +- Allow choosing OpenType features for individual fonts via the + :opt:`font_features` option. + + 0.15.1 [2019-12-21] -------------------- diff --git a/kitty/config.py b/kitty/config.py index 131265ca6..13396f4e8 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -411,16 +411,13 @@ def handle_symbol_map(key, val, ans): @special_handler -def handle_font_feature_settings(key, val, ans): - parts = val.split(':', maxsplit=2) - if len(parts) != 2: - if parts[0] == "none": - return +def handle_font_features(key, val, ans): + if val != 'none': + parts = val.split() + if len(parts) < 2: + log_error("Ignoring invalid font_features {}".format(val)) else: - log_error("Ignoring invalid font_feature_settings for font {}".format(parts[0])) - else: - font, features = parts - ans['font_feature_settings'].update({font.strip(): features.strip().split()}) + ans['font_features'][parts[0]] = parts[1:] @special_handler @@ -508,7 +505,7 @@ def option_names_for_completion(): def parse_config(lines, check_keys=True, accumulate_bad_lines=None): - ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': [], 'env': {}, 'kitten_aliases': {}, 'font_feature_settings': {}} + ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': [], 'env': {}, 'kitten_aliases': {}, 'font_features': {}} parse_config_base( lines, defaults, diff --git a/kitty/config_data.py b/kitty/config_data.py index 48d6df8eb..76c79cd76 100644 --- a/kitty/config_data.py +++ b/kitty/config_data.py @@ -277,23 +277,21 @@ or by defining shortcuts for it in kitty.conf, for example:: map alt+2 disable_ligatures_in all never map alt+3 disable_ligatures_in tab cursor -Note: This function is equivalent to setting :code:`font_feature_settings` to -:code:`-liga -dlig -calt`. ''')) -o('font_feature_settings', 'none', long_text=_(''' +o('font_features', 'none', long_text=_(''' Choose exactly which OpenType features to enable or disable. This is useful as -some fonts might have many features worthwhile in a terminal—for example, Fira +some fonts might have features worthwhile in a terminal. For example, Fira Code Retina includes a discretionary feature, :code:`zero`, which in that font changes the appearance of the zero (0), to make it more easily distinguishable from Ø. Fira Code Retina also includes other discretionary features known as Stylistic Sets which have the tags :code:`ss01` through :code:`ss20`. -Note that this code is indexed by PostScript name, and not TTF name or font -family; this allows you to define very precise feature settings; e.g. you can +Note that this code is indexed by PostScript name, and not the font +family. This allows you to define very precise feature settings; e.g. you can disable a feature in the italic font but not in the regular font. -To get the PostScript name for a font, ask Fontconfig for it, using your family +To get the PostScript name for a font, ask Fontconfig for it, using the family name:: $ fc-match "Fira Code" postscriptname @@ -305,16 +303,16 @@ name:: Enable alternate zero and oldstyle numerals:: - font_feature_settings FiraCode-Retina: +zero +onum + font_features FiraCode-Retina +zero +onum Enable only alternate zero:: - font_feature_settings FiraCode-Retina: +zero + font_features FiraCode-Retina +zero Disable the normal ligatures, but keep the :code:`calt` feature which (in this font) breaks up monotony:: - font_feature_settings TT2020StyleB-Regular: -liga +calt + font_features TT2020StyleB-Regular -liga +calt ''')) diff --git a/kitty/fonts.c b/kitty/fonts.c index 9deffa669..f4a910c42 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -76,9 +76,7 @@ typedef struct { PyObject *face; // Map glyphs to sprite map co-ords SpritePosition sprite_map[1024]; - hb_feature_t hb_features[8]; hb_feature_t* ffs_hb_features; - size_t num_hb_features; size_t num_ffs_hb_features; SpecialGlyphCache special_glyph_cache[SPECIAL_GLYPH_CACHE_SIZE]; bool bold, italic, emoji_presentation; @@ -349,45 +347,47 @@ desc_to_face(PyObject *desc, FONTS_DATA_HANDLE fg) { return ans; } -static inline void -copy_hb_feature(Font *f, HBFeature which) { - memcpy(f->hb_features + f->num_hb_features++, hb_features + which, sizeof(hb_features[0])); -} - static inline bool init_font(Font *f, PyObject *face, bool bold, bool italic, bool emoji_presentation) { f->face = face; Py_INCREF(f->face); f->bold = bold; f->italic = italic; f->emoji_presentation = emoji_presentation; - f->num_hb_features = 0; + f->num_ffs_hb_features = 0; const char *psname = postscript_name_for_face(face); - copy_hb_feature(f, LIGA_FEATURE); - copy_hb_feature(f, DLIG_FEATURE); - copy_hb_feature(f, CALT_FEATURE); if (font_feature_settings != NULL){ PyObject* o = PyDict_GetItemString(font_feature_settings, psname); if (o != NULL) { - long len = PyList_Size(o); - if (len==0) return true; - f->num_ffs_hb_features = len + f->num_hb_features; - hb_feature_t* hb_feat = calloc(f->num_ffs_hb_features, sizeof(hb_feature_t)); - for (long i = len-1; i >= 0; i--) { - PyObject* item = PyList_GetItem(o, i); - const char* feat = PyUnicode_AsUTF8(item); - hb_feature_from_string(feat, -1, &hb_feat[i]); + Py_ssize_t len = PySequence_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)); + } + } + memcpy(f->ffs_hb_features + len, &hb_features[CALT_FEATURE], sizeof(hb_feature_t)); } - for (size_t i = 0; i < f->num_hb_features; i++) - hb_feat[len+i] = hb_features[i]; - f->ffs_hb_features = hb_feat; } } + if (!f->num_ffs_hb_features) { + f->ffs_hb_features = calloc(4, sizeof(hb_feature_t)); + if (!f->ffs_hb_features) return false; + if (strstr(psname, "NimbusMonoPS-") == psname) { + memcpy(f->ffs_hb_features + f->num_ffs_hb_features++, &hb_features[LIGA_FEATURE], sizeof(hb_feature_t)); + memcpy(f->ffs_hb_features + f->num_ffs_hb_features++, &hb_features[DLIG_FEATURE], sizeof(hb_feature_t)); + } + memcpy(f->ffs_hb_features + f->num_ffs_hb_features++, &hb_features[CALT_FEATURE], sizeof(hb_feature_t)); + } return true; } static inline void del_font(Font *f) { Py_CLEAR(f->face); - if (f->num_ffs_hb_features > 0) - free(f->ffs_hb_features); + free(f->ffs_hb_features); f->ffs_hb_features = NULL; free_maps(f); f->bold = false; f->italic = false; } @@ -795,10 +795,9 @@ shape(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, hb group_state.last_gpu_cell = first_gpu_cell + (num_cells ? num_cells - 1 : 0); load_hb_buffer(first_cpu_cell, first_gpu_cell, num_cells); - if (fobj->num_ffs_hb_features > 0) - hb_shape(font, harfbuzz_buffer, fobj->ffs_hb_features, fobj->num_ffs_hb_features - (disable_ligature ? 0 : fobj->num_hb_features)); - else - hb_shape(font, harfbuzz_buffer, fobj->hb_features, fobj->num_hb_features - (disable_ligature ? 0 : fobj->num_hb_features)); + size_t num_features = fobj->num_ffs_hb_features; + if (num_features && !disable_ligature) num_features--; // the last feature is always -calt + hb_shape(font, harfbuzz_buffer, fobj->ffs_hb_features, num_features); unsigned int info_length, positions_length; group_state.info = hb_buffer_get_glyph_infos(harfbuzz_buffer, &info_length); @@ -1049,8 +1048,6 @@ test_shape(PyObject UNUSED *self, PyObject *args) { if (face == NULL) return NULL; font = calloc(1, sizeof(Font)); font->face = face; - font->hb_features[0] = hb_features[CALT_FEATURE]; - font->num_hb_features = 1; } else { FontGroup *fg = font_groups; font = fg->fonts + fg->medium_font_idx; diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index 6b0527346..ae571a49b 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -103,7 +103,7 @@ def set_font_family(opts=None, override_font_size=None, debug_font_matching=Fals set_font_data( render_box_drawing, prerender_function, descriptor_for_idx, indices['bold'], indices['italic'], indices['bi'], num_symbol_fonts, - sm, sz, opts['font_feature_settings'] + sm, sz, opts.font_features )