diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 6bc2bf199..87cc6483d 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -416,6 +416,10 @@ def fc_match_postscript_name( pass +class Face: + def __init__(self, descriptor: Optional[FontConfigPattern] = None, path: str = '', index: int = 0): ... + + class CoreTextFont(TypedDict): path: str postscript_name: str diff --git a/kitty/fontconfig.c b/kitty/fontconfig.c index 3e6c2379e..7f29435bf 100644 --- a/kitty/fontconfig.c +++ b/kitty/fontconfig.c @@ -246,12 +246,8 @@ fc_list(PyObject UNUSED *self, PyObject *args, PyObject *kw) { AP(FcPatternAddBool, FC_SCALABLE, FcTrue, "scalable"); } if (spacing > -1) AP(FcPatternAddInteger, FC_SPACING, spacing, "spacing"); -#define basic_properties FC_FILE, FC_POSTSCRIPT_NAME, FC_FAMILY, FC_STYLE, FC_FULLNAME, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_HINT_STYLE, FC_INDEX, FC_HINTING, FC_SCALABLE, FC_OUTLINE, FC_COLOR, FC_SPACING - if (only_variable) { - AP(FcPatternAddBool, FC_VARIABLE, FcTrue, "variable"); - os = FcObjectSetBuild(basic_properties, FC_VARIABLE, FC_FONT_VARIATIONS, NULL); - } else os = FcObjectSetBuild(basic_properties, NULL); -#undef basic_properties + if (only_variable) AP(FcPatternAddBool, FC_VARIABLE, FcTrue, "variable"); + os = FcObjectSetBuild(FC_FILE, FC_POSTSCRIPT_NAME, FC_FAMILY, FC_STYLE, FC_FULLNAME, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_HINT_STYLE, FC_INDEX, FC_HINTING, FC_SCALABLE, FC_OUTLINE, FC_COLOR, FC_SPACING, FC_VARIABLE, NULL); if (!os) { PyErr_SetString(PyExc_ValueError, "Failed to create fontconfig object set"); goto end; } fs = FcFontList(NULL, pat, os); if (!fs) { PyErr_SetString(PyExc_ValueError, "Failed to create fontconfig font set"); goto end; } diff --git a/kitty/fonts/__init__.py b/kitty/fonts/__init__.py index cd468df69..1767efcd9 100644 --- a/kitty/fonts/__init__.py +++ b/kitty/fonts/__init__.py @@ -1,8 +1,5 @@ -try: - from typing import NamedTuple, TypedDict -except ImportError: - TypedDict = dict from enum import Enum, IntEnum, auto +from typing import Any, NamedTuple, TypedDict class ListedFont(TypedDict): @@ -10,6 +7,8 @@ class ListedFont(TypedDict): full_name: str postscript_name: str is_monospace: bool + is_variable: bool + descriptor: Any class FontFeature: diff --git a/kitty/fonts/fontconfig.py b/kitty/fonts/fontconfig.py index ce78d4f52..d84336df9 100644 --- a/kitty/fonts/fontconfig.py +++ b/kitty/fonts/fontconfig.py @@ -17,7 +17,7 @@ from kitty.fast_data_types import ( fc_match_postscript_name, parse_font_feature, ) -from kitty.fast_data_types import fc_match as fc_match_impl +from kitty.fast_data_types import fc_match as fc_match_impl, Face from kitty.options.types import Options from kitty.typing import FontConfigPattern from kitty.utils import log_error @@ -58,8 +58,8 @@ def all_fonts_map(monospaced: bool = True) -> FontMap: return create_font_map(ans) -def list_fonts() -> Generator[ListedFont, None, None]: - for fd in fc_list(): +def list_fonts(only_variable: bool = False) -> Generator[ListedFont, None, None]: + for fd in fc_list(only_variable=only_variable): f = fd.get('family') if f and isinstance(f, str): fn_ = fd.get('full_name') @@ -68,7 +68,10 @@ def list_fonts() -> Generator[ListedFont, None, None]: else: fn = f'{f} {fd.get("style", "")}'.strip() is_mono = fd.get('spacing') in ('MONO', 'DUAL') - yield {'family': f, 'full_name': fn, 'postscript_name': str(fd.get('postscript_name', '')), 'is_monospace': is_mono} + yield { + 'family': f, 'full_name': fn, 'postscript_name': str(fd.get('postscript_name', '')), + 'is_monospace': is_mono, 'descriptor': fd, 'is_variable': fd.get('variable', False), + } def family_name_to_key(family: str) -> str: @@ -171,3 +174,8 @@ def get_font_files(opts: Options) -> Dict[str, FontConfigPattern]: def font_for_family(family: str) -> Tuple[FontConfigPattern, bool, bool]: ans = find_best_match(family, monospaced=False) return ans, ans.get('weight', 0) >= FC_WEIGHT_BOLD, ans.get('slant', FC_SLANT_ROMAN) != FC_SLANT_ROMAN + + +def get_variable_data_for_descriptor(fd: FontConfigPattern) -> None: + f = Face(descriptor=fd) + print(f) diff --git a/kitty/fonts/list.py b/kitty/fonts/list.py index a9303048e..fefccbd03 100644 --- a/kitty/fonts/list.py +++ b/kitty/fonts/list.py @@ -4,6 +4,7 @@ import sys from typing import Dict, List, Sequence +from kittens.tui.operations import styled from kitty.constants import is_macos from . import ListedFont @@ -11,7 +12,19 @@ from . import ListedFont if is_macos: from .core_text import list_fonts else: - from .fontconfig import list_fonts + from .fontconfig import get_variable_data_for_descriptor, list_fonts + + +def title(x: str) -> str: + if sys.stdout.isatty(): + return styled(x, fg='green', bold=True) + return x + + +def italic(x: str) -> str: + if sys.stdout.isatty(): + return styled(x, italic=True) + return x def create_family_groups(monospaced: bool = True) -> Dict[str, List[ListedFont]]: @@ -22,19 +35,20 @@ def create_family_groups(monospaced: bool = True) -> Dict[str, List[ListedFont]] return g +def show_variable(f: ListedFont, psnames: bool) -> None: + get_variable_data_for_descriptor(f['descriptor']) + + def main(argv: Sequence[str]) -> None: psnames = '--psnames' in argv - isatty = sys.stdout.isatty() groups = create_family_groups() for k in sorted(groups, key=lambda x: x.lower()): - if isatty: - print(f'\033[1;32m{k}\033[m') - else: - print(k) + print(title(k)) for f in sorted(groups[k], key=lambda x: x['full_name'].lower()): - p = f['full_name'] - if isatty: - p = f'\033[3m{p}\033[m' + if f['is_variable']: + show_variable(f, psnames) + continue + p = italic(f['full_name']) if psnames: p += ' ({})'.format(f['postscript_name']) print(' ', p) diff --git a/kitty/freetype.c b/kitty/freetype.c index f542cba83..7d49b43e5 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -36,7 +36,7 @@ typedef struct { int ascender, descender, height, max_advance_width, max_advance_height, underline_position, underline_thickness, strikethrough_position, strikethrough_thickness; int hinting, hintstyle; FaceIndex instance; - bool is_scalable, has_color; + bool is_scalable, has_color, is_variable, has_svg; float size_in_pts; FT_F26Dot6 char_width, char_height; FT_UInt xdpi, ydpi; @@ -201,8 +201,10 @@ init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, FONTS_DATA_ #undef CPY self->is_scalable = FT_IS_SCALABLE(self->face); self->has_color = FT_HAS_COLOR(self->face); + self->is_variable = FT_HAS_MULTIPLE_MASTERS(self->face); + self->has_svg = FT_HAS_SVG(self->face); self->hinting = hinting; self->hintstyle = hintstyle; - if (!set_size_for_face((PyObject*)self, 0, false, fg)) return false; + if (fg && !set_size_for_face((PyObject*)self, 0, false, fg)) return false; self->harfbuzz_font = hb_ft_font_create(self->face, NULL); if (self->harfbuzz_font == NULL) { PyErr_NoMemory(); return false; } hb_ft_font_set_load_flags(self->harfbuzz_font, get_load_flags(self->hinting, self->hintstyle, FT_LOAD_DEFAULT)); @@ -265,6 +267,20 @@ face_from_descriptor(PyObject *descriptor, FONTS_DATA_HANDLE fg) { return (PyObject*)self; } +static PyObject* +new(PyTypeObject *type UNUSED, PyObject *args, PyObject *kw) { + const char *path = NULL; + long index = 0; + PyObject *descriptor = NULL; + + static char *kwds[] = {"descriptor", "path", "index", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kw, "|Osi", kwds, &descriptor, &path, &index)) return NULL; + if (descriptor) return face_from_descriptor(descriptor, NULL); + if (path) return face_from_path(path, index, NULL); + PyErr_SetString(PyExc_TypeError, "Must specify either path or descriptor"); + return NULL; +} + FT_Face native_face_from_path(const char *path, int index) { int error; @@ -733,6 +749,9 @@ static PyMemberDef members[] = { MEM(strikethrough_position, T_INT), MEM(strikethrough_thickness, T_INT), MEM(is_scalable, T_BOOL), + MEM(is_variable, T_BOOL), + MEM(has_svg, T_BOOL), + MEM(has_color, T_BOOL), MEM(path, T_OBJECT_EX), {NULL} /* Sentinel */ }; @@ -746,6 +765,7 @@ static PyMethodDef methods[] = { PyTypeObject Face_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.Face", + .tp_new = new, .tp_basicsize = sizeof(Face), .tp_dealloc = (destructor)dealloc, .tp_flags = Py_TPFLAGS_DEFAULT,