mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-10-21 20:52:19 +00:00
work on passing font features via font specs
This commit is contained in:
parent
7e56920fa3
commit
060732b428
14 changed files with 270 additions and 113 deletions
|
|
@ -33,6 +33,7 @@ typedef struct {
|
|||
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 @@ get_path_for_font_descriptor(CTFontDescriptorRef font) {
|
|||
|
||||
|
||||
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 @@ ct_face(CTFontRef font) {
|
|||
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 @@ dealloc(CTFace* self) {
|
|||
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 @@ string_to_tag(const uint8_t *bytes) {
|
|||
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 @@ create_fallback_face(PyObject *base_face, CPUCell* cell, bool bold, bool italic,
|
|||
break;
|
||||
}
|
||||
}
|
||||
return ans ? ans : (PyObject*)ct_face(new_font);
|
||||
return ans ? ans : (PyObject*)ct_face(new_font, NULL);
|
||||
}
|
||||
|
||||
unsigned int
|
||||
|
|
@ -590,7 +597,7 @@ face_from_descriptor(PyObject *descriptor, FONTS_DATA_HANDLE fg) {
|
|||
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 @@ face_from_path(const char *path, int UNUSED index, FONTS_DATA_HANDLE fg UNUSED)
|
|||
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*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
171
kitty/fonts.c
171
kitty/fonts.c
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 @@ class FontSpec(NamedTuple):
|
|||
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 @@ class FontSpec(NamedTuple):
|
|||
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}')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -132,10 +132,8 @@ Note that this refers to programming ligatures, typically implemented using the
|
|||
'''
|
||||
)
|
||||
|
||||
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
|
||||
|
|
|
|||
2
kitty/options/parse.py
generated
2
kitty/options/parse.py
generated
|
|
@ -18,7 +18,7 @@ from kitty.options.utils import (
|
|||
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
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
15
kitty/options/to-c-generated.h
generated
15
kitty/options/to-c-generated.h
generated
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
10
kitty/options/types.py
generated
|
|
@ -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] = {}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ from kitty.conf.utils import (
|
|||
)
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue