From 822c9367c6fbd3c14eb40b6a16341036fe799a02 Mon Sep 17 00:00:00 2001 From: Antoine Bertin Date: Wed, 16 Dec 2020 14:58:38 +0100 Subject: [PATCH] Support default font_features from fontconfig --- kitty/config.py | 9 +-------- kitty/fast_data_types.pyi | 7 +++++++ kitty/fontconfig.c | 22 ++++++++++++++++++++++ kitty/fonts/__init__.py | 8 ++++++++ kitty/fonts/core_text.py | 5 +++++ kitty/fonts/fontconfig.py | 24 ++++++++++++++++++++++-- kitty/fonts/render.py | 10 +++++++--- 7 files changed, 72 insertions(+), 13 deletions(-) diff --git a/kitty/config.py b/kitty/config.py index bd4a276cf..81c07745f 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -21,6 +21,7 @@ from .conf.utils import ( ) from .config_data import all_options, parse_mods, type_convert from .constants import cache_dir, defconf, is_macos +from .fonts import FontFeature from .key_names import get_key_name_lookup, key_name_aliases from .options_stub import Options as OptionsStub from .typing import TypedDict @@ -524,14 +525,6 @@ def handle_symbol_map(key: str, val: str, ans: Dict[str, Any]) -> None: ans['symbol_map'].update(parse_symbol_map(val)) -class FontFeature(str): - - def __new__(cls, name: str, parsed: bytes) -> 'FontFeature': - ans: FontFeature = str.__new__(cls, name) # type: ignore - ans.parsed = parsed # type: ignore - return ans - - @special_handler def handle_font_features(key: str, val: str, ans: Dict[str, Any]) -> None: if val != 'none': diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 0e91c20dd..998342fdf 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -410,6 +410,7 @@ class FontConfigPattern(TypedDict): postscript_name: str style: str spacing: str + fontfeatures: str weight: int width: int slant: int @@ -441,6 +442,12 @@ def fc_match( pass +def fc_match_postscript_name( + postscript_name: str +) -> FontConfigPattern: + pass + + class CoreTextFont(TypedDict): path: str postscript_name: str diff --git a/kitty/fontconfig.c b/kitty/fontconfig.c index 7156d9ee5..7b210d8fc 100644 --- a/kitty/fontconfig.c +++ b/kitty/fontconfig.c @@ -46,6 +46,7 @@ pattern_as_dict(FcPattern *pat) { S(FC_STYLE, style); S(FC_FULLNAME, full_name); S(FC_POSTSCRIPT_NAME, postscript_name); + S(FC_FONT_FEATURES, fontfeatures); I(FC_WEIGHT, weight); I(FC_WIDTH, width) I(FC_SLANT, slant); @@ -179,6 +180,26 @@ end: return ans; } +static PyObject* +fc_match_postscript_name(PyObject UNUSED *self, PyObject *args) { + char *postscript_name = NULL; + FcPattern *pat = NULL; + PyObject *ans = NULL; + + if (!PyArg_ParseTuple(args, "|z", &postscript_name)) return NULL; + pat = FcPatternCreate(); + if (pat == NULL) return PyErr_NoMemory(); + if (!postscript_name || strlen(postscript_name) == 0) return NULL; + + AP(FcPatternAddString, FC_POSTSCRIPT_NAME, (const FcChar8*)postscript_name, "postscript_name"); + + ans = _fc_match(pat); + +end: + if (pat != NULL) FcPatternDestroy(pat); + return ans; +} + PyObject* specialize_font_descriptor(PyObject *base_descriptor, FONTS_DATA_HANDLE fg) { PyObject *p = PyDict_GetItemString(base_descriptor, "path"), *ans = NULL; @@ -224,6 +245,7 @@ end: static PyMethodDef module_methods[] = { METHODB(fc_list, METH_VARARGS), METHODB(fc_match, METH_VARARGS), + METHODB(fc_match_postscript_name, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/fonts/__init__.py b/kitty/fonts/__init__.py index 88ec07c94..a1733fc3c 100644 --- a/kitty/fonts/__init__.py +++ b/kitty/fonts/__init__.py @@ -9,3 +9,11 @@ class ListedFont(TypedDict): full_name: str postscript_name: str is_monospace: bool + + +class FontFeature(str): + + def __new__(cls, name: str, parsed: bytes) -> 'FontFeature': + ans: FontFeature = str.__new__(cls, name) # type: ignore + ans.parsed = parsed # type: ignore + return ans diff --git a/kitty/fonts/core_text.py b/kitty/fonts/core_text.py index 08289c3fb..e61de74db 100644 --- a/kitty/fonts/core_text.py +++ b/kitty/fonts/core_text.py @@ -50,6 +50,11 @@ def list_fonts() -> Generator[ListedFont, None, None]: yield {'family': f, 'full_name': fn, 'postscript_name': fd['postscript_name'] or '', 'is_monospace': is_mono} +def find_font_features(postscript_name: str) -> Tuple[str, ...]: + """Not Implemented""" + return tuple() + + def find_best_match(family: str, bold: bool = False, italic: bool = False) -> CoreTextFont: q = re.sub(r'\s+', ' ', family.lower()) font_map = all_fonts_map() diff --git a/kitty/fonts/fontconfig.py b/kitty/fonts/fontconfig.py index 90825264a..44cf7b369 100644 --- a/kitty/fonts/fontconfig.py +++ b/kitty/fonts/fontconfig.py @@ -8,12 +8,14 @@ from typing import Dict, Generator, List, Optional, Tuple, cast from kitty.fast_data_types import ( FC_DUAL, FC_MONO, FC_SLANT_ITALIC, FC_SLANT_ROMAN, FC_WEIGHT_BOLD, - FC_WEIGHT_REGULAR, FC_WIDTH_NORMAL, fc_list, fc_match as fc_match_impl + FC_WEIGHT_REGULAR, FC_WIDTH_NORMAL, fc_list, fc_match as fc_match_impl, + fc_match_postscript_name, parse_font_feature ) from kitty.options_stub import Options from kitty.typing import FontConfigPattern +from kitty.utils import log_error -from . import ListedFont +from . import ListedFont, FontFeature attr_map = {(False, False): 'font_family', (True, False): 'bold_font', @@ -69,6 +71,24 @@ def fc_match(family: str, bold: bool, italic: bool, spacing: int = FC_MONO) -> F return fc_match_impl(family, bold, italic, spacing) +def find_font_features(postscript_name: str) -> Tuple[str, ...]: + pat = fc_match_postscript_name(postscript_name) + + if 'postscript_name' not in pat or pat['postscript_name'] != postscript_name or 'fontfeatures' not in pat: + return tuple() + + features = [] + for feat in pat['fontfeatures'].split(): + try: + parsed = parse_font_feature(feat) + except ValueError: + log_error('Ignoring invalid font feature: {}'.format(feat)) + else: + features.append(FontFeature(feat, parsed)) + + return tuple(features) + + def find_best_match(family: str, bold: bool = False, italic: bool = False, monospaced: bool = True) -> FontConfigPattern: q = family_name_to_key(family) font_map = all_fonts_map(monospaced) diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index abc673b7b..24c385ee1 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -25,9 +25,9 @@ from kitty.typing import CoreTextFont, FontConfigPattern from kitty.utils import log_error if is_macos: - from .core_text import get_font_files as get_font_files_coretext, font_for_family as font_for_family_macos + from .core_text import get_font_files as get_font_files_coretext, font_for_family as font_for_family_macos, find_font_features else: - from .fontconfig import get_font_files as get_font_files_fontconfig, font_for_family as font_for_family_fontconfig + from .fontconfig import get_font_files as get_font_files_fontconfig, font_for_family as font_for_family_fontconfig, find_font_features FontObject = Union[CoreTextFont, FontConfigPattern] current_faces: List[Tuple[FontObject, bool, bool]] = [] @@ -189,12 +189,16 @@ def set_font_family(opts: Optional[OptionsStub] = None, override_font_size: Opti before = len(current_faces) sm = create_symbol_map(opts) num_symbol_fonts = len(current_faces) - before + font_features = {} + for face, _, _ in current_faces: + font_features[face['postscript_name']] = find_font_features(face['postscript_name']) + font_features.update(opts.font_features) if debug_font_matching: dump_faces(ftypes, indices) set_font_data( render_box_drawing, prerender_function, descriptor_for_idx, indices['bold'], indices['italic'], indices['bi'], num_symbol_fonts, - sm, sz, opts.font_features + sm, sz, font_features )