mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 22:28:24 +02:00
Work on list variable fonts on Linux
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user