mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-06-25 18:37:50 +00:00
Get feature human readable names
This commit is contained in:
parent
a2a0f3cf41
commit
7e56920fa3
9 changed files with 136 additions and 22 deletions
|
|
@ -6,7 +6,7 @@ import os
|
|||
import string
|
||||
import sys
|
||||
import tempfile
|
||||
from typing import Any, Dict, Literal, Tuple, TypedDict
|
||||
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Tuple, TypedDict
|
||||
|
||||
from kitty.cli import create_default_opts
|
||||
from kitty.conf.utils import to_color
|
||||
|
|
@ -22,12 +22,16 @@ from kitty.fonts.common import (
|
|||
is_variable,
|
||||
spec_for_face,
|
||||
)
|
||||
from kitty.fonts.features import Type, known_features
|
||||
from kitty.fonts.list import create_family_groups
|
||||
from kitty.fonts.render import display_bitmap
|
||||
from kitty.options.types import Options
|
||||
from kitty.options.utils import parse_font_spec
|
||||
from kitty.typing import NotRequired
|
||||
from kitty.utils import screen_size_function
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.fast_data_types import FeatureData
|
||||
|
||||
def setup_debug_print() -> bool:
|
||||
if 'KITTY_STDIO_FORWARDED' in os.environ:
|
||||
|
|
@ -86,6 +90,35 @@ RenderedSampleTransmit = Dict[str, Any]
|
|||
SAMPLE_TEXT = string.ascii_lowercase + ' ' + string.digits + ' ' + string.ascii_uppercase + ' ' + string.punctuation
|
||||
|
||||
|
||||
class FD(TypedDict):
|
||||
is_index: bool
|
||||
name: NotRequired[str]
|
||||
tooltip: NotRequired[str]
|
||||
sample: NotRequired[str]
|
||||
params: NotRequired[Tuple[str, ...]]
|
||||
|
||||
|
||||
|
||||
def get_features(features: Dict[str, Optional['FeatureData']]) -> Dict[str, FD]:
|
||||
ans = {}
|
||||
for tag, data in features.items():
|
||||
kf = known_features.get(tag)
|
||||
if kf is None or kf.type is Type.hidden:
|
||||
continue
|
||||
fd: FD = {'is_index': kf.type is Type.index}
|
||||
ans[tag] = fd
|
||||
if data is not None:
|
||||
if n := data.get('name'):
|
||||
fd['name'] = n
|
||||
if n := data.get('tooltip'):
|
||||
fd['tooltip'] = n
|
||||
if n := data.get('sample'):
|
||||
fd['sample'] = n
|
||||
if p := data.get('params'):
|
||||
fd['params'] = p
|
||||
return ans
|
||||
|
||||
|
||||
def render_face_sample(font: Descriptor, opts: Options, dpi_x: float, dpi_y: float, width: int, height: int) -> RenderedSample:
|
||||
face = face_from_descriptor(font)
|
||||
face.set_size(opts.font_size, dpi_x, dpi_y)
|
||||
|
|
@ -93,6 +126,7 @@ def render_face_sample(font: Descriptor, opts: Options, dpi_x: float, dpi_y: flo
|
|||
'variable_data': get_variable_data_for_face(face),
|
||||
'style': font['style'],
|
||||
'psname': face.postscript_name(),
|
||||
'features': get_features(face.get_features()),
|
||||
}
|
||||
if is_variable(font):
|
||||
ns = get_named_style(face)
|
||||
|
|
|
|||
|
|
@ -87,13 +87,21 @@ type ListResult struct {
|
|||
Resolved_faces ResolvedFaces `json:"resolved_faces"`
|
||||
}
|
||||
|
||||
type FeatureData struct {
|
||||
Name string `json:"name"`
|
||||
Tooltip string `json:"tooltip"`
|
||||
Sample string `json:"sample"`
|
||||
Params []string `json:"params"`
|
||||
}
|
||||
|
||||
type RenderedSampleTransmit struct {
|
||||
Path string `json:"path"`
|
||||
Variable_data VariableData `json:"variable_data"`
|
||||
Style string `json:"style"`
|
||||
Psname string `json:"psname"`
|
||||
Variable_named_style NamedStyle `json:"variable_named_style"`
|
||||
Variable_axis_map map[string]float64 `json:"variable_axis_map"`
|
||||
Path string `json:"path"`
|
||||
Variable_data VariableData `json:"variable_data"`
|
||||
Style string `json:"style"`
|
||||
Psname string `json:"psname"`
|
||||
Features map[string]FeatureData `json:"features"`
|
||||
Variable_named_style NamedStyle `json:"variable_named_style"`
|
||||
Variable_axis_map map[string]float64 `json:"variable_axis_map"`
|
||||
}
|
||||
|
||||
func (self RenderedSampleTransmit) default_axis_values() (ans map[string]float64) {
|
||||
|
|
|
|||
|
|
@ -948,15 +948,16 @@ get_variation(CTFace *self) {
|
|||
|
||||
static PyObject*
|
||||
get_features(CTFace *self, PyObject *a UNUSED) {
|
||||
RAII_PyObject(output, PyFrozenSet_New(NULL)); if (!output) return NULL;
|
||||
if (!ensure_name_table(self)) return NULL;
|
||||
RAII_PyObject(output, PyDict_New()); if (!output) return NULL;
|
||||
RAII_CoreFoundation(CFDataRef, cftable, CTFontCopyTable(self->ct_font, kCTFontTableGSUB, kCTFontTableOptionNoOptions));
|
||||
const uint8_t *table = cftable ? CFDataGetBytePtr(cftable) : NULL;
|
||||
size_t table_len = cftable ? CFDataGetLength(cftable) : 0;
|
||||
if (!read_features_from_font_table(table, table_len, output)) return NULL;
|
||||
if (!read_features_from_font_table(table, table_len, self->name_lookup_table, output)) return NULL;
|
||||
RAII_CoreFoundation(CFDataRef, cfpostable, CTFontCopyTable(self->ct_font, kCTFontTableGPOS, kCTFontTableOptionNoOptions));
|
||||
table = cfpostable ? CFDataGetBytePtr(cfpostable) : NULL;
|
||||
table_len = cfpostable ? CFDataGetLength(cfpostable) : 0;
|
||||
if (!read_features_from_font_table(table, table_len, output)) return NULL;
|
||||
if (!read_features_from_font_table(table, table_len, self->name_lookup_table, output)) return NULL;
|
||||
Py_INCREF(output); return output;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import termios
|
||||
from ctypes import Array, c_ubyte
|
||||
from typing import Any, Callable, Dict, Iterator, List, Literal, NewType, Optional, Tuple, TypedDict, Union, overload, FrozenSet
|
||||
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
|
||||
|
|
@ -424,6 +424,13 @@ def fc_match_postscript_name(
|
|||
pass
|
||||
|
||||
|
||||
class FeatureData(TypedDict):
|
||||
name: NotRequired[str]
|
||||
tooltip: NotRequired[str]
|
||||
sample: NotRequired[str]
|
||||
params: NotRequired[Tuple[str, ...]]
|
||||
|
||||
|
||||
class Face:
|
||||
path: Optional[str]
|
||||
def __init__(self, descriptor: Optional[FontConfigPattern] = None, path: str = '', index: int = 0): ...
|
||||
|
|
@ -433,7 +440,7 @@ class Face:
|
|||
def set_size(self, sz_in_pts: float, dpi_x: float, dpi_y: float) -> None: ...
|
||||
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) -> FrozenSet[str]: ...
|
||||
def get_features(self) -> Dict[str, Optional[FeatureData]]: ...
|
||||
|
||||
|
||||
class CoreTextFont(TypedDict):
|
||||
|
|
@ -468,7 +475,7 @@ class CTFace:
|
|||
def set_size(self, sz_in_pts: float, dpi_x: float, dpi_y: float) -> None: ...
|
||||
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) -> FrozenSet[str]: ...
|
||||
def get_features(self) -> Dict[str, Optional[FeatureData]]: ...
|
||||
|
||||
|
||||
def coretext_all_fonts(monospaced_only: bool) -> Tuple[CoreTextFont, ...]:
|
||||
|
|
|
|||
|
|
@ -119,8 +119,53 @@ read_name_font_table(const uint8_t *table, size_t table_len) {
|
|||
|
||||
}
|
||||
|
||||
static bool is_digit(char x) { return '0' <= x && x <= '9'; }
|
||||
|
||||
static PyObject*
|
||||
read_cv_feature_table(const uint8_t *table, const uint8_t *limit, PyObject *name_lookup_table) {
|
||||
RAII_PyObject(ans, PyDict_New()); if (!ans) return NULL;
|
||||
if (limit - table >= 12) {
|
||||
uint16_t *p = (uint16_t*)(table + 2);
|
||||
uint16_t name_id = next, tooltip_id = next, sample_id = next, num_params = next, first_value_id = next;
|
||||
if (name_id) {
|
||||
RAII_PyObject(name, get_best_name(name_lookup_table, name_id)); if (!name) return NULL;
|
||||
if (PyDict_SetItemString(ans, "name", name) != 0) return NULL;
|
||||
}
|
||||
if (tooltip_id) {
|
||||
RAII_PyObject(tooltip, get_best_name(name_lookup_table, tooltip_id)); if (!tooltip) return NULL;
|
||||
if (PyDict_SetItemString(ans, "tooltip", tooltip) != 0) return NULL;
|
||||
}
|
||||
if (sample_id) {
|
||||
RAII_PyObject(sample, get_best_name(name_lookup_table, sample_id)); if (!sample) return NULL;
|
||||
if (PyDict_SetItemString(ans, "sample", sample) != 0) return NULL;
|
||||
}
|
||||
if (num_params && first_value_id) {
|
||||
RAII_PyObject(params, PyTuple_New(num_params)); if (!params) return NULL;
|
||||
for (uint16_t i = 0; i < num_params; i++) {
|
||||
PyObject *pval = get_best_name(name_lookup_table, first_value_id + i); if (!pval) return NULL;
|
||||
PyTuple_SET_ITEM(params, i, pval);
|
||||
}
|
||||
if (PyDict_SetItemString(ans, "params", params) != 0) return NULL;
|
||||
}
|
||||
}
|
||||
Py_INCREF(ans); return ans;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
read_ss_feature_table(const uint8_t *table, const uint8_t *limit, PyObject *name_lookup_table) {
|
||||
RAII_PyObject(ans, PyDict_New()); if (!ans) return NULL;
|
||||
if (limit - table < 4) { Py_INCREF(ans); return ans; }
|
||||
uint16_t *p = (uint16_t*)(table + 2);
|
||||
uint16_t name_id = next;
|
||||
if (name_id) {
|
||||
RAII_PyObject(name, get_best_name(name_lookup_table, name_id)); if (!name) return NULL;
|
||||
if (PyDict_SetItemString(ans, "name", name) != 0) return NULL;
|
||||
}
|
||||
Py_INCREF(ans); return ans;
|
||||
}
|
||||
|
||||
bool
|
||||
read_features_from_font_table(const uint8_t *table, size_t table_len, PyObject *output) {
|
||||
read_features_from_font_table(const uint8_t *table, size_t table_len, PyObject *name_lookup_table, PyObject *output) {
|
||||
if (table_len < 20) return true;
|
||||
const uint16_t *p = (uint16_t*)table;
|
||||
const uint8_t *limit = table + table_len;
|
||||
|
|
@ -132,11 +177,29 @@ read_features_from_font_table(const uint8_t *table, size_t table_len, PyObject *
|
|||
p = (uint16_t*)feature_list_table;
|
||||
uint16_t feature_count = next;
|
||||
const uint8_t *pos = (uint8_t*)p;
|
||||
for (uint16_t i = 0; i < feature_count && pos + 4 < limit; pos += 6, i++) {
|
||||
for (uint16_t i = 0; i < feature_count && pos + 6 <= limit; pos += 6, i++) {
|
||||
memcpy(tag_buf, pos, 4);
|
||||
RAII_PyObject(tag, PyUnicode_FromString(tag_buf));
|
||||
if (!tag) return false;
|
||||
if (PySet_Add(output, tag) != 0) return false;
|
||||
if (PyDict_Contains(output, tag) == 1) continue;
|
||||
if (PyDict_SetItem(output, tag, Py_None) != 0) return false;
|
||||
p = (uint16_t*)(pos + 4); uint16_t offset_to_feature_table = next;
|
||||
const uint8_t *feature_table = feature_list_table + offset_to_feature_table;
|
||||
if (feature_table + 2 > limit) continue;
|
||||
p = (uint16_t*)(feature_table); uint16_t offset_to_feature_params_table = next;
|
||||
if (tag_buf[0] == 'c' && tag_buf[1] == 'v' && is_digit(tag_buf[2]) && is_digit(tag_buf[3])) {
|
||||
if (offset_to_feature_params_table) {
|
||||
RAII_PyObject(cv, read_cv_feature_table(feature_table + offset_to_feature_params_table, limit, name_lookup_table));
|
||||
if (!cv) return false;
|
||||
if (PyDict_SetItem(output, tag, cv) != 0) return false;
|
||||
}
|
||||
} else if (tag_buf[0] == 's' && tag_buf[1] == 's' && '0' <= tag_buf[2] && tag_buf[2] <= '2' && is_digit(tag_buf[3])) {
|
||||
if (offset_to_feature_params_table) {
|
||||
RAII_PyObject(ss, read_ss_feature_table(feature_table + offset_to_feature_params_table, limit, name_lookup_table));
|
||||
if (!ss) return false;
|
||||
if (PyDict_SetItem(output, tag, ss) != 0) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ read_fvar_font_table(const uint8_t *table, size_t table_len, PyObject *name_look
|
|||
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 *output);
|
||||
read_features_from_font_table(const uint8_t *table, size_t table_len, PyObject *name_lookup_table, PyObject *output);
|
||||
|
||||
static inline void
|
||||
right_shift_canvas(pixel *canvas, size_t width, size_t height, size_t amt) {
|
||||
|
|
|
|||
|
|
@ -474,7 +474,7 @@ def develop(family: str = '') -> None:
|
|||
f = face_from_descriptor(d)
|
||||
print(name, str(f))
|
||||
features = f.get_features()
|
||||
print(' Features :', ' '.join(sorted(features)))
|
||||
print(' Features :', features)
|
||||
|
||||
s('Medium :', ff['medium'])
|
||||
print()
|
||||
|
|
|
|||
|
|
@ -846,14 +846,15 @@ static PyObject*
|
|||
get_features(Face *self, PyObject *a UNUSED) {
|
||||
FT_Error err;
|
||||
FT_ULong length = 0;
|
||||
RAII_PyObject(output, PyFrozenSet_New(NULL)); if (!output) return NULL;
|
||||
if (!ensure_name_table(self)) return NULL;
|
||||
RAII_PyObject(output, PyDict_New()); if (!output) return NULL;
|
||||
if ((err = FT_Load_Sfnt_Table(self->face, FT_MAKE_TAG('G', 'S', 'U', 'B'), 0, NULL, &length)) == 0) {
|
||||
RAII_ALLOC(uint8_t, table, malloc(length));
|
||||
if (!table) return PyErr_NoMemory();
|
||||
if ((err = FT_Load_Sfnt_Table(self->face, FT_MAKE_TAG('G', 'S', 'U', 'B'), 0, table, &length))) {
|
||||
set_freetype_error("Failed to load the GSUB table from font with error:", err); return NULL;
|
||||
}
|
||||
if (!read_features_from_font_table(table, length, output)) return NULL;
|
||||
if (!read_features_from_font_table(table, length, self->name_lookup_table, output)) return NULL;
|
||||
}
|
||||
length = 0;
|
||||
if ((err = FT_Load_Sfnt_Table(self->face, FT_MAKE_TAG('G', 'P', 'O', 'S'), 0, NULL, &length)) == 0) {
|
||||
|
|
@ -862,7 +863,7 @@ get_features(Face *self, PyObject *a UNUSED) {
|
|||
if ((err = FT_Load_Sfnt_Table(self->face, FT_MAKE_TAG('G', 'P', 'O', 'S'), 0, table, &length))) {
|
||||
set_freetype_error("Failed to load the GSUB table from font with error:", err); return NULL;
|
||||
}
|
||||
if (!read_features_from_font_table(table, length, output)) return NULL;
|
||||
if (!read_features_from_font_table(table, length, self->name_lookup_table, output)) return NULL;
|
||||
}
|
||||
Py_INCREF(output); return output;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,4 +21,4 @@ PowerlineStyle = str
|
|||
MatchType = str
|
||||
Protocol = object
|
||||
OptionsProtocol = object
|
||||
NotRequired = object
|
||||
NotRequired = Tuple
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue