work on passing font features via font specs

This commit is contained in:
Kovid Goyal 2024-05-24 19:42:42 +05:30
parent 7e56920fa3
commit 060732b428
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
14 changed files with 270 additions and 113 deletions

View File

@ -33,6 +33,7 @@
CTFontRef ct_font;
hb_font_t *hb_font;
PyObject *family_name, *full_name, *postscript_name, *path, *name_lookup_table;
FontFeatures font_features;
} CTFace;
PyTypeObject CTFace_Type;
static CTFontRef window_title_font = nil;
@ -87,7 +88,7 @@
static CTFace*
ct_face(CTFontRef font) {
ct_face(CTFontRef font, PyObject *features) {
CTFace *self = (CTFace *)CTFace_Type.tp_alloc(&CTFace_Type, 0);
if (self) {
init_face(self, font);
@ -96,6 +97,9 @@
self->postscript_name = convert_cfstring(CTFontCopyPostScriptName(self->ct_font), true);
self->path = get_path_for_font(self->ct_font);
if (self->family_name == NULL || self->full_name == NULL || self->postscript_name == NULL || self->path == NULL) { Py_CLEAR(self); }
else {
if (!create_features_for_face(postscript_name_for_face((PyObject*)self), features, &self->font_features)) { Py_CLEAR(self); }
}
}
return self;
}
@ -106,6 +110,7 @@
if (self->ct_font) CFRelease(self->ct_font);
self->hb_font = NULL;
self->ct_font = NULL;
free(self->font_features.features);
Py_CLEAR(self->family_name); Py_CLEAR(self->full_name); Py_CLEAR(self->postscript_name); Py_CLEAR(self->path);
Py_CLEAR(self->name_lookup_table);
Py_TYPE(self)->tp_free((PyObject*)self);
@ -126,6 +131,8 @@
return (((uint32_t)bytes[0]) << 24) | (((uint32_t)bytes[1]) << 16) | (((uint32_t)bytes[2]) << 8) | bytes[3];
}
FontFeatures*
features_for_face(PyObject *s) { return &((CTFace*)s)->font_features; }
static void
add_variation_pair(const void *key_, const void *value_, void *ctx) {
@ -444,7 +451,7 @@ static CTFontRef nerd_font(CGFloat sz) {
break;
}
}
return ans ? ans : (PyObject*)ct_face(new_font);
return ans ? ans : (PyObject*)ct_face(new_font, NULL);
}
unsigned int
@ -590,7 +597,7 @@ static CTFontRef nerd_font(CGFloat sz) {
if (!desc) return NULL;
RAII_CoreFoundation(CTFontRef, font, CTFontCreateWithFontDescriptor(desc, fg ? scaled_point_sz(fg) : 12, NULL));
if (!font) { PyErr_SetString(PyExc_ValueError, "Failed to create CTFont object"); return NULL; }
return (PyObject*) ct_face(font);
return (PyObject*) ct_face(font, PyDict_GetItemString(descriptor, "features"));
}
PyObject*
@ -600,7 +607,7 @@ static CTFontRef nerd_font(CGFloat sz) {
RAII_CoreFoundation(CGDataProviderRef, dp, CGDataProviderCreateWithURL(url));
RAII_CoreFoundation(CGFontRef, cg_font, CGFontCreateWithDataProvider(dp));
RAII_CoreFoundation(CTFontRef, ct_font, CTFontCreateWithGraphicsFont(cg_font, 0.0, NULL, NULL));
return (PyObject*) ct_face(ct_font);
return (PyObject*) ct_face(ct_font, NULL);
}
static PyObject*

View File

@ -3,7 +3,7 @@ from ctypes import Array, c_ubyte
from typing import Any, Callable, Dict, Iterator, List, Literal, NewType, Optional, Tuple, TypedDict, Union, overload
from kitty.boss import Boss
from kitty.fonts import FontFeature, VariableData
from kitty.fonts import VariableData
from kitty.fonts.render import FontObject
from kitty.marks import MarkerFunc
from kitty.options.types import Options
@ -400,6 +400,7 @@ class FontConfigPattern(TypedDict):
# The following two are used by C code to get a face from the pattern
named_style: NotRequired[int]
axes: NotRequired[Tuple[float, ...]]
features: NotRequired[Tuple[ParsedFontFeature, ...]]
def fc_list(spacing: int = -1, allow_bitmapped_fonts: bool = False, only_variable: bool = False) -> Tuple[FontConfigPattern, ...]:
@ -464,6 +465,7 @@ class CoreTextFont(TypedDict):
# The following is used by C code to get a face from the pattern
axis_map: NotRequired[Dict[str, float]]
features: NotRequired[Tuple[ParsedFontFeature, ...]]
class CTFace:
@ -482,6 +484,10 @@ def coretext_all_fonts(monospaced_only: bool) -> Tuple[CoreTextFont, ...]:
pass
class ParsedFontFeature:
def __init__(self, s: str): ...
def add_timer(
callback: Callable[[Optional[int]], None],
interval: float,
@ -600,10 +606,6 @@ def get_options() -> Options:
pass
def parse_font_feature(ff: str) -> bytes:
pass
def glfw_primary_monitor_size() -> Tuple[int, int]:
pass
@ -1071,7 +1073,6 @@ def set_font_data(
descriptor_for_idx: Callable[[int], Tuple[FontObject, bool, bool]],
bold: int, italic: int, bold_italic: int, num_symbol_fonts: int,
symbol_maps: Tuple[Tuple[int, int, int], ...], font_sz_in_pts: float,
font_feature_settings: Dict[str, Tuple[FontFeature, ...]],
narrow_symbols: Tuple[Tuple[int, int, int], ...],
) -> None:
pass

View File

@ -7,6 +7,7 @@
*/
#include "fonts.h"
#include "pyport.h"
#include "state.h"
#include "emoji.h"
#include "unicode-data.h"
@ -43,7 +44,6 @@ static hb_feature_t hb_features[3] = {{0}};
static char_type shape_buffer[4096] = {0};
static size_t max_texture_size = 1024, max_array_len = 1024;
typedef enum { LIGA_FEATURE, DLIG_FEATURE, CALT_FEATURE } HBFeature;
static PyObject* font_feature_settings = NULL;
typedef struct {
char_type left, right;
@ -290,40 +290,45 @@ desc_to_face(PyObject *desc, FONTS_DATA_HANDLE fg) {
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;
__typeof__(OPT(font_features).entries) from_opts = NULL;
if (psname) {
for (size_t i = 0; i < OPT(font_features).num && !from_opts; i++) {
__typeof__(OPT(font_features).entries) e = OPT(font_features).entries + i;
if (strcmp(e->psname, psname) == 0) from_opts = e;
}
}
size_t count_from_opts = from_opts ? from_opts->num : 0;
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];
}
for (size_t i = 0; i < count_from_descriptor; i++) {
ParsedFontFeature *f = (ParsedFontFeature*)PyTuple_GET_ITEM(features, i);
output->features[output->count++] = 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];
}
}
return true;
}
static bool
init_font(Font *f, PyObject *face, bool bold, bool italic, bool emoji_presentation) {
f->face = face; Py_INCREF(f->face);
f->bold = bold; f->italic = italic; f->emoji_presentation = emoji_presentation;
f->num_ffs_hb_features = 0;
const char *psname = postscript_name_for_face(face);
if (font_feature_settings != NULL){
PyObject* o = PyDict_GetItemString(font_feature_settings, psname);
if (o != NULL && PyTuple_Check(o)) {
Py_ssize_t len = PyTuple_GET_SIZE(o);
if (len > 0) {
f->num_ffs_hb_features = len + 1;
f->ffs_hb_features = calloc(f->num_ffs_hb_features, sizeof(hb_feature_t));
if (!f->ffs_hb_features) return false;
for (Py_ssize_t i = 0; i < len; i++) {
PyObject* parsed = PyObject_GetAttrString(PyTuple_GET_ITEM(o, i), "parsed");
if (parsed) {
memcpy(f->ffs_hb_features + i, PyBytes_AS_STRING(parsed), sizeof(hb_feature_t));
Py_DECREF(parsed);
}
}
memcpy(f->ffs_hb_features + len, &hb_features[CALT_FEATURE], sizeof(hb_feature_t));
}
}
}
if (!f->num_ffs_hb_features) {
f->ffs_hb_features = calloc(4, sizeof(hb_feature_t));
if (!f->ffs_hb_features) return false;
if (strstr(psname, "NimbusMonoPS-") == psname) {
memcpy(f->ffs_hb_features + f->num_ffs_hb_features++, &hb_features[LIGA_FEATURE], sizeof(hb_feature_t));
memcpy(f->ffs_hb_features + f->num_ffs_hb_features++, &hb_features[DLIG_FEATURE], sizeof(hb_feature_t));
}
memcpy(f->ffs_hb_features + f->num_ffs_hb_features++, &hb_features[CALT_FEATURE], sizeof(hb_feature_t));
}
const FontFeatures *features = features_for_face(face);
f->ffs_hb_features = calloc(1 + features->count, sizeof(hb_feature_t));
if (!f->ffs_hb_features) { PyErr_NoMemory(); return false; }
f->num_ffs_hb_features = features->count;
memcpy(f->ffs_hb_features, features->features, sizeof(hb_feature_t) * features->count);
memcpy(f->ffs_hb_features + f->num_ffs_hb_features++, &hb_features[CALT_FEATURE], sizeof(hb_feature_t));
return true;
}
@ -1433,12 +1438,12 @@ set_symbol_maps(SymbolMap **maps, size_t *num, const PyObject *sm) {
static PyObject*
set_font_data(PyObject UNUSED *m, PyObject *args) {
PyObject *sm, *ns;
Py_CLEAR(box_drawing_function); Py_CLEAR(prerender_function); Py_CLEAR(descriptor_for_idx); Py_CLEAR(font_feature_settings);
if (!PyArg_ParseTuple(args, "OOOIIIIO!dOO!",
Py_CLEAR(box_drawing_function); Py_CLEAR(prerender_function); Py_CLEAR(descriptor_for_idx);
if (!PyArg_ParseTuple(args, "OOOIIIIO!dO!",
&box_drawing_function, &prerender_function, &descriptor_for_idx,
&descriptor_indices.bold, &descriptor_indices.italic, &descriptor_indices.bi, &descriptor_indices.num_symbol_fonts,
&PyTuple_Type, &sm, &OPT(font_size), &font_feature_settings, &PyTuple_Type, &ns)) return NULL;
Py_INCREF(box_drawing_function); Py_INCREF(prerender_function); Py_INCREF(descriptor_for_idx); Py_INCREF(font_feature_settings);
&PyTuple_Type, &sm, &OPT(font_size), &PyTuple_Type, &ns)) return NULL;
Py_INCREF(box_drawing_function); Py_INCREF(prerender_function); Py_INCREF(descriptor_for_idx);
free_font_groups();
clear_symbol_maps();
set_symbol_maps(&symbol_maps, &num_symbol_maps, sm);
@ -1539,7 +1544,6 @@ finalize(void) {
Py_CLEAR(box_drawing_function);
Py_CLEAR(prerender_function);
Py_CLEAR(descriptor_for_idx);
Py_CLEAR(font_feature_settings);
free_font_groups();
free(ligature_types);
if (harfbuzz_buffer) { hb_buffer_destroy(harfbuzz_buffer); harfbuzz_buffer = NULL; }
@ -1710,26 +1714,89 @@ free_font_data(PyObject *self UNUSED, PyObject *args UNUSED) {
Py_RETURN_NONE;
}
static PyObject*
parse_font_feature(PyObject *self UNUSED, PyObject *feature) {
if (!PyUnicode_Check(feature)) {
PyErr_SetString(PyExc_TypeError, "feature must be a unicode object");
return NULL;
static PyObject *
parsed_font_feature_new(PyTypeObject *type, PyObject *args, PyObject *kwds UNUSED) {
const char *s;
if (!PyArg_ParseTuple(args, "s", &s)) return NULL;
ParsedFontFeature *self = (ParsedFontFeature *)type->tp_alloc(type, 0);
if (self != NULL) {
if (!hb_feature_from_string(s, -1, &self->feature)) {
PyErr_Format(PyExc_ValueError, "%s is not a valid font feature", s);
Py_CLEAR(self);
}
}
PyObject *ans = PyBytes_FromStringAndSize(NULL, sizeof(hb_feature_t));
if (!ans) return NULL;
if (!hb_feature_from_string(PyUnicode_AsUTF8(feature), -1, (hb_feature_t*)PyBytes_AS_STRING(ans))) {
Py_CLEAR(ans);
PyErr_Format(PyExc_ValueError, "%U is not a valid font feature", feature);
return NULL;
}
return ans;
return (PyObject*) self;
}
static PyObject*
parsed_font_feature_str(PyObject *self_) {
char buf[128];
hb_feature_to_string(&((ParsedFontFeature*)self_)->feature, buf, arraysz(buf));
return PyUnicode_FromString(buf);
}
static PyObject*
parsed_font_feature_repr(PyObject *self_) {
RAII_PyObject(s, parsed_font_feature_str(self_));
return s ? PyObject_Repr(s) : NULL;
}
PyTypeObject ParsedFontFeature_Type;
static PyObject*
parsed_font_feature_cmp(PyObject *self, PyObject *other, int op) {
if (op != Py_EQ && op != Py_NE) return Py_NotImplemented;
if (!PyObject_TypeCheck(other, &ParsedFontFeature_Type)) {
if (op == Py_EQ) Py_RETURN_FALSE;
Py_RETURN_TRUE;
}
ParsedFontFeature *a = (ParsedFontFeature*)self, *b = (ParsedFontFeature*)other;
PyObject *ret = Py_True;
if (memcmp(&a->feature, &b->feature, sizeof(hb_feature_t)) == 0) {
if (op == Py_NE) ret = Py_False;
} else {
if (op == Py_EQ) ret = Py_False;
}
Py_INCREF(ret); return ret;
}
static Py_hash_t
parsed_font_feature_hash(PyObject *s) {
ParsedFontFeature *self = (ParsedFontFeature*)s;
if (self->hash_computed) return self->hashval;
self->hash_computed = true;
HASH_FUNCTION(&self->feature, sizeof(hb_feature_t), self->hashval);
return self->hashval;
}
static PyObject*
parsed_font_feature_call(PyObject *s, PyObject *args, PyObject *kwargs UNUSED) {
ParsedFontFeature *self = (ParsedFontFeature*)s;
void *dest = PyLong_AsVoidPtr(args);
memcpy(dest, &self->feature, sizeof(hb_feature_t));
Py_RETURN_NONE;
}
PyTypeObject ParsedFontFeature_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "kitty.fast_data_types.ParsedFontFeature",
.tp_basicsize = sizeof(ParsedFontFeature),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "FontFeature",
.tp_new = parsed_font_feature_new,
.tp_str = parsed_font_feature_str,
.tp_repr = parsed_font_feature_repr,
.tp_richcompare = parsed_font_feature_cmp,
.tp_hash = parsed_font_feature_hash,
.tp_call = parsed_font_feature_call,
};
static PyMethodDef module_methods[] = {
METHODB(set_font_data, METH_VARARGS),
METHODB(free_font_data, METH_NOARGS),
METHODB(parse_font_feature, METH_O),
METHODB(create_test_font_group, METH_VARARGS),
METHODB(sprite_map_set_layout, METH_VARARGS),
METHODB(test_sprite_position_for, METH_VARARGS),
@ -1757,5 +1824,9 @@ init_fonts(PyObject *module) {
create_feature("-calt", CALT_FEATURE);
#undef create_feature
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
if (PyType_Ready(&ParsedFontFeature_Type) < 0) return 0;
if (PyModule_AddObject(module, "ParsedFontFeature", (PyObject *)&ParsedFontFeature_Type) != 0) return 0;
Py_INCREF(&ParsedFontFeature_Type);
return true;
}

View File

@ -18,6 +18,20 @@ typedef struct {
size_t width, height;
} StringCanvas;
typedef struct FontFeatures {
size_t count;
hb_feature_t *features;
} FontFeatures;
typedef struct ParsedFontFeature {
PyObject_HEAD
hb_feature_t feature;
Py_hash_t hashval;
bool hash_computed;
} ParsedFontFeature;
// API that font backends need to implement
unsigned int glyph_id_for_codepoint(PyObject *, char_type);
int get_glyph_width(PyObject *, glyph_index);
@ -54,6 +68,8 @@ bool
read_STAT_font_table(const uint8_t *table, size_t table_len, PyObject *name_lookup_table, PyObject *output);
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);
static inline void
right_shift_canvas(pixel *canvas, size_t width, size_t height, size_t amt) {

View File

@ -1,6 +1,7 @@
from enum import Enum, IntEnum, auto
from typing import TYPE_CHECKING, Dict, List, Literal, NamedTuple, Optional, Sequence, Tuple, TypedDict, TypeVar, Union
from kitty.fast_data_types import ParsedFontFeature
from kitty.types import run_once
from kitty.typing import CoreTextFont, FontConfigPattern
from kitty.utils import shlex_split
@ -88,18 +89,6 @@ class VariableData(TypedDict):
multi_axis_styles: Tuple[MultiAxisStyle, ...]
class FontFeature:
__slots__ = 'name', 'parsed'
def __init__(self, name: str, parsed: bytes):
self.name = name
self.parsed = parsed
def __repr__(self) -> str:
return repr(self.name)
class ModificationType(Enum):
underline_position = auto()
underline_thickness = auto()
@ -144,6 +133,7 @@ class FontSpec(NamedTuple):
system: Optional[str] = None
axes: Tuple[Tuple[str, float], ...] = ()
variable_name: Optional[str] = None
features: Tuple[ParsedFontFeature, ...] = ()
created_from_string: str = ''
@classmethod
@ -155,19 +145,21 @@ def from_setting(cls, spec: str) -> 'FontSpec':
return FontSpec(system=spec, created_from_string=spec)
axes = {}
defined = {}
features: Tuple[ParsedFontFeature, ...] = ()
for item in items:
k, sep, v = item.partition('=')
if sep != '=':
raise ValueError(f'The font specification: {spec} is not valid as {item} does not contain an =')
if k in ('family', 'style', 'full_name', 'postscript_name', 'variable_name'):
defined[k] = v
elif k == 'features':
features += tuple(ParsedFontFeature(x) for x in v.split())
else:
try:
axes[k] = float(v)
except Exception:
raise ValueError(f'The font specification: {spec} is not valid as {v} is not a number')
return FontSpec(axes=tuple(axes.items()), created_from_string=spec, **defined)
return FontSpec(axes=tuple(axes.items()), created_from_string=spec, features=features, **defined)
@property
def is_system(self) -> bool:
@ -198,6 +190,8 @@ def a(key: str, val: str) -> None:
a('variable_name', self.variable_name)
if self.style is not None:
a('style', self.style)
if self.features:
a('features', ' '.join(str(f) for f in self.features))
if self.axes:
for (key, val) in self.axes:
a(key, f'{val:g}')

View File

@ -189,7 +189,7 @@ def set_font_family(opts: Optional[Options] = None, override_font_size: Optional
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.copy(), ns
sm, sz, ns
)

View File

@ -48,6 +48,7 @@ typedef struct {
free_extra_data_func free_extra_data;
float apple_leading;
PyObject *name_lookup_table;
FontFeatures font_features;
} Face;
PyTypeObject Face_Type;
@ -233,8 +234,7 @@ init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, FONTS_DATA_
self->strikethrough_thickness = os2->yStrikeoutSize;
}
self->path = path;
Py_INCREF(self->path);
self->path = path; Py_INCREF(self->path);
self->space_glyph_id = glyph_id_for_codepoint((PyObject*)self, ' ');
return true;
}
@ -299,11 +299,15 @@ face_from_descriptor(PyObject *descriptor, FONTS_DATA_HANDLE fg) {
}
if ((error = FT_Set_Var_Design_Coordinates(self->face, sz, coords))) return set_load_error(path, error);
}
if (!create_features_for_face(postscript_name_for_face((PyObject*)self), PyDict_GetItemString(descriptor, "features"), &self->font_features)) return NULL;
}
Py_XINCREF(retval);
return retval;
}
FontFeatures*
features_for_face(PyObject *s) { return &((Face*)s)->font_features; }
static PyObject*
new(PyTypeObject *type UNUSED, PyObject *args, PyObject *kw) {
const char *path = NULL;
@ -343,6 +347,7 @@ dealloc(Face* self) {
if (self->harfbuzz_font) hb_font_destroy(self->harfbuzz_font);
if (self->face) FT_Done_Face(self->face);
if (self->extra_data && self->free_extra_data) self->free_extra_data(self->extra_data);
free(self->font_features.features);
Py_CLEAR(self->path);
Py_CLEAR(self->name_lookup_table);
Py_TYPE(self)->tp_free((PyObject*)self);

View File

@ -132,10 +132,8 @@
'''
)
opt('+font_features', 'none',
option_type='font_features',
add_to_default=False,
long_text='''
opt('+font_features', 'none', option_type='font_features', ctype='!font_features',
add_to_default=False, long_text='''
Choose exactly which OpenType features to enable or disable. This is useful as
some fonts might have features worthwhile in a terminal. For example, Fira Code
includes a discretionary feature, :code:`zero`, which in that font changes the

View File

@ -18,7 +18,7 @@
scrollback_pager_history_size, shell_integration, store_multiple, symbol_map, tab_activity_symbol,
tab_bar_edge, tab_bar_margin_height, tab_bar_min_tabs, tab_fade, tab_font_style, tab_separator,
tab_title_template, titlebar_color, to_cursor_shape, to_font_size, to_layout_names, to_modifiers,
url_prefixes, url_style, visual_window_select_characters, window_border_width, window_size
url_prefixes, url_style, visual_window_select_characters, window_border_width, window_size, window_logo_scale
)

View File

@ -44,6 +44,19 @@ convert_from_opts_disable_ligatures(PyObject *py_opts, Options *opts) {
Py_DECREF(ret);
}
static void
convert_from_python_font_features(PyObject *val, Options *opts) {
font_features(val, opts);
}
static void
convert_from_opts_font_features(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "font_features");
if (ret == NULL) return;
convert_from_python_font_features(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_modify_font(PyObject *val, Options *opts) {
modify_font(val, opts);
@ -1170,6 +1183,8 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) {
if (PyErr_Occurred()) return false;
convert_from_opts_disable_ligatures(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_font_features(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_modify_font(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_text_composition_strategy(py_opts, opts);

View File

@ -8,6 +8,7 @@
#include "../state.h"
#include "../colors.h"
#include "../fonts.h"
static inline float
PyFloat_AsFloat(PyObject *o) {
@ -44,7 +45,7 @@ parse_ms_long_to_monotonic_t(PyObject *val) {
return ms_to_monotonic_t(PyLong_AsUnsignedLong(val));
}
static WindowTitleIn
static inline WindowTitleIn
window_title_in(PyObject *title_in) {
const char *in = PyUnicode_AsUTF8(title_in);
switch(in[0]) {
@ -57,7 +58,7 @@ window_title_in(PyObject *title_in) {
return ALL;
}
static UnderlineHyperlinks
static inline UnderlineHyperlinks
underline_hyperlinks(PyObject *x) {
const char *in = PyUnicode_AsUTF8(x);
switch(in[0]) {
@ -67,7 +68,7 @@ underline_hyperlinks(PyObject *x) {
}
}
static BackgroundImageLayout
static inline BackgroundImageLayout
bglayout(PyObject *layout_name) {
const char *name = PyUnicode_AsUTF8(layout_name);
switch(name[0]) {
@ -82,7 +83,7 @@ bglayout(PyObject *layout_name) {
return TILING;
}
static ImageAnchorPosition
static inline ImageAnchorPosition
bganchor(PyObject *anchor_name) {
const char *name = PyUnicode_AsUTF8(anchor_name);
ImageAnchorPosition anchor = {0.5f, 0.5f, 0.5f, 0.5f};
@ -108,16 +109,16 @@ bganchor(PyObject *anchor_name) {
if (opts->name) memcpy(opts->name, s, sz); \
}
static void
static inline void
background_image(PyObject *src, Options *opts) { STR_SETTER(background_image); }
static void
static inline void
bell_path(PyObject *src, Options *opts) { STR_SETTER(bell_path); }
static void
static inline void
bell_theme(PyObject *src, Options *opts) { STR_SETTER(bell_theme); }
static void
static inline void
window_logo_path(PyObject *src, Options *opts) { STR_SETTER(default_window_logo); }
#undef STR_SETTER
@ -132,7 +133,7 @@ parse_font_mod_size(PyObject *val, float *sz, AdjustmentUnit *unit) {
}
}
static void
static inline void
modify_font(PyObject *mf, Options *opts) {
#define S(which) { PyObject *v = PyDict_GetItemString(mf, #which); if (v) parse_font_mod_size(v, &opts->which.val, &opts->which.unit); }
S(underline_position); S(underline_thickness); S(strikethrough_thickness); S(strikethrough_position);
@ -140,7 +141,45 @@ modify_font(PyObject *mf, Options *opts) {
#undef S
}
static MouseShape
static inline void
free_font_features(Options *opts) {
if (opts->font_features.entries) {
for (size_t i = 0; i < opts->font_features.num; i++) {
free((void*)opts->font_features.entries[i].psname);
free((void*)opts->font_features.entries[i].features);
}
free(opts->font_features.entries);
}
memset(&opts->font_features, 0, sizeof(opts->font_features));
}
static inline void
font_features(PyObject *mf, Options *opts) {
free_font_features(opts);
opts->font_features.num = PyDict_GET_SIZE(mf);
if (!opts->font_features.num) return;
opts->font_features.entries = calloc(opts->font_features.num, sizeof(opts->font_features.entries[0]));
if (!opts->font_features.entries) { PyErr_NoMemory(); return; }
PyObject *key, *value;
Py_ssize_t pos = 0, i = 0;
while (PyDict_Next(mf, &pos, &key, &value)) {
__typeof__(opts->font_features.entries) e = opts->font_features.entries + i++;
Py_ssize_t psname_sz; const char *psname = PyUnicode_AsUTF8AndSize(key, &psname_sz);
e->psname = strndup(psname, psname_sz);
if (!e->psname) { PyErr_NoMemory(); return; }
e->num = PyTuple_GET_SIZE(value);
if (e->num) {
e->features = calloc(e->num, sizeof(e->features[0]));
if (!e->features) { PyErr_NoMemory(); return; }
for (size_t n = 0; n < e->num; n++) {
ParsedFontFeature *f = (ParsedFontFeature*)PyTuple_GET_ITEM(value, n);
e->features[n] = f->feature;
}
}
}
}
static inline MouseShape
pointer_shape(PyObject *shape_name) {
const char *name = PyUnicode_AsUTF8(shape_name);
if (!name) return TEXT_POINTER;
@ -181,7 +220,7 @@ pointer_shape(PyObject *shape_name) {
return TEXT_POINTER;
}
static int
static inline int
macos_colorspace(PyObject *csname) {
if (PyUnicode_CompareWithASCIIString(csname, "srgb") == 0) return 1;
if (PyUnicode_CompareWithASCIIString(csname, "displayp3") == 0) return 2;
@ -198,7 +237,7 @@ free_url_prefixes(Options *opts) {
}
}
static void
static inline void
url_prefixes(PyObject *up, Options *opts) {
if (!PyTuple_Check(up)) { PyErr_SetString(PyExc_TypeError, "url_prefixes must be a tuple"); return; }
free_url_prefixes(opts);
@ -233,7 +272,7 @@ free_menu_map(Options *opts) {
opts->global_menu.count = 0;
}
static void
static inline void
menu_map(PyObject *entry_dict, Options *opts) {
if (!PyDict_Check(entry_dict)) { PyErr_SetString(PyExc_TypeError, "menu_map entries must be a dict"); return; }
free_menu_map(opts);
@ -261,7 +300,7 @@ menu_map(PyObject *entry_dict, Options *opts) {
}
}
static void
static inline void
text_composition_strategy(PyObject *val, Options *opts) {
if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "text_rendering_strategy must be a string"); return; }
opts->text_old_gamma = false;
@ -305,30 +344,30 @@ list_of_chars(PyObject *chars) {
return ans;
}
static void
static inline void
url_excluded_characters(PyObject *chars, Options *opts) {
free(opts->url_excluded_characters);
opts->url_excluded_characters = list_of_chars(chars);
}
static void
static inline void
select_by_word_characters(PyObject *chars, Options *opts) {
free(opts->select_by_word_characters);
opts->select_by_word_characters = list_of_chars(chars);
}
static void
static inline void
select_by_word_characters_forward(PyObject *chars, Options *opts) {
free(opts->select_by_word_characters_forward);
opts->select_by_word_characters_forward = list_of_chars(chars);
}
static void
static inline void
tab_bar_style(PyObject *val, Options *opts) {
opts->tab_bar_hidden = PyUnicode_CompareWithASCIIString(val, "hidden") == 0 ? true: false;
}
static void
static inline void
tab_bar_margin_height(PyObject *val, Options *opts) {
if (!PyTuple_Check(val) || PyTuple_GET_SIZE(val) != 2) {
PyErr_SetString(PyExc_TypeError, "tab_bar_margin_height is not a 2-item tuple");
@ -344,16 +383,17 @@ window_logo_scale(PyObject *src, Options *opts) {
opts->window_logo_scale.height = PyFloat_AsFloat(PyTuple_GET_ITEM(src, 1));
}
static void
static inline void
resize_debounce_time(PyObject *src, Options *opts) {
opts->resize_debounce_time.on_end = s_double_to_monotonic_t(PyFloat_AsDouble(PyTuple_GET_ITEM(src, 0)));
opts->resize_debounce_time.on_pause = s_double_to_monotonic_t(PyFloat_AsDouble(PyTuple_GET_ITEM(src, 1)));
}
static void
static inline void
free_allocs_in_options(Options *opts) {
free_menu_map(opts);
free_url_prefixes(opts);
free_font_features(opts);
#define F(x) free(opts->x); opts->x = NULL;
F(select_by_word_characters); F(url_excluded_characters); F(select_by_word_characters_forward);
F(background_image); F(bell_path); F(bell_theme); F(default_window_logo);

10
kitty/options/types.py generated
View File

@ -488,8 +488,8 @@ class Options:
bell_border_color: Color = Color(255, 90, 0)
bell_on_tab: str = '🔔 '
bell_path: typing.Optional[str] = None
bold_font: FontSpec = FontSpec(family='', style='', postscript_name='', full_name='', system='auto', axes=())
bold_italic_font: FontSpec = FontSpec(family='', style='', postscript_name='', full_name='', system='auto', axes=())
bold_font: FontSpec = FontSpec(family=None, style=None, postscript_name=None, full_name=None, system='auto', axes=(), variable_name=None, features=(), created_from_string='auto')
bold_italic_font: FontSpec = FontSpec(family=None, style=None, postscript_name=None, full_name=None, system='auto', axes=(), variable_name=None, features=(), created_from_string='auto')
box_drawing_scale: typing.Tuple[float, float, float, float] = (0.001, 1.0, 1.5, 2.0)
clear_all_mouse_actions: bool = False
clear_all_shortcuts: bool = False
@ -520,7 +520,7 @@ class Options:
enabled_layouts: typing.List[str] = ['fat', 'grid', 'horizontal', 'splits', 'stack', 'tall', 'vertical']
file_transfer_confirmation_bypass: str = ''
focus_follows_mouse: bool = False
font_family: FontSpec = FontSpec(family='', style='', postscript_name='', full_name='', system='monospace', axes=())
font_family: FontSpec = FontSpec(family=None, style=None, postscript_name=None, full_name=None, system='monospace', axes=(), variable_name=None, features=(), created_from_string='monospace')
font_size: float = 11.0
force_ltr: bool = False
foreground: Color = Color(221, 221, 221)
@ -534,7 +534,7 @@ class Options:
initial_window_height: typing.Tuple[int, str] = (400, 'px')
initial_window_width: typing.Tuple[int, str] = (640, 'px')
input_delay: int = 3
italic_font: FontSpec = FontSpec(family='', style='', postscript_name='', full_name='', system='auto', axes=())
italic_font: FontSpec = FontSpec(family=None, style=None, postscript_name=None, full_name=None, system='auto', axes=(), variable_name=None, features=(), created_from_string='auto')
kitty_mod: int = 5
linux_bell_theme: str = '__custom'
linux_display_server: choices_for_linux_display_server = 'auto'
@ -631,7 +631,7 @@ class Options:
action_alias: typing.Dict[str, str] = {}
env: typing.Dict[str, str] = {}
exe_search_path: typing.Dict[str, str] = {}
font_features: typing.Dict[str, typing.Tuple[kitty.fonts.FontFeature, ...]] = {}
font_features: typing.Dict[str, typing.Tuple[kitty.fast_data_types.ParsedFontFeature, ...]] = {}
kitten_alias: typing.Dict[str, str] = {}
menu_map: typing.Dict[typing.Tuple[str, ...], str] = {}
modify_font: typing.Dict[str, kitty.fonts.FontModification] = {}

View File

@ -45,7 +45,7 @@
)
from kitty.constants import is_macos
from kitty.fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE, NO_CURSOR_SHAPE, Color, Shlex, SingleKey
from kitty.fonts import FontFeature, FontModification, FontSpec, ModificationType, ModificationUnit, ModificationValue
from kitty.fonts import FontModification, FontSpec, ModificationType, ModificationUnit, ModificationValue
from kitty.key_names import character_key_name_aliases, functional_key_name_aliases, get_key_name_lookup
from kitty.rgb import color_as_int
from kitty.types import FloatEdges, MouseEvent
@ -879,7 +879,7 @@ def clear_all_shortcuts(val: str, dict_with_parse_results: Optional[Dict[str, An
return ans
def font_features(val: str) -> Iterable[Tuple[str, Tuple[FontFeature, ...]]]:
def font_features(val: str) -> Iterable[Tuple[str, Tuple[defines.ParsedFontFeature, ...]]]:
if val == 'none':
return
parts = val.split()
@ -890,11 +890,9 @@ def font_features(val: str) -> Iterable[Tuple[str, Tuple[FontFeature, ...]]]:
features = []
for feat in parts[1:]:
try:
parsed = defines.parse_font_feature(feat)
features.append(defines.ParsedFontFeature(feat))
except ValueError:
log_error(f'Ignoring invalid font feature: {feat}')
else:
features.append(FontFeature(feat, parsed))
yield parts[0], tuple(features)

View File

@ -9,6 +9,10 @@
#include "screen.h"
#include "monotonic.h"
#include "window_logo.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#include <hb.h>
#pragma GCC diagnostic pop
#define OPT(name) global_state.opts.name
#define debug_rendering(...) if (global_state.debug_rendering) { timed_debug_print(__VA_ARGS__); }
@ -106,6 +110,14 @@ typedef struct {
unsigned long wayland_titlebar_color;
struct { struct MenuItem *entries; size_t count; } global_menu;
bool wayland_enable_ime;
struct {
size_t num;
struct {
const char *psname;
size_t num;
hb_feature_t *features;
} *entries;
} font_features;
} Options;
typedef struct WindowLogoRenderData {