Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add font_feature_settings #2248

Merged
merged 5 commits into from
Jan 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion kitty/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,19 @@ def handle_symbol_map(key, val, ans):
ans['symbol_map'].update(parse_symbol_map(val))


@special_handler
def handle_font_feature_settings(key, val, ans):
parts = val.split(':', maxsplit=2)
if len(parts) != 2:
if parts[0] == "none":
return
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()})


@special_handler
def handle_kitten_alias(key, val, ans):
parts = val.split(maxsplit=2)
Expand Down Expand Up @@ -495,7 +508,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': {}}
ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': [], 'env': {}, 'kitten_aliases': {}, 'font_feature_settings': {}}
parse_config_base(
lines,
defaults,
Expand Down
39 changes: 39 additions & 0 deletions kitty/config_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,45 @@ def disable_ligatures(x):
map alt+1 disable_ligatures_in active always
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=_('''
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
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
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
name::

$ fc-match "Fira Code" postscriptname
:postscriptname=FiraCode-Regular
$ fc-match "Fira Code Retina" postscriptname
:postscriptname=FiraCode-Retina
$ fc-match "TT2020Base:style=italic" postscriptname
:postscriptname=TT2020Base-Italic

Enable alternate zero and oldstyle numerals::

font_feature_settings FiraCode-Retina: +zero +onum

Enable only alternate zero::

font_feature_settings 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
'''))


Expand Down
45 changes: 34 additions & 11 deletions kitty/fonts.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ static hb_feature_t hb_features[3] = {{0}};
static char_type shape_buffer[4096] = {0};
static size_t max_texture_size = 1024, max_array_len = 1024;
typedef enum { LIGA_FEATURE, DLIG_FEATURE, CALT_FEATURE } HBFeature;
static PyObject* font_feature_settings = NULL;

typedef struct {
char_type left, right;
Expand All @@ -71,14 +72,14 @@ typedef struct {
static SymbolMap *symbol_maps = NULL;
static size_t num_symbol_maps = 0;



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;
} Font;
Expand Down Expand Up @@ -359,16 +360,34 @@ init_font(Font *f, PyObject *face, bool bold, bool italic, bool emoji_presentati
f->bold = bold; f->italic = italic; f->emoji_presentation = emoji_presentation;
f->num_hb_features = 0;
const char *psname = postscript_name_for_face(face);
if (strstr(psname, "NimbusMonoPS-") == psname) {
copy_hb_feature(f, LIGA_FEATURE); copy_hb_feature(f, DLIG_FEATURE);
}
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]);
}
for (size_t i = 0; i < f->num_hb_features; i++)
hb_feat[len+i] = hb_features[i];
f->ffs_hb_features = hb_feat;
}
}
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_maps(f);
f->bold = false; f->italic = false;
}
Expand Down Expand Up @@ -776,7 +795,10 @@ 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);

hb_shape(font, harfbuzz_buffer, fobj->hb_features, fobj->num_hb_features - (disable_ligature ? 0 : 1));
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));

unsigned int info_length, positions_length;
group_state.info = hb_buffer_get_glyph_infos(harfbuzz_buffer, &info_length);
Expand Down Expand Up @@ -1059,7 +1081,7 @@ render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, inde
default:
shape_run(first_cpu_cell, first_gpu_cell, num_cells, &fg->fonts[font_idx], disable_ligature_strategy == DISABLE_LIGATURES_ALWAYS);
if (pua_space_ligature) merge_groups_for_pua_space_ligature();
else if (cursor_offset > -1) {
else if (cursor_offset > -1) { // false if DISABLE_LIGATURES_NEVER
index_type left, right;
split_run_at_offset(cursor_offset, &left, &right);
if (right > left) {
Expand Down Expand Up @@ -1183,12 +1205,12 @@ DescriptorIndices descriptor_indices = {0};
static PyObject*
set_font_data(PyObject UNUSED *m, PyObject *args) {
PyObject *sm;
Py_CLEAR(box_drawing_function); Py_CLEAR(prerender_function); Py_CLEAR(descriptor_for_idx);
if (!PyArg_ParseTuple(args, "OOOIIIIO!d",
Py_CLEAR(box_drawing_function); Py_CLEAR(prerender_function); Py_CLEAR(descriptor_for_idx); Py_CLEAR(font_feature_settings);
if (!PyArg_ParseTuple(args, "OOOIIIIO!dO",
&box_drawing_function, &prerender_function, &descriptor_for_idx,
&descriptor_indices.bold, &descriptor_indices.italic, &descriptor_indices.bi, &descriptor_indices.num_symbol_fonts,
&PyTuple_Type, &sm, &global_state.font_sz_in_pts)) return NULL;
Py_INCREF(box_drawing_function); Py_INCREF(prerender_function); Py_INCREF(descriptor_for_idx);
&PyTuple_Type, &sm, &global_state.font_sz_in_pts, &font_feature_settings)) return NULL;
Py_INCREF(box_drawing_function); Py_INCREF(prerender_function); Py_INCREF(descriptor_for_idx); Py_INCREF(font_feature_settings);
ctrlcctrlv marked this conversation as resolved.
Show resolved Hide resolved
free_font_groups();
clear_symbol_maps();
num_symbol_maps = PyTuple_GET_SIZE(sm);
Expand Down Expand Up @@ -1290,6 +1312,7 @@ finalize(void) {
Py_CLEAR(box_drawing_function);
Py_CLEAR(prerender_function);
Py_CLEAR(descriptor_for_idx);
Py_CLEAR(font_feature_settings);
free_font_groups();
if (harfbuzz_buffer) { hb_buffer_destroy(harfbuzz_buffer); harfbuzz_buffer = NULL; }
free(group_state.groups); group_state.groups = NULL; group_state.groups_capacity = 0;
Expand Down
2 changes: 1 addition & 1 deletion kitty/fonts/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
sm, sz, opts['font_feature_settings']
)


Expand Down