Work on list variable fonts on Linux

This commit is contained in:
Kovid Goyal
2024-04-19 15:12:32 +05:30
parent 3ba2492e99
commit 54fb6be112
6 changed files with 66 additions and 25 deletions

View File

@@ -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

View File

@@ -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; }

View File

@@ -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:

View File

@@ -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)

View File

@@ -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)

View File

@@ -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,