Add tests to validate feature-from-spec

This commit is contained in:
Kovid Goyal 2024-05-26 09:49:00 +05:30
parent 060732b428
commit 57edb412e6
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 64 additions and 5 deletions

View File

@ -953,6 +953,20 @@ static CTFontRef nerd_font(CGFloat sz) {
return variation_to_python(src);
}
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;
}
static PyObject*
get_features(CTFace *self, PyObject *a UNUSED) {
if (!ensure_name_table(self)) return NULL;
@ -1012,6 +1026,7 @@ static CTFontRef nerd_font(CGFloat sz) {
METHODB(display_name, METH_NOARGS),
METHODB(postscript_name, METH_NOARGS),
METHODB(get_variable_data, METH_NOARGS),
METHODB(applied_features, METH_NOARGS),
METHODB(get_features, METH_NOARGS),
METHODB(get_variation, METH_NOARGS),
METHODB(identify_for_debug, METH_NOARGS),

View File

@ -442,6 +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, ...]: ...
class CoreTextFont(TypedDict):
@ -478,6 +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 coretext_all_fonts(monospaced_only: bool) -> Tuple[CoreTextFont, ...]:

View File

@ -290,6 +290,17 @@ desc_to_face(PyObject *desc, FONTS_DATA_HANDLE fg) {
return ans;
}
static void
add_feature(FontFeatures *output, const hb_feature_t *feature) {
for (size_t i = 0; i < output->count; i++) {
if (output->features[i].tag == feature->tag) {
output->features[i] = *feature;
return;
}
}
output->features[output->count++] = *feature;
}
bool
create_features_for_face(const char *psname, PyObject *features, FontFeatures *output) {
size_t count_from_descriptor = features ? PyTuple_GET_SIZE(features): 0;
@ -304,16 +315,16 @@ create_features_for_face(const char *psname, PyObject *features, FontFeatures *o
output->features = calloc(MAX(2u, count_from_opts + count_from_descriptor), sizeof(output->features[0]));
if (!output->features) { PyErr_NoMemory(); return false; }
for (size_t i = 0; i < count_from_opts; i++) {
output->features[output->count++] = from_opts->features[i];
add_feature(output, &from_opts->features[i]);
}
for (size_t i = 0; i < count_from_descriptor; i++) {
ParsedFontFeature *f = (ParsedFontFeature*)PyTuple_GET_ITEM(features, i);
output->features[output->count++] = f->feature;
add_feature(output, &f->feature);
}
if (!output->count) {
if (strstr(psname, "NimbusMonoPS-") == psname) {
output->features[output->count++] = hb_features[LIGA_FEATURE];
output->features[output->count++] = hb_features[DLIG_FEATURE];
add_feature(output, &hb_features[LIGA_FEATURE]);
add_feature(output, &hb_features[DLIG_FEATURE]);
}
}
return true;

View File

@ -315,8 +315,11 @@ def get_font_from_spec(
resolved_medium_font: Optional[Descriptor] = None, match_is_more_specific_than_family: Event = Event()
) -> Descriptor:
if not spec.is_system:
return get_fine_grained_font(spec, bold, italic, resolved_medium_font=resolved_medium_font, family_axis_values=family_axis_values,
ans = get_fine_grained_font(spec, bold, italic, resolved_medium_font=resolved_medium_font, family_axis_values=family_axis_values,
match_is_more_specific_than_family=match_is_more_specific_than_family)
if spec.features:
ans['features'] = spec.features
return ans
family = spec.system or ''
if family == 'auto':
if bold or italic:

View File

@ -847,6 +847,20 @@ get_variation(Face *self, PyObject *a UNUSED) {
Py_INCREF(ans); return ans;
}
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;
}
static PyObject*
get_features(Face *self, PyObject *a UNUSED) {
FT_Error err;
@ -1023,6 +1037,7 @@ static PyMethodDef methods[] = {
METHODB(identify_for_debug, METH_NOARGS),
METHODB(extra_data, METH_NOARGS),
METHODB(get_variable_data, METH_NOARGS),
METHODB(applied_features, METH_NOARGS),
METHODB(get_features, METH_NOARGS),
METHODB(get_variation, METH_NOARGS),
METHODB(get_best_name, METH_O),

View File

@ -10,6 +10,7 @@
from kitty.constants import is_macos, read_kitty_resource
from kitty.fast_data_types import (
DECAWM,
ParsedFontFeature,
get_fallback_font,
sprite_map_set_layout,
sprite_map_set_limits,
@ -33,6 +34,7 @@ def parse_font_spec(spec):
class Selection(BaseTest):
def test_font_selection(self):
self.set_options({'font_features': {'LiberationMono': (ParsedFontFeature('-dlig'),)}})
opts = Options()
fonts_map = all_fonts_map(True)
names = set(fonts_map['family_map']) | set(fonts_map['variable_map'])
@ -140,6 +142,17 @@ def t(x, **kw):
t('bold', spec='variable_name=CascadiaCodeRoman wght=603')
t('bi', spec='variable_name= wght=603')
# Test font features
if has('liberation mono'):
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"')
ff = get_font_files(opts)
self.ae(face_from_descriptor(ff['medium']).applied_features(), ('dlig', 'test'))
class Rendering(BaseTest):