mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-18 22:44:50 +02:00
Get variable font selection working on coretext
This commit is contained in:
@@ -111,6 +111,45 @@ dealloc(CTFace* self) {
|
||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||
}
|
||||
|
||||
static const char*
|
||||
tag_to_string(uint32_t tag, uint8_t bytes[5]) {
|
||||
bytes[0] = (tag >> 24) & 0xff;
|
||||
bytes[1] = (tag >> 16) & 0xff;
|
||||
bytes[2] = (tag >> 8) & 0xff;
|
||||
bytes[3] = (tag) & 0xff;
|
||||
bytes[4] = 0;
|
||||
return (const char*)bytes;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
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];
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
add_variation_pair(const void *key_, const void *value_, void *ctx) {
|
||||
PyObject *ans = ctx;
|
||||
CFNumberRef key = key_, value = value_;
|
||||
uint32_t tag; double val;
|
||||
if (!CFNumberGetValue(key, kCFNumberSInt32Type, &tag)) return;
|
||||
if (!CFNumberGetValue(value, kCFNumberDoubleType, &val)) return;
|
||||
uint8_t tag_string[5];
|
||||
tag_to_string(tag, tag_string);
|
||||
RAII_PyObject(pyval, PyFloat_FromDouble(val));
|
||||
if (pyval) PyDict_SetItemString(ans, (const char*)tag_string, pyval);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
variation_to_python(CFDictionaryRef v) {
|
||||
if (!v) { Py_RETURN_NONE; }
|
||||
RAII_PyObject(ans, PyDict_New());
|
||||
if (!ans) return NULL;
|
||||
CFDictionaryApplyFunction(v, add_variation_pair, ans);
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
Py_INCREF(ans); return ans;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
font_descriptor_to_python(CTFontDescriptorRef descriptor) {
|
||||
RAII_PyObject(path, get_path_for_font_descriptor(descriptor));
|
||||
@@ -127,9 +166,12 @@ font_descriptor_to_python(CTFontDescriptorRef descriptor) {
|
||||
get_number(traits, kCTFontWeightTrait, weight, kCFNumberFloatType);
|
||||
get_number(traits, kCTFontWidthTrait, width, kCFNumberFloatType);
|
||||
get_number(traits, kCTFontSlantTrait, slant, kCFNumberFloatType);
|
||||
RAII_CoreFoundation(CFDictionaryRef, variation, CTFontDescriptorCopyAttribute(descriptor, kCTFontVariationAttribute));
|
||||
RAII_CoreFoundation(CFDictionaryRef, cf_variation, CTFontDescriptorCopyAttribute(descriptor, kCTFontVariationAttribute));
|
||||
RAII_PyObject(variation, variation_to_python(cf_variation));
|
||||
if (!variation) return NULL;
|
||||
#undef get_number
|
||||
|
||||
|
||||
PyObject *ans = Py_BuildValue("{ss sOsOsOsOsO sOsOsOsOsOsOsO sfsfsfsk}",
|
||||
"descriptor_type", "core_text",
|
||||
|
||||
@@ -141,7 +183,7 @@ font_descriptor_to_python(CTFontDescriptorRef descriptor) {
|
||||
"expanded", (symbolic_traits & kCTFontExpandedTrait) != 0 ? Py_True : Py_False,
|
||||
"condensed", (symbolic_traits & kCTFontCondensedTrait) != 0 ? Py_True : Py_False,
|
||||
"color_glyphs", (symbolic_traits & kCTFontColorGlyphsTrait) != 0 ? Py_True : Py_False,
|
||||
"variable", variation ? Py_True : Py_False,
|
||||
"variation", variation,
|
||||
|
||||
"weight", weight, "width", width, "slant", slant, "traits", symbolic_traits
|
||||
);
|
||||
@@ -151,7 +193,7 @@ font_descriptor_to_python(CTFontDescriptorRef descriptor) {
|
||||
static CTFontDescriptorRef
|
||||
font_descriptor_from_python(PyObject *src) {
|
||||
CTFontSymbolicTraits symbolic_traits = 0;
|
||||
NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
|
||||
RAII_CoreFoundation(CFMutableDictionaryRef, ans, CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
|
||||
PyObject *t = PyDict_GetItemString(src, "traits");
|
||||
if (t == NULL) {
|
||||
symbolic_traits = (
|
||||
@@ -161,19 +203,33 @@ font_descriptor_from_python(PyObject *src) {
|
||||
} else {
|
||||
symbolic_traits = PyLong_AsUnsignedLong(t);
|
||||
}
|
||||
NSDictionary *traits = @{(id)kCTFontSymbolicTrait:[NSNumber numberWithUnsignedInt:symbolic_traits]};
|
||||
attrs[(id)kCTFontTraitsAttribute] = traits;
|
||||
RAII_CoreFoundation(CFNumberRef, cf_symbolic_traits, CFNumberCreate(NULL, kCFNumberSInt32Type, &symbolic_traits));
|
||||
CFTypeRef keys[] = { kCTFontSymbolicTrait };
|
||||
CFTypeRef values[] = { cf_symbolic_traits };
|
||||
RAII_CoreFoundation(CFDictionaryRef, traits, CFDictionaryCreate(NULL, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
|
||||
CFDictionaryAddValue(ans, kCTFontTraitsAttribute, traits);
|
||||
|
||||
#define SET(x, attr) \
|
||||
t = PyDict_GetItemString(src, #x); \
|
||||
if (t) attrs[(id)attr] = @(PyUnicode_AsUTF8(t));
|
||||
#define SET(x, attr) if ((t = PyDict_GetItemString(src, #x))) { \
|
||||
RAII_CoreFoundation(CFStringRef, cs, CFStringCreateWithCString(NULL, PyUnicode_AsUTF8(t), kCFStringEncodingUTF8)); \
|
||||
CFDictionaryAddValue(ans, attr, cs); }
|
||||
|
||||
SET(family, kCTFontFamilyNameAttribute);
|
||||
SET(style, kCTFontStyleNameAttribute);
|
||||
SET(postscript_name, kCTFontNameAttribute);
|
||||
#undef SET
|
||||
|
||||
return CTFontDescriptorCreateWithAttributes((CFDictionaryRef) attrs);
|
||||
if ((t = PyDict_GetItemString(src, "axis_map"))) {
|
||||
RAII_CoreFoundation(CFMutableDictionaryRef, axis_map, CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
|
||||
PyObject *key, *value; Py_ssize_t pos = 0;
|
||||
while (PyDict_Next(t, &pos, &key, &value)) {
|
||||
double val = PyFloat_AS_DOUBLE(value);
|
||||
uint32_t tag = string_to_tag((const uint8_t*)PyUnicode_AsUTF8(key));
|
||||
RAII_CoreFoundation(CFNumberRef, cf_tag, CFNumberCreate(NULL, kCFNumberSInt32Type, &tag));
|
||||
RAII_CoreFoundation(CFNumberRef, cf_val, CFNumberCreate(NULL, kCFNumberDoubleType, &val));
|
||||
CFDictionaryAddValue(axis_map, cf_tag, cf_val);
|
||||
}
|
||||
CFDictionaryAddValue(ans, kCTFontVariationAttribute, axis_map);
|
||||
}
|
||||
return CTFontDescriptorCreateWithAttributes(ans);
|
||||
}
|
||||
|
||||
static CTFontCollectionRef all_fonts_collection_data = NULL;
|
||||
|
||||
@@ -397,7 +397,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[List[float]]
|
||||
axes: NotRequired[Tuple[float, ...]]
|
||||
|
||||
|
||||
def fc_list(spacing: int = -1, allow_bitmapped_fonts: bool = False, only_variable: bool = False) -> Tuple[FontConfigPattern, ...]:
|
||||
@@ -444,15 +444,14 @@ class CoreTextFont(TypedDict):
|
||||
condensed: bool
|
||||
color_glyphs: bool
|
||||
monospace: bool
|
||||
variable: bool
|
||||
variation: Optional[Dict[str, float]]
|
||||
weight: float
|
||||
width: float
|
||||
slant: float
|
||||
traits: int
|
||||
|
||||
# The following two are used by C code to get a face from the pattern
|
||||
named_style: NotRequired[int]
|
||||
axes: NotRequired[List[float]]
|
||||
# The following is used by C code to get a face from the pattern
|
||||
axis_map: NotRequired[Dict[str, float]]
|
||||
|
||||
|
||||
class CTFace:
|
||||
|
||||
@@ -138,6 +138,7 @@ class FontSpec(NamedTuple):
|
||||
full_name: str = ''
|
||||
system: str = ''
|
||||
axes: Tuple[Tuple[str, float], ...] = ()
|
||||
variable_name: str = ''
|
||||
|
||||
@property
|
||||
def is_system(self) -> bool:
|
||||
|
||||
@@ -23,14 +23,35 @@ if TYPE_CHECKING:
|
||||
def find_last_resort_text_font(bold: bool = False, italic: bool = False, monospaced: bool = True) -> Descriptor: ...
|
||||
def face_from_descriptor(descriptor: Descriptor) -> Face: ...
|
||||
def is_monospace(descriptor: Descriptor) -> bool: ...
|
||||
def is_variable(descriptor: Descriptor) -> bool: ...
|
||||
def set_named_style(name: str, font: Descriptor, vd: VariableData) -> bool: ...
|
||||
def set_axis_values(tag_map: Dict[str, float], font: Descriptor, vd: VariableData) -> bool: ...
|
||||
else:
|
||||
FontCollectionMapType = FontMap = None
|
||||
if is_macos:
|
||||
from kitty.fast_data_types import CTFace as Face
|
||||
from kitty.fonts.core_text import all_fonts_map, create_scorer, find_best_match, find_last_resort_text_font, is_monospace
|
||||
from kitty.fonts.core_text import (
|
||||
all_fonts_map,
|
||||
create_scorer,
|
||||
find_best_match,
|
||||
find_last_resort_text_font,
|
||||
is_monospace,
|
||||
is_variable,
|
||||
set_axis_values,
|
||||
set_named_style,
|
||||
)
|
||||
else:
|
||||
from kitty.fast_data_types import Face
|
||||
from kitty.fonts.fontconfig import all_fonts_map, create_scorer, find_best_match, find_last_resort_text_font, is_monospace
|
||||
from kitty.fonts.fontconfig import (
|
||||
all_fonts_map,
|
||||
create_scorer,
|
||||
find_best_match,
|
||||
find_last_resort_text_font,
|
||||
is_monospace,
|
||||
is_variable,
|
||||
set_axis_values,
|
||||
set_named_style,
|
||||
)
|
||||
def face_from_descriptor(descriptor: Descriptor) -> Face: return Face(descriptor=descriptor)
|
||||
|
||||
|
||||
@@ -70,9 +91,9 @@ def find_medium_variant(font: Descriptor) -> Descriptor:
|
||||
vd = get_variable_data_for_descriptor(font)
|
||||
for i, ns in enumerate(vd['named_styles']):
|
||||
if ns['name'] == 'Regular':
|
||||
font['named_style'] = i
|
||||
set_named_style(ns['psname'], font, vd)
|
||||
return font
|
||||
font['axes'] = axes = [ax['default'] for ax in vd['axes']]
|
||||
axis_values = {}
|
||||
for i, ax in enumerate(vd['axes']):
|
||||
tag = ax['tag']
|
||||
for dax in vd['design_axes']:
|
||||
@@ -80,8 +101,10 @@ def find_medium_variant(font: Descriptor) -> Descriptor:
|
||||
for x in dax['values']:
|
||||
if x['format'] in (1, 2):
|
||||
if x['name'] == 'Regular':
|
||||
axes[i] = x['value']
|
||||
axis_values[tag] = x['value']
|
||||
break
|
||||
if axis_values:
|
||||
set_axis_values(axis_values, font, vd)
|
||||
return font
|
||||
|
||||
|
||||
@@ -100,28 +123,51 @@ def get_design_value_for(dax: DesignAxis, default: float, bold: bool, italic: bo
|
||||
|
||||
|
||||
def find_bold_italic_variant(medium: Descriptor, bold: bool, italic: bool) -> Descriptor:
|
||||
key = family_name_to_key(medium['family'])
|
||||
monospaced = is_monospace(medium)
|
||||
# we first pick the best font file for bold/italic if there are more than
|
||||
# one. For example SourceCodeVF has Italic and Upright faces with variable
|
||||
# weights in each, so we rely on the OS font matcher to give us the best
|
||||
# font file.
|
||||
fonts = all_fonts_map(monospaced)['variable_map'][key]
|
||||
monospaced = is_monospace(medium)
|
||||
fonts = all_fonts_map(monospaced)['variable_map'][family_name_to_key(medium['family'])]
|
||||
scorer = create_scorer(bold, italic, monospaced)
|
||||
fonts.sort(key=scorer)
|
||||
vd = get_variable_data_for_descriptor(fonts[0])
|
||||
ans = fonts[0].copy()
|
||||
# now we need to specialise all axes in ans
|
||||
vd = get_variable_data_for_descriptor(ans)
|
||||
ans['axes'] = axes = [ax['default'] for ax in vd['axes']]
|
||||
axis_values = {}
|
||||
for i, ax in enumerate(vd['axes']):
|
||||
tag = ax['tag']
|
||||
for dax in vd['design_axes']:
|
||||
if dax['tag'] == tag:
|
||||
axes[i] = get_design_value_for(dax, axes[i], bold, italic)
|
||||
axis_values[tag] = get_design_value_for(dax, ax['default'], bold, italic)
|
||||
break
|
||||
if axis_values:
|
||||
set_axis_values(axis_values, ans, vd)
|
||||
return ans
|
||||
|
||||
|
||||
def find_best_variable_face(spec: FontSpec, bold: bool, italic: bool, monospaced: bool, candidates: List[Descriptor]) -> Descriptor:
|
||||
if spec.variable_name:
|
||||
q = spec.variable_name.lower()
|
||||
for font in candidates:
|
||||
vd = get_variable_data_for_descriptor(font)
|
||||
if vd['variations_postscript_name_prefix'].lower() == q:
|
||||
return font
|
||||
if spec.style:
|
||||
q = spec.style.lower()
|
||||
for font in candidates:
|
||||
vd = get_variable_data_for_descriptor(font)
|
||||
for x in vd['named_styles']:
|
||||
if x['psname'].lower() == q:
|
||||
return font
|
||||
for x in vd['named_styles']:
|
||||
if x['name'].lower() == q:
|
||||
return font
|
||||
scorer = create_scorer(bold, italic, monospaced)
|
||||
candidates.sort(key=scorer)
|
||||
return candidates[0]
|
||||
|
||||
|
||||
def get_fine_grained_font(
|
||||
spec: FontSpec, bold: bool = False, italic: bool = False, medium_font_spec: FontSpec = FontSpec(),
|
||||
resolved_medium_font: Optional[Descriptor] = None, monospaced: bool = True
|
||||
@@ -142,8 +188,7 @@ def get_fine_grained_font(
|
||||
# First look for a variable font
|
||||
candidates = font_map['variable_map'].get(key, [])
|
||||
if candidates:
|
||||
candidates.sort(key=scorer)
|
||||
q = candidates[0]
|
||||
q = candidates[0] if len(candidates) == 1 else find_best_variable_face(spec, bold, italic, monospaced, candidates)
|
||||
q, applied = apply_variation_to_pattern(q, spec)
|
||||
if applied:
|
||||
return q
|
||||
@@ -162,42 +207,26 @@ def get_fine_grained_font(
|
||||
|
||||
|
||||
def apply_variation_to_pattern(pat: Descriptor, spec: FontSpec) -> Tuple[Descriptor, bool]:
|
||||
if not pat['variable']:
|
||||
return pat, False
|
||||
|
||||
vd = face_from_descriptor(pat).get_variable_data()
|
||||
pat = pat.copy()
|
||||
if spec.style:
|
||||
q = spec.style.lower()
|
||||
for i, ns in enumerate(vd['named_styles']):
|
||||
if ns['psname'].lower() == q:
|
||||
pat = pat.copy()
|
||||
pat['named_style'] = i
|
||||
return pat, True
|
||||
else:
|
||||
for i, ns in enumerate(vd['named_styles']):
|
||||
if ns['name'].lower() == q:
|
||||
pat = pat.copy()
|
||||
pat['named_style'] = i
|
||||
return pat, True
|
||||
if set_named_style(spec.style, pat, vd):
|
||||
return pat, True
|
||||
tag_map, name_map = {}, {}
|
||||
axes = [ax['default'] for ax in vd['axes']]
|
||||
for i, ax in enumerate(vd['axes']):
|
||||
tag_map[ax['tag']] = i
|
||||
if ax['strid']:
|
||||
name_map[ax['strid'].lower()] = i
|
||||
changed = False
|
||||
name_map[ax['strid'].lower()] = ax['tag']
|
||||
axis_values = {}
|
||||
for axspec in spec.axes:
|
||||
qname = axspec[0]
|
||||
axis = tag_map.get(qname)
|
||||
if axis is None:
|
||||
axis = name_map.get(qname.lower())
|
||||
if axis is not None:
|
||||
axes[axis] = axspec[1]
|
||||
changed = True
|
||||
if changed:
|
||||
pat = pat.copy()
|
||||
pat['axes'] = axes
|
||||
return pat, changed
|
||||
if qname in tag_map:
|
||||
axis_values[qname] = axspec[1]
|
||||
continue
|
||||
tag = name_map.get(qname.lower())
|
||||
if tag:
|
||||
axis_values[tag] = axspec[1]
|
||||
return pat, set_axis_values(axis_values, pat, vd)
|
||||
|
||||
|
||||
def get_font_from_spec(
|
||||
@@ -211,7 +240,7 @@ def get_font_from_spec(
|
||||
if bold or italic:
|
||||
assert resolved_medium_font is not None
|
||||
family = resolved_medium_font['family']
|
||||
if resolved_medium_font['variable']:
|
||||
if is_variable(resolved_medium_font):
|
||||
v = find_bold_italic_variant(resolved_medium_font, bold, italic)
|
||||
if v is not None:
|
||||
return v
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import itertools
|
||||
import operator
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from functools import lru_cache
|
||||
from typing import Dict, Generator, Iterable, List, Optional, Tuple
|
||||
|
||||
@@ -11,7 +14,7 @@ from kitty.options.types import Options
|
||||
from kitty.typing import CoreTextFont
|
||||
from kitty.utils import log_error
|
||||
|
||||
from . import Descriptor, ListedFont, Score, Scorer
|
||||
from . import Descriptor, ListedFont, Score, Scorer, VariableData
|
||||
|
||||
attr_map = {(False, False): 'font_family',
|
||||
(True, False): 'bold_font',
|
||||
@@ -24,6 +27,7 @@ FontMap = Dict[str, Dict[str, List[CoreTextFont]]]
|
||||
|
||||
def create_font_map(all_fonts: Iterable[CoreTextFont]) -> FontMap:
|
||||
ans: FontMap = {'family_map': {}, 'ps_map': {}, 'full_map': {}, 'variable_map': {}}
|
||||
vmap: Dict[str, List[CoreTextFont]] = defaultdict(list)
|
||||
for x in all_fonts:
|
||||
f = (x['family'] or '').lower()
|
||||
s = (x['style'] or '').lower()
|
||||
@@ -31,8 +35,19 @@ def create_font_map(all_fonts: Iterable[CoreTextFont]) -> FontMap:
|
||||
ans['family_map'].setdefault(f, []).append(x)
|
||||
ans['ps_map'].setdefault(ps, []).append(x)
|
||||
ans['full_map'].setdefault(f'{f} {s}', []).append(x)
|
||||
if x['variable']:
|
||||
ans['variable_map'].setdefault(f, []).append(x)
|
||||
if x['variation'] is not None:
|
||||
vmap[f].append(x)
|
||||
# CoreText makes a separate descriptor for each named style in each
|
||||
# variable font file. Keep only the default style descriptor, which has an
|
||||
# empty variation dictionary. If no default exists, pick the one with the
|
||||
# smallest variation dictionary size.
|
||||
keyfunc = operator.itemgetter('path')
|
||||
for k, v in vmap.items():
|
||||
v.sort(key=keyfunc)
|
||||
uniq_per_path = []
|
||||
for _, g in itertools.groupby(v, keyfunc):
|
||||
uniq_per_path.append(sorted(g, key=lambda x: len(x['variation'] or ()))[0])
|
||||
ans['variable_map'][k] = uniq_per_path
|
||||
return ans
|
||||
|
||||
|
||||
@@ -45,6 +60,10 @@ def is_monospace(descriptor: CoreTextFont) -> bool:
|
||||
return descriptor['monospace']
|
||||
|
||||
|
||||
def is_variable(descriptor: CoreTextFont) -> bool:
|
||||
return descriptor['variation'] is not None
|
||||
|
||||
|
||||
def list_fonts() -> Generator[ListedFont, None, None]:
|
||||
for fd in coretext_all_fonts(False):
|
||||
f = fd['family']
|
||||
@@ -53,14 +72,14 @@ def list_fonts() -> Generator[ListedFont, None, None]:
|
||||
if not fn:
|
||||
fn = f'{f} {fd["style"]}'.strip()
|
||||
yield {'family': f, 'full_name': fn, 'postscript_name': fd['postscript_name'] or '', 'is_monospace': fd['monospace'],
|
||||
'is_variable': fd['variable'], 'descriptor': fd, 'style': fd['style']}
|
||||
'is_variable': is_variable(fd), 'descriptor': fd, 'style': fd['style']}
|
||||
|
||||
|
||||
def create_scorer(bold: bool = False, italic: bool = False, monospaced: bool = True, prefer_variable: bool = False) -> Scorer:
|
||||
|
||||
def score(candidate: Descriptor) -> Score:
|
||||
assert candidate['descriptor_type'] == 'core_text'
|
||||
variable_score = 0 if prefer_variable and candidate['variable'] else 1
|
||||
variable_score = 0 if prefer_variable and candidate['variation'] is not None else 1
|
||||
style_match = 1 if candidate['bold'] == bold and candidate[
|
||||
'italic'
|
||||
] == italic else 0
|
||||
@@ -161,3 +180,26 @@ def prune_family_group(g: List[ListedFont]) -> List[ListedFont]:
|
||||
return True
|
||||
return False
|
||||
return [x for x in g if is_ok(descriptor(x))]
|
||||
|
||||
|
||||
def set_axis_values(tag_map: Dict[str, float], font: CoreTextFont, vd: VariableData) -> bool:
|
||||
known_axes = {ax['tag'] for ax in vd['axes']}
|
||||
previous = font.get('axis_map', {})
|
||||
new = previous.copy()
|
||||
for tag in known_axes:
|
||||
val = tag_map.get(tag)
|
||||
if val is not None:
|
||||
new[tag] = val
|
||||
font['axis_map'] = new
|
||||
return new != previous
|
||||
|
||||
|
||||
def set_named_style(name: str, font: CoreTextFont, vd: VariableData) -> bool:
|
||||
q = name.lower()
|
||||
for i, ns in enumerate(vd['named_styles']):
|
||||
if ns['psname'].lower() == q:
|
||||
return set_axis_values(ns['axis_values'], font, vd)
|
||||
for i, ns in enumerate(vd['named_styles']):
|
||||
if ns['name'].lower() == q:
|
||||
return set_axis_values(ns['axis_values'], font, vd)
|
||||
return False
|
||||
|
||||
@@ -17,7 +17,7 @@ from kitty.fast_data_types import (
|
||||
from kitty.fast_data_types import fc_match as fc_match_impl
|
||||
from kitty.typing import FontConfigPattern
|
||||
|
||||
from . import Descriptor, ListedFont, Score, Scorer, family_name_to_key
|
||||
from . import Descriptor, ListedFont, Score, Scorer, VariableData, family_name_to_key
|
||||
|
||||
FontCollectionMapType = Literal['family_map', 'ps_map', 'full_map', 'variable_map']
|
||||
FontMap = Dict[FontCollectionMapType, Dict[str, List[FontConfigPattern]]]
|
||||
@@ -54,6 +54,10 @@ def is_monospace(descriptor: FontConfigPattern) -> bool:
|
||||
return descriptor['spacing'] in ('MONO', 'DUAL')
|
||||
|
||||
|
||||
def is_variable(descriptor: FontConfigPattern) -> bool:
|
||||
return descriptor['variable']
|
||||
|
||||
|
||||
def list_fonts(only_variable: bool = False) -> Generator[ListedFont, None, None]:
|
||||
for fd in fc_list(only_variable=only_variable):
|
||||
f = fd.get('family')
|
||||
@@ -65,7 +69,7 @@ def list_fonts(only_variable: bool = False) -> Generator[ListedFont, None, None]
|
||||
fn = f'{f} {fd.get("style", "")}'.strip()
|
||||
yield {
|
||||
'family': f, 'full_name': fn, 'postscript_name': str(fd.get('postscript_name', '')),
|
||||
'is_monospace': is_monospace(fd), 'descriptor': fd, 'is_variable': fd.get('variable', False),
|
||||
'is_monospace': is_monospace(fd), 'descriptor': fd, 'is_variable': is_variable(fd),
|
||||
'style': fd['style'],
|
||||
}
|
||||
|
||||
@@ -161,3 +165,29 @@ def prune_family_group(g: List[ListedFont]) -> List[ListedFont]:
|
||||
return d['variable'] or d['path'] not in variable_paths
|
||||
|
||||
return [x for x in g if is_ok(descriptor(x))]
|
||||
|
||||
|
||||
def set_named_style(name: str, font: FontConfigPattern, vd: VariableData) -> bool:
|
||||
q = name.lower()
|
||||
for i, ns in enumerate(vd['named_styles']):
|
||||
if ns['psname'].lower() == q:
|
||||
font['named_style'] = i
|
||||
return True
|
||||
for i, ns in enumerate(vd['named_styles']):
|
||||
if ns['name'].lower() == q:
|
||||
font['named_style'] = i
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def set_axis_values(tag_map: Dict[str, float], font: FontConfigPattern, vd: VariableData) -> bool:
|
||||
axes = list(font.get('axes', ())) or [ax['default'] for ax in vd['axes']]
|
||||
changed = False
|
||||
for i, ax in enumerate(vd['axes']):
|
||||
val = tag_map.get(ax['tag'])
|
||||
if val is not None:
|
||||
changed = True
|
||||
axes[i] = val
|
||||
if changed:
|
||||
font['axes'] = tuple(axes)
|
||||
return changed
|
||||
|
||||
@@ -288,15 +288,16 @@ face_from_descriptor(PyObject *descriptor, FONTS_DATA_HANDLE fg) {
|
||||
if ((error = FT_Set_Named_Instance(self->face, index + 1))) return set_load_error(path, error);
|
||||
}
|
||||
PyObject *axes = PyDict_GetItemString(descriptor, "axes");
|
||||
if (axes && PyList_GET_SIZE(axes)) {
|
||||
RAII_ALLOC(FT_Fixed, coords, malloc(sizeof(FT_Fixed) * PyList_GET_SIZE(axes)));
|
||||
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(axes); i++) {
|
||||
PyObject *t = PyList_GET_ITEM(axes, i);
|
||||
Py_ssize_t sz;
|
||||
if (axes && (sz = PyTuple_GET_SIZE(axes))) {
|
||||
RAII_ALLOC(FT_Fixed, coords, malloc(sizeof(FT_Fixed) * sz));
|
||||
for (Py_ssize_t i = 0; i < sz; i++) {
|
||||
PyObject *t = PyTuple_GET_ITEM(axes, i);
|
||||
double val = PyFloat_AsDouble(t);
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
coords[i] = (FT_Fixed)(val * 65536.0);
|
||||
}
|
||||
if ((error = FT_Set_Var_Design_Coordinates(self->face, PyList_GET_SIZE(axes), coords))) return set_load_error(path, error);
|
||||
if ((error = FT_Set_Var_Design_Coordinates(self->face, sz, coords))) return set_load_error(path, error);
|
||||
}
|
||||
}
|
||||
Py_XINCREF(retval);
|
||||
|
||||
@@ -1402,7 +1402,7 @@ def parse_font_spec(spec: str) -> FontSpec:
|
||||
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'):
|
||||
if k in ('family', 'style', 'full_name', 'postscript_name', 'variable_name'):
|
||||
defined[k] = v
|
||||
else:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user