From a69d71d416f13b36c0783a450204da6751426e4a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 27 May 2024 12:47:09 +0530 Subject: [PATCH] Render font feature list in UI --- kittens/choose_fonts/backend.py | 1 + kittens/choose_fonts/face.go | 34 +++++++++++++++++++++++++++++++++ kittens/choose_fonts/types.go | 1 + kitty/core_text.m | 11 +---------- kitty/fast_data_types.pyi | 4 ++-- kitty/fonts.c | 26 +++++++++++++++++++++++++ kitty/fonts.h | 2 ++ kitty/fonts/features.py | 4 ++-- kitty/freetype.c | 11 +---------- kitty_tests/fonts.py | 8 ++++---- 10 files changed, 74 insertions(+), 28 deletions(-) diff --git a/kittens/choose_fonts/backend.py b/kittens/choose_fonts/backend.py index f1b77f554..0a2ec78a1 100644 --- a/kittens/choose_fonts/backend.py +++ b/kittens/choose_fonts/backend.py @@ -127,6 +127,7 @@ def render_face_sample(font: Descriptor, opts: Options, dpi_x: float, dpi_y: flo 'style': font['style'], 'psname': face.postscript_name(), 'features': get_features(face.get_features()), + 'applied_features': face.applied_features(), } if is_variable(font): ns = get_named_style(face) diff --git a/kittens/choose_fonts/face.go b/kittens/choose_fonts/face.go index 937377bfb..c9263aa2d 100644 --- a/kittens/choose_fonts/face.go +++ b/kittens/choose_fonts/face.go @@ -125,6 +125,37 @@ func (self *face_panel) draw_family_style_select(_ loop.ScreenSize, start_y int, return y, nil } +func (self *face_panel) draw_font_features(_ loop.ScreenSize, start_y int, preview RenderedSampleTransmit) (y int, err error) { + lp := self.handler.lp + y = start_y + if len(preview.Features) == 0 { + return + } + formatted := make([]string, 0, len(preview.Features)) + for feat_tag, data := range preview.Features { + var text string + if preview.Applied_features[feat_tag] != "" { + text = preview.Applied_features[feat_tag] + text = strings.Replace(text, "+", lp.SprintStyled("fg=green", "+"), 1) + text = strings.Replace(text, "-", lp.SprintStyled("fg=red", "-"), 1) + text = strings.Replace(text, "=", lp.SprintStyled("fg=cyan", "="), 1) + if data.Name != "" { + text = fmt.Sprintf("%s: %s", data.Name, text) + } + } else { + text = utils.IfElse(data.Name == "", feat_tag, data.Name) + text = lp.SprintStyled("dim", text) + } + formatted = append(formatted, tui.InternalHyperlink(text, "feature:"+feat_tag)) + } + utils.SortWithKey(formatted, func(a string) string { + return strings.ToLower(wcswidth.StripEscapeCodes(a)) + }) + line := lp.SprintStyled(control_name_style, `Features`) + ": " + strings.Join(formatted, ", ") + y = self.render_lines(start_y, ``, line) + return +} + func (self *handler) draw_preview_header(x int) { sz, _ := self.lp.ScreenSize() width := int(sz.WidthCells) - x @@ -197,6 +228,9 @@ func (self *face_panel) draw_screen() (err error) { if err != nil { return err } + if y, err = self.draw_font_features(sz, y, preview); err != nil { + return err + } if int(sz.HeightCells)-y >= num_lines+2 { y += 1 diff --git a/kittens/choose_fonts/types.go b/kittens/choose_fonts/types.go index abd7ce71a..4e8d61d25 100644 --- a/kittens/choose_fonts/types.go +++ b/kittens/choose_fonts/types.go @@ -100,6 +100,7 @@ type RenderedSampleTransmit struct { Style string `json:"style"` Psname string `json:"psname"` Features map[string]FeatureData `json:"features"` + Applied_features map[string]string `json:"applied_features"` Variable_named_style NamedStyle `json:"variable_named_style"` Variable_axis_map map[string]float64 `json:"variable_axis_map"` } diff --git a/kitty/core_text.m b/kitty/core_text.m index d5598b42e..45a7b3f69 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -955,16 +955,7 @@ get_variation(CTFace *self) { static PyObject* applied_features(CTFace *self, PyObject *a UNUSED) { - RAII_PyObject(ans, PyTuple_New(self->font_features.count)); - if (!ans) return NULL; - char buf[256]; - for (size_t i = 0; i < self->font_features.count; i++) { - hb_feature_to_string(&self->font_features.features[i], buf, arraysz(buf)); - PyObject *t = PyUnicode_FromString(buf); - if (!t) return NULL; - PyTuple_SET_ITEM(ans, i, t); - } - Py_INCREF(ans); return ans; + return font_features_as_dict(&self->font_features); } static PyObject* diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 2b990e847..f596cdd1a 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -442,7 +442,7 @@ class Face: def render_sample_text(self, text: str, width: int, height: int, fg_color: int = 0xffffff) -> bytes: ... def get_variation(self) -> Optional[Dict[str, float]]: ... def get_features(self) -> Dict[str, Optional[FeatureData]]: ... - def applied_features(self) -> Tuple[str, ...]: ... + def applied_features(self) -> Dict[str, str]: ... class CoreTextFont(TypedDict): @@ -479,7 +479,7 @@ class CTFace: def render_sample_text(self, text: str, width: int, height: int, fg_color: int = 0xffffff) -> bytes: ... def get_variation(self) -> Optional[Dict[str, float]]: ... def get_features(self) -> Dict[str, Optional[FeatureData]]: ... - def applied_features(self) -> Tuple[str, ...]: ... + def applied_features(self) -> Dict[str, str]: ... def coretext_all_fonts(monospaced_only: bool) -> Tuple[CoreTextFont, ...]: diff --git a/kitty/fonts.c b/kitty/fonts.c index 54f0a7f12..07a33d7eb 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -301,6 +301,32 @@ add_feature(FontFeatures *output, const hb_feature_t *feature) { output->features[output->count++] = *feature; } +static const char* +tag_to_string(uint32_t tag, uint8_t bytes[5]) { + bytes[0] = (tag >> 24) & 0xff; + bytes[1] = (tag >> 16) & 0xff; + bytes[2] = (tag >> 8) & 0xff; + bytes[3] = (tag) & 0xff; + bytes[4] = 0; + return (const char*)bytes; +} + +PyObject* +font_features_as_dict(const FontFeatures *font_features) { + RAII_PyObject(ans, PyDict_New()); + if (!ans) return NULL; + char buf[256]; + char tag[5] = {0}; + for (size_t i = 0; i < font_features->count; i++) { + tag_to_string(font_features->features[i].tag, (unsigned char*)tag); + hb_feature_to_string(&font_features->features[i], buf, arraysz(buf)); + PyObject *t = PyUnicode_FromString(buf); + if (!t) return NULL; + if (PyDict_SetItemString(ans, tag, t) != 0) return NULL; + } + Py_INCREF(ans); return ans; +} + bool create_features_for_face(const char *psname, PyObject *features, FontFeatures *output) { size_t count_from_descriptor = features ? PyTuple_GET_SIZE(features): 0; diff --git a/kitty/fonts.h b/kitty/fonts.h index cfcde2f15..985a52e7b 100644 --- a/kitty/fonts.h +++ b/kitty/fonts.h @@ -70,6 +70,8 @@ bool read_features_from_font_table(const uint8_t *table, size_t table_len, PyObject *name_lookup_table, PyObject *output); FontFeatures* features_for_face(PyObject *); bool create_features_for_face(const char* psname, PyObject *features, FontFeatures* output); +PyObject* +font_features_as_dict(const FontFeatures *font_features); static inline void right_shift_canvas(pixel *canvas, size_t width, size_t height, size_t amt) { diff --git a/kitty/fonts/features.py b/kitty/fonts/features.py index 1b75d202f..5c324c48a 100644 --- a/kitty/fonts/features.py +++ b/kitty/fonts/features.py @@ -141,9 +141,9 @@ known_features: Dict[str, FeatureDefinition] = { # {{{ 'zero': FeatureDefinition('Slashed Zero', Type.boolean), } for i in range(1, 100): - known_features[f'cv{i:2d}'] = FeatureDefinition(f'Character Variant {i}', Type.index) + known_features[f'cv{i:02d}'] = FeatureDefinition(f'Character Variant {i}', Type.index) for i in range(1, 20): - known_features[f'ss{i:2d}'] = FeatureDefinition(f'Stylistic Set {i}', Type.boolean) + known_features[f'ss{i:02d}'] = FeatureDefinition(f'Stylistic Set {i}', Type.boolean) # }}} diff --git a/kitty/freetype.c b/kitty/freetype.c index 20873a575..8f827b28b 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -849,16 +849,7 @@ get_variation(Face *self, PyObject *a UNUSED) { static PyObject* applied_features(Face *self, PyObject *a UNUSED) { - RAII_PyObject(ans, PyTuple_New(self->font_features.count)); - if (!ans) return NULL; - char buf[256]; - for (size_t i = 0; i < self->font_features.count; i++) { - hb_feature_to_string(&self->font_features.features[i], buf, arraysz(buf)); - PyObject *t = PyUnicode_FromString(buf); - if (!t) return NULL; - PyTuple_SET_ITEM(ans, i, t); - } - Py_INCREF(ans); return ans; + return font_features_as_dict(&self->font_features); } static PyObject* diff --git a/kitty_tests/fonts.py b/kitty_tests/fonts.py index 36ecf6443..2a9f1e4c3 100644 --- a/kitty_tests/fonts.py +++ b/kitty_tests/fonts.py @@ -147,11 +147,11 @@ class Selection(BaseTest): opts = Options() opts.font_family = parse_font_spec('family="liberation mono"') ff = get_font_files(opts) - self.ae(face_from_descriptor(ff['medium']).applied_features(), ('-dlig',)) - self.ae(face_from_descriptor(ff['bold']).applied_features(), ()) - opts.font_family = parse_font_spec('family="liberation mono" features="dlig test"') + self.ae(face_from_descriptor(ff['medium']).applied_features(), {'dlig': '-dlig'}) + self.ae(face_from_descriptor(ff['bold']).applied_features(), {}) + opts.font_family = parse_font_spec('family="liberation mono" features="dlig test=3"') ff = get_font_files(opts) - self.ae(face_from_descriptor(ff['medium']).applied_features(), ('dlig', 'test')) + self.ae(face_from_descriptor(ff['medium']).applied_features(), {'dlig': 'dlig', 'test': 'test=3'}) class Rendering(BaseTest):