Render font feature list in UI

This commit is contained in:
Kovid Goyal 2024-05-27 12:47:09 +05:30
parent 57edb412e6
commit a69d71d416
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
10 changed files with 74 additions and 28 deletions

View file

@ -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)

View file

@ -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

View file

@ -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"`
}

View file

@ -955,16 +955,7 @@ static CTFontRef nerd_font(CGFloat sz) {
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*

View file

@ -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, ...]:

View file

@ -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;

View file

@ -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) {

View file

@ -141,9 +141,9 @@ class FeatureDefinition(NamedTuple):
'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)
# }}}

View file

@ -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*

View file

@ -147,11 +147,11 @@ def t(x, **kw):
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):