Implement basic support for selecting font variations in fontconfig
This commit is contained in:
parent
6e3c9856d1
commit
56f8fb9f75
10 changed files with 233 additions and 105 deletions
|
|
@ -2653,7 +2653,7 @@ def apply_new_options(self, opts: Options) -> None:
|
|||
# Update font data
|
||||
set_scale(opts.box_drawing_scale)
|
||||
from .fonts.render import set_font_family
|
||||
set_font_family(opts, debug_font_matching=self.args.debug_font_fallback)
|
||||
set_font_family(opts)
|
||||
for os_window_id, tm in self.os_window_map.items():
|
||||
if tm is not None:
|
||||
os_window_font_size(os_window_id, opts.font_size, True)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
from typing import Any, Callable, Dict, Iterator, List, Literal, NewType, NotRequired, Optional, Tuple, TypedDict, Union, overload
|
||||
|
||||
from kitty.boss import Boss
|
||||
from kitty.fonts import FontFeature, VariableData
|
||||
|
|
@ -394,6 +394,10 @@ class FontConfigPattern(TypedDict):
|
|||
color: bool
|
||||
variable: bool
|
||||
|
||||
# The following two are used by C code to get a face from the pattern
|
||||
named_style: NotRequired[int]
|
||||
axes: NotRequired[List[float]]
|
||||
|
||||
|
||||
def fc_list(spacing: int = -1, allow_bitmapped_fonts: bool = False, only_variable: bool = False) -> Tuple[FontConfigPattern, ...]:
|
||||
pass
|
||||
|
|
@ -421,6 +425,7 @@ class Face:
|
|||
def __init__(self, descriptor: Optional[FontConfigPattern] = None, path: str = '', index: int = 0): ...
|
||||
def get_variable_data(self) -> VariableData: ...
|
||||
def identify_for_debug(self) -> str: ...
|
||||
def display_name(self) -> str: ...
|
||||
|
||||
|
||||
class CoreTextFont(TypedDict):
|
||||
|
|
@ -447,6 +452,7 @@ class CTFace:
|
|||
def __init__(self, descriptor: Optional[CoreTextFont] = None, path: str = ''): ...
|
||||
def get_variable_data(self) -> VariableData: ...
|
||||
def identify_for_debug(self) -> str: ...
|
||||
def display_name(self) -> str: ...
|
||||
|
||||
|
||||
def coretext_all_fonts() -> Tuple[CoreTextFont, ...]:
|
||||
|
|
@ -903,8 +909,18 @@ def concat_cells(cell_width: int, cell_height: int, is_32_bit: bool, cells: Tupl
|
|||
pass
|
||||
|
||||
|
||||
def current_fonts() -> Dict[str, Any]:
|
||||
pass
|
||||
FontFace = Union[Face, CTFace]
|
||||
|
||||
class CurrentFonts(TypedDict):
|
||||
medium: FontFace
|
||||
bold: FontFace
|
||||
italic: FontFace
|
||||
bi: FontFace
|
||||
symbol: Tuple[FontFace, ...]
|
||||
fallback: Tuple[FontFace, ...]
|
||||
|
||||
|
||||
def current_fonts() -> CurrentFonts: ...
|
||||
|
||||
|
||||
def remove_window(os_window_id: int, tab_id: int, window_id: int) -> None:
|
||||
|
|
|
|||
|
|
@ -395,25 +395,40 @@ end:
|
|||
PyObject*
|
||||
specialize_font_descriptor(PyObject *base_descriptor, FONTS_DATA_HANDLE fg) {
|
||||
ensure_initialized();
|
||||
PyObject *p = PyDict_GetItemString(base_descriptor, "path"), *ans = NULL;
|
||||
PyObject *p = PyDict_GetItemString(base_descriptor, "path");
|
||||
PyObject *idx = PyDict_GetItemString(base_descriptor, "index");
|
||||
if (p == NULL) { PyErr_SetString(PyExc_ValueError, "Base descriptor has no path"); return NULL; }
|
||||
if (idx == NULL) { PyErr_SetString(PyExc_ValueError, "Base descriptor has no index"); return NULL; }
|
||||
unsigned long face_idx = PyLong_AsUnsignedLong(idx);
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
|
||||
FcPattern *pat = FcPatternCreate();
|
||||
if (pat == NULL) return PyErr_NoMemory();
|
||||
long face_idx = MAX(0, PyLong_AsLong(idx));
|
||||
RAII_PyObject(ans, NULL);
|
||||
AP(FcPatternAddString, FC_FILE, (const FcChar8*)PyUnicode_AsUTF8(p), "path");
|
||||
AP(FcPatternAddInteger, FC_INDEX, face_idx, "index");
|
||||
AP(FcPatternAddDouble, FC_SIZE, fg->font_sz_in_pts, "size");
|
||||
AP(FcPatternAddDouble, FC_DPI, (fg->logical_dpi_x + fg->logical_dpi_y) / 2.0, "dpi");
|
||||
ans = _fc_match(pat);
|
||||
FcPatternDestroy(pat); pat = NULL;
|
||||
|
||||
if (face_idx > 0) {
|
||||
// For some reason FcFontMatch sets the index to zero, so manually restore it.
|
||||
PyDict_SetItemString(ans, "index", idx);
|
||||
if (PyDict_SetItemString(ans, "index", idx) != 0) return NULL;
|
||||
}
|
||||
end:
|
||||
if (pat != NULL) FcPatternDestroy(pat);
|
||||
PyObject *named_style = PyDict_GetItemString(base_descriptor, "named_style");
|
||||
if (named_style) {
|
||||
if (PyDict_SetItemString(ans, "named_style", named_style) != 0) return NULL;
|
||||
}
|
||||
PyObject *axes = PyDict_GetItemString(base_descriptor, "axes");
|
||||
if (axes) {
|
||||
if (PyDict_SetItemString(ans, "axes", axes) != 0) return NULL;
|
||||
}
|
||||
Py_INCREF(ans);
|
||||
return ans;
|
||||
end:
|
||||
if (pat) FcPatternDestroy(pat);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
|||
|
|
@ -1635,25 +1635,31 @@ concat_cells(PyObject UNUSED *self, PyObject *args) {
|
|||
static PyObject*
|
||||
current_fonts(PYNOARG) {
|
||||
if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create font group first"); return NULL; }
|
||||
PyObject *ans = PyDict_New();
|
||||
RAII_PyObject(ans, PyDict_New());
|
||||
if (!ans) return NULL;
|
||||
FontGroup *fg = font_groups;
|
||||
#define SET(key, val) {if (PyDict_SetItemString(ans, #key, fg->fonts[val].face) != 0) { goto error; }}
|
||||
#define SET(key, val) {if (PyDict_SetItemString(ans, #key, fg->fonts[val].face) != 0) { return NULL; }}
|
||||
SET(medium, fg->medium_font_idx);
|
||||
if (fg->bold_font_idx > 0) SET(bold, fg->bold_font_idx);
|
||||
if (fg->italic_font_idx > 0) SET(italic, fg->italic_font_idx);
|
||||
if (fg->bi_font_idx > 0) SET(bi, fg->bi_font_idx);
|
||||
PyObject *ff = PyTuple_New(fg->fallback_fonts_count);
|
||||
if (!ff) goto error;
|
||||
unsigned num_symbol_fonts = fg->first_fallback_font_idx - fg->first_symbol_font_idx;
|
||||
RAII_PyObject(ss, PyTuple_New(num_symbol_fonts));
|
||||
if (!ss) return NULL;
|
||||
for (size_t i = 0; i < num_symbol_fonts; i++) {
|
||||
Py_INCREF(fg->fonts[fg->first_symbol_font_idx + i].face);
|
||||
PyTuple_SET_ITEM(ss, i, fg->fonts[fg->first_symbol_font_idx + i].face);
|
||||
}
|
||||
if (PyDict_SetItemString(ans, "symbol", ss) != 0) return NULL;
|
||||
RAII_PyObject(ff, PyTuple_New(fg->fallback_fonts_count));
|
||||
if (!ff) return NULL;
|
||||
for (size_t i = 0; i < fg->fallback_fonts_count; i++) {
|
||||
Py_INCREF(fg->fonts[fg->first_fallback_font_idx + i].face);
|
||||
PyTuple_SET_ITEM(ff, i, fg->fonts[fg->first_fallback_font_idx + i].face);
|
||||
}
|
||||
PyDict_SetItemString(ans, "fallback", ff);
|
||||
Py_CLEAR(ff);
|
||||
if (PyDict_SetItemString(ans, "fallback", ff) != 0) return NULL;
|
||||
Py_INCREF(ans);
|
||||
return ans;
|
||||
error:
|
||||
Py_CLEAR(ans); return NULL;
|
||||
#undef SET
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from enum import Enum, IntEnum, auto
|
||||
from typing import Dict, NamedTuple, Optional, Tuple, TypedDict, Union
|
||||
from typing import Dict, NamedTuple, Tuple, TypedDict, Union
|
||||
|
||||
from kitty.typing import CoreTextFont, FontConfigPattern
|
||||
|
||||
|
|
@ -24,8 +24,8 @@ class VariableAxis(TypedDict):
|
|||
|
||||
class NamedStyle(TypedDict):
|
||||
axis_values: Dict[str, float]
|
||||
name: Optional[str]
|
||||
psname: Optional[str]
|
||||
name: str
|
||||
psname: str # can be empty string when not present
|
||||
|
||||
|
||||
class VariableData(TypedDict):
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
from typing import Dict, Generator, Iterable, List, Optional, Tuple
|
||||
|
||||
from kitty.fast_data_types import CTFace, coretext_all_fonts
|
||||
from kitty.fonts import FontFeature, FontSpec, VariableData
|
||||
from kitty.fonts import FontSpec, VariableData
|
||||
from kitty.options.types import Options
|
||||
from kitty.typing import CoreTextFont
|
||||
from kitty.utils import log_error
|
||||
|
|
@ -52,11 +52,6 @@ def list_fonts() -> Generator[ListedFont, None, None]:
|
|||
'is_variable': fd['variable'], 'descriptor': fd}
|
||||
|
||||
|
||||
def find_font_features(postscript_name: str) -> Tuple[FontFeature, ...]:
|
||||
"""Not Implemented"""
|
||||
return ()
|
||||
|
||||
|
||||
def find_best_match(family: str, bold: bool = False, italic: bool = False, ignore_face: Optional[CoreTextFont] = None) -> CoreTextFont:
|
||||
q = re.sub(r'\s+', ' ', family.lower())
|
||||
font_map = all_fonts_map()
|
||||
|
|
@ -124,8 +119,17 @@ def font_for_family(family: str) -> Tuple[CoreTextFont, bool, bool]:
|
|||
return ans, ans['bold'], ans['italic']
|
||||
|
||||
|
||||
cache_for_variable_data_by_path: Dict[str, VariableData] = {}
|
||||
|
||||
|
||||
def get_variable_data_for_descriptor(f: ListedFont) -> VariableData:
|
||||
return CTFace(descriptor=descriptor(f)).get_variable_data()
|
||||
d = descriptor(f)
|
||||
if not d['path']:
|
||||
return CTFace(descriptor=d).get_variable_data()
|
||||
ans = cache_for_variable_data_by_path.get(d['path'])
|
||||
if ans is None:
|
||||
ans = cache_for_variable_data_by_path[d['path']] = CTFace(descriptor=d).get_variable_data()
|
||||
return ans
|
||||
|
||||
|
||||
def descriptor(f: ListedFont) -> CoreTextFont:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import re
|
||||
from functools import lru_cache
|
||||
from typing import Dict, Generator, List, Optional, Tuple, cast
|
||||
from typing import Callable, Dict, Generator, List, Literal, NamedTuple, Optional, Tuple, cast
|
||||
|
||||
from kitty.fast_data_types import (
|
||||
FC_DUAL,
|
||||
|
|
@ -15,15 +15,12 @@
|
|||
FC_WIDTH_NORMAL,
|
||||
Face,
|
||||
fc_list,
|
||||
fc_match_postscript_name,
|
||||
parse_font_feature,
|
||||
)
|
||||
from kitty.fast_data_types import fc_match as fc_match_impl
|
||||
from kitty.options.types import Options
|
||||
from kitty.typing import FontConfigPattern
|
||||
from kitty.utils import log_error
|
||||
|
||||
from . import FontFeature, FontSpec, ListedFont, VariableData
|
||||
from . import FontSpec, ListedFont, VariableData
|
||||
|
||||
attr_map = {(False, False): 'font_family',
|
||||
(True, False): 'bold_font',
|
||||
|
|
@ -31,7 +28,8 @@
|
|||
(True, True): 'bold_italic_font'}
|
||||
|
||||
|
||||
FontMap = Dict[str, Dict[str, List[FontConfigPattern]]]
|
||||
FontCollectionMapType = Literal['family_map', 'ps_map', 'full_map']
|
||||
FontMap = Dict[FontCollectionMapType, Dict[str, List[FontConfigPattern]]]
|
||||
|
||||
|
||||
def create_font_map(all_fonts: Tuple[FontConfigPattern, ...]) -> FontMap:
|
||||
|
|
@ -84,47 +82,56 @@ def fc_match(family: str, bold: bool, italic: bool, spacing: int = FC_MONO) -> F
|
|||
return fc_match_impl(family, bold, italic, spacing)
|
||||
|
||||
|
||||
def find_font_features(postscript_name: str) -> Tuple[FontFeature, ...]:
|
||||
pat = fc_match_postscript_name(postscript_name)
|
||||
class Score(NamedTuple):
|
||||
variable_score: int
|
||||
style_score: int
|
||||
monospace_score: int
|
||||
width_score: int
|
||||
|
||||
if pat.get('postscript_name') != postscript_name or 'fontfeatures' not in pat:
|
||||
return ()
|
||||
Scorer = Callable[[FontConfigPattern], Score]
|
||||
|
||||
features = []
|
||||
for feat in pat['fontfeatures']:
|
||||
try:
|
||||
parsed = parse_font_feature(feat)
|
||||
except ValueError:
|
||||
log_error(f'Ignoring invalid font feature: {feat}')
|
||||
else:
|
||||
features.append(FontFeature(feat, parsed))
|
||||
def create_scorer(bold: bool = False, italic: bool = False, monospaced: bool = True, prefer_variable: bool = False) -> Scorer:
|
||||
|
||||
return tuple(features)
|
||||
def score(candidate: FontConfigPattern) -> Score:
|
||||
variable_score = 0 if prefer_variable and candidate['variable'] else 1
|
||||
bold_score = abs((FC_WEIGHT_BOLD if bold else FC_WEIGHT_REGULAR) - candidate.get('weight', 0))
|
||||
italic_score = abs((FC_SLANT_ITALIC if italic else FC_SLANT_ROMAN) - candidate.get('slant', 0))
|
||||
monospace_match = 0
|
||||
if monospaced:
|
||||
monospace_match = 0 if candidate.get('spacing') == 'MONO' else 1
|
||||
width_score = abs(candidate.get('width', FC_WIDTH_NORMAL) - FC_WIDTH_NORMAL)
|
||||
|
||||
return Score(variable_score, bold_score + italic_score, monospace_match, width_score)
|
||||
|
||||
return score
|
||||
|
||||
|
||||
def find_best_match_in_candidates(
|
||||
candidates: List[FontConfigPattern], scorer: Scorer, is_medium_face: bool
|
||||
) -> Optional[FontConfigPattern]:
|
||||
if not candidates:
|
||||
return None
|
||||
if len(candidates) == 1 and not is_medium_face and candidates[0].get('family') == candidates[0].get('full_name'):
|
||||
# IBM Plex Mono does this, where the full name of the regular font
|
||||
# face is the same as its family name
|
||||
return None
|
||||
candidates.sort(key=scorer)
|
||||
return candidates[0]
|
||||
|
||||
|
||||
def find_best_match(family: str, bold: bool = False, italic: bool = False, monospaced: bool = True) -> FontConfigPattern:
|
||||
q = family_name_to_key(family)
|
||||
font_map = all_fonts_map(monospaced)
|
||||
|
||||
def score(candidate: FontConfigPattern) -> Tuple[int, int, int]:
|
||||
bold_score = abs((FC_WEIGHT_BOLD if bold else FC_WEIGHT_REGULAR) - candidate.get('weight', 0))
|
||||
italic_score = abs((FC_SLANT_ITALIC if italic else FC_SLANT_ROMAN) - candidate.get('slant', 0))
|
||||
monospace_match = 0 if candidate.get('spacing') == 'MONO' else 1
|
||||
width_score = abs(candidate.get('width', FC_WIDTH_NORMAL) - FC_WIDTH_NORMAL)
|
||||
|
||||
return bold_score + italic_score, monospace_match, width_score
|
||||
|
||||
scorer = create_scorer(bold, italic, monospaced)
|
||||
is_medium_face = not bold and not italic
|
||||
# First look for an exact match
|
||||
for selector in ('ps_map', 'full_map', 'family_map'):
|
||||
candidates = font_map[selector].get(q)
|
||||
if not candidates:
|
||||
continue
|
||||
if len(candidates) == 1 and (bold or italic) and candidates[0].get('family') == candidates[0].get('full_name'):
|
||||
# IBM Plex Mono does this, where the full name of the regular font
|
||||
# face is the same as its family name
|
||||
continue
|
||||
candidates.sort(key=score)
|
||||
return candidates[0]
|
||||
exact_match = (
|
||||
find_best_match_in_candidates(font_map['ps_map'].get(q, []), scorer, is_medium_face) or
|
||||
find_best_match_in_candidates(font_map['full_map'].get(q, []), scorer, is_medium_face) or
|
||||
find_best_match_in_candidates(font_map['family_map'].get(q, []), scorer, is_medium_face)
|
||||
)
|
||||
if exact_match:
|
||||
return exact_match
|
||||
|
||||
# Use fc-match to see if we can find a monospaced font that matches family
|
||||
# When aliases are defined, spacing can cause the incorrect font to be
|
||||
|
|
@ -135,6 +142,7 @@ def score(candidate: FontConfigPattern) -> Tuple[int, int, int]:
|
|||
tries = (dual_possibility, mono_possibility) if any_possibility == dual_possibility else (mono_possibility, dual_possibility)
|
||||
for possibility in tries:
|
||||
for key, map_key in (('postscript_name', 'ps_map'), ('full_name', 'full_map'), ('family', 'family_map')):
|
||||
map_key = cast(FontCollectionMapType, map_key)
|
||||
val: Optional[str] = cast(Optional[str], possibility.get(key))
|
||||
if val:
|
||||
candidates = font_map[map_key].get(family_name_to_key(val))
|
||||
|
|
@ -146,19 +154,83 @@ def score(candidate: FontConfigPattern) -> Tuple[int, int, int]:
|
|||
family_name_candidates = font_map['family_map'].get(family_name_to_key(candidates[0]['family']))
|
||||
if family_name_candidates and len(family_name_candidates) > 1:
|
||||
candidates = family_name_candidates
|
||||
return sorted(candidates, key=score)[0]
|
||||
return sorted(candidates, key=scorer)[0]
|
||||
|
||||
# Use fc-match with a generic family
|
||||
family = 'monospace' if monospaced else 'sans-serif'
|
||||
return fc_match(family, bold, italic)
|
||||
|
||||
|
||||
def get_fine_grained_font(
|
||||
spec: FontSpec, bold: bool = False, italic: bool = False, medium_font_spec: FontSpec = FontSpec(),
|
||||
resolved_medium_font: Optional[FontConfigPattern] = None, monospaced: bool = True
|
||||
) -> FontConfigPattern:
|
||||
font_map = all_fonts_map(monospaced)
|
||||
scorer = create_scorer(bold, italic, monospaced, prefer_variable=bool(spec.axes) or bool(spec.style))
|
||||
is_medium_face = not bold and not italic
|
||||
if spec.postscript_name:
|
||||
q = find_best_match_in_candidates(font_map['ps_map'].get(family_name_to_key(spec.postscript_name), []), scorer, is_medium_face)
|
||||
if q:
|
||||
return q
|
||||
if spec.full_name:
|
||||
q = find_best_match_in_candidates(font_map['full_map'].get(family_name_to_key(spec.full_name), []), scorer, is_medium_face)
|
||||
if q:
|
||||
return q
|
||||
if spec.family:
|
||||
candidates = font_map['family_map'].get(family_name_to_key(spec.family), [])
|
||||
if spec.style:
|
||||
qs = spec.style.lower()
|
||||
candidates = [x for x in candidates if x['style'].lower() == qs]
|
||||
q = find_best_match_in_candidates(candidates, scorer, is_medium_face)
|
||||
if q:
|
||||
return q
|
||||
# Use fc-match with a generic family
|
||||
family = 'monospace' if monospaced else 'sans-serif'
|
||||
return fc_match(family, bold, italic)
|
||||
|
||||
|
||||
def apply_variation_to_pattern(pat: FontConfigPattern, spec: FontSpec) -> FontConfigPattern:
|
||||
if not pat['variable']:
|
||||
return pat
|
||||
|
||||
vd = Face(descriptor=pat).get_variable_data()
|
||||
if spec.style:
|
||||
q = spec.style.lower()
|
||||
for i, ns in enumerate(vd['named_styles']):
|
||||
if ns.get('psname') and ns['psname'].lower() == q:
|
||||
pat['named_style'] = i
|
||||
break
|
||||
else:
|
||||
for i, ns in enumerate(vd['named_styles']):
|
||||
if ns['name'].lower() == q:
|
||||
pat['named_style'] = i
|
||||
break
|
||||
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
|
||||
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['axes'] = axes
|
||||
return pat
|
||||
|
||||
|
||||
def get_font_from_spec(
|
||||
spec: FontSpec, bold: bool = False, italic: bool = False, medium_font_spec: FontSpec = FontSpec(),
|
||||
resolved_medium_font: Optional[FontConfigPattern] = None
|
||||
) -> FontConfigPattern:
|
||||
if not spec.is_system:
|
||||
raise NotImplementedError('TODO: Implement me')
|
||||
return apply_variation_to_pattern(get_fine_grained_font(spec, bold, italic, medium_font_spec, resolved_medium_font), spec)
|
||||
family = spec.system
|
||||
if family == 'auto' and (bold or italic):
|
||||
assert resolved_medium_font is not None
|
||||
|
|
@ -191,8 +263,17 @@ def descriptor(f: ListedFont) -> FontConfigPattern:
|
|||
return d
|
||||
|
||||
|
||||
cache_for_variable_data_by_path: Dict[str, VariableData] = {}
|
||||
|
||||
|
||||
def get_variable_data_for_descriptor(f: ListedFont) -> VariableData:
|
||||
return Face(descriptor=descriptor(f)).get_variable_data()
|
||||
d = descriptor(f)
|
||||
if not d['path']:
|
||||
return Face(descriptor=d).get_variable_data()
|
||||
ans = cache_for_variable_data_by_path.get(d['path'])
|
||||
if ans is None:
|
||||
ans = cache_for_variable_data_by_path[d['path']] = Face(descriptor=d).get_variable_data()
|
||||
return ans
|
||||
|
||||
|
||||
def prune_family_group(g: List[ListedFont]) -> List[ListedFont]:
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
NUM_UNDERLINE_STYLES,
|
||||
Screen,
|
||||
create_test_font_group,
|
||||
current_fonts,
|
||||
get_fallback_font,
|
||||
get_options,
|
||||
set_font_data,
|
||||
|
|
@ -29,11 +30,9 @@
|
|||
from kitty.utils import log_error
|
||||
|
||||
if is_macos:
|
||||
from .core_text import find_font_features
|
||||
from .core_text import font_for_family as font_for_family_macos
|
||||
from .core_text import get_font_files as get_font_files_coretext
|
||||
else:
|
||||
from .fontconfig import find_font_features
|
||||
from .fontconfig import font_for_family as font_for_family_fontconfig
|
||||
from .fontconfig import get_font_files as get_font_files_fontconfig
|
||||
|
||||
|
|
@ -165,27 +164,19 @@ def descriptor_for_idx(idx: int) -> Tuple[FontObject, bool, bool]:
|
|||
return current_faces[idx]
|
||||
|
||||
|
||||
def dump_faces(ftypes: List[str], indices: Dict[str, int]) -> None:
|
||||
def face_str(f: Tuple[FontObject, bool, bool]) -> str:
|
||||
fo = f[0]
|
||||
if 'index' in fo:
|
||||
return '{}:{}'.format(fo['path'], cast('FontConfigPattern', fo)['index'])
|
||||
fo = cast('CoreTextFont', fo)
|
||||
return fo['path']
|
||||
|
||||
log_error('Preloaded font faces:')
|
||||
log_error('normal face:', face_str(current_faces[0]))
|
||||
for ftype in ftypes:
|
||||
if indices[ftype]:
|
||||
log_error(ftype, 'face:', face_str(current_faces[indices[ftype]]))
|
||||
si_faces = current_faces[max(indices.values())+1:]
|
||||
if si_faces:
|
||||
log_error('Symbol map faces:')
|
||||
for face in si_faces:
|
||||
log_error(face_str(face))
|
||||
def dump_font_debug() -> None:
|
||||
cf = current_fonts()
|
||||
log_error('Text fonts:')
|
||||
for key, text in {'medium': 'Normal', 'bold': 'Bold', 'italic': 'Italic', 'bi': 'Bold-Italic'}.items():
|
||||
log_error(f' {text}:', cf[key].identify_for_debug()) # type: ignore
|
||||
ss = cf['symbol']
|
||||
if ss:
|
||||
log_error('Symbol map fonts:')
|
||||
for s in ss:
|
||||
log_error(' ' + s.identify_for_debug())
|
||||
|
||||
|
||||
def set_font_family(opts: Optional[Options] = None, override_font_size: Optional[float] = None, debug_font_matching: bool = False) -> None:
|
||||
def set_font_family(opts: Optional[Options] = None, override_font_size: Optional[float] = None) -> None:
|
||||
global current_faces
|
||||
opts = opts or defaults
|
||||
sz = override_font_size or opts.font_size
|
||||
|
|
@ -201,16 +192,10 @@ def set_font_family(opts: Optional[Options] = None, override_font_size: Optional
|
|||
sm = create_symbol_map(opts)
|
||||
ns = create_narrow_symbols(opts)
|
||||
num_symbol_fonts = len(current_faces) - before
|
||||
font_features = {}
|
||||
for face, _, _ in current_faces:
|
||||
font_features[face['postscript_name']] = find_font_features(face['postscript_name'])
|
||||
font_features.update(opts.font_features)
|
||||
if debug_font_matching:
|
||||
dump_faces(ftypes, indices)
|
||||
set_font_data(
|
||||
render_box_drawing, prerender_function, descriptor_for_idx,
|
||||
indices['bold'], indices['italic'], indices['bi'], num_symbol_fonts,
|
||||
sm, sz, font_features, ns
|
||||
sm, sz, opts.font_features.copy(), ns
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -265,13 +265,32 @@ face_from_descriptor(PyObject *descriptor, FONTS_DATA_HANDLE fg) {
|
|||
D(hinting, PyObject_IsTrue, true);
|
||||
D(hint_style, PyLong_AsLong, true);
|
||||
#undef D
|
||||
Face *self = (Face *)Face_Type.tp_alloc(&Face_Type, 0);
|
||||
if (self != NULL) {
|
||||
int error = FT_New_Face(library, path, index, &(self->face));
|
||||
if(error) { Py_CLEAR(self); return set_load_error(path, error); }
|
||||
if (!init_ft_face(self, PyDict_GetItemString(descriptor, "path"), hinting, hint_style, fg)) { Py_CLEAR(self); return NULL; }
|
||||
RAII_PyObject(retval, Face_Type.tp_alloc(&Face_Type, 0));
|
||||
if (retval != NULL) {
|
||||
Face *self = (Face *)retval;
|
||||
int error;
|
||||
if ((error = FT_New_Face(library, path, index, &(self->face)))) { self->face = NULL; set_load_error(path, error); }
|
||||
if (!init_ft_face(self, PyDict_GetItemString(descriptor, "path"), hinting, hint_style, fg)) return NULL;
|
||||
PyObject *ns = PyDict_GetItemString(descriptor, "named_style");
|
||||
if (ns) {
|
||||
unsigned long index = PyLong_AsUnsignedLong(ns);
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
if ((error = FT_Set_Named_Instance(self->face, index + 1))) return set_load_error(path, error);
|
||||
}
|
||||
PyObject *axes = PyDict_GetItemString(descriptor, "axes");
|
||||
if (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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
return (PyObject*)self;
|
||||
Py_XINCREF(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
|
|
@ -768,7 +787,7 @@ convert_named_style_to_python(Face *face, const FT_Var_Named_Style *src, FT_Var_
|
|||
if (!name) PyErr_Clear();
|
||||
RAII_PyObject(psname, src->psid == 0xffff ? NULL : _get_best_name(face, src->psid));
|
||||
if (!psname) PyErr_Clear();
|
||||
return Py_BuildValue("{sO sO sO}", "axis_values", axis_values, "name", name ? name : Py_None, "psname", psname ? psname : Py_None);
|
||||
return Py_BuildValue("{sO sO sO}", "axis_values", axis_values, "name", name ? name : PyUnicode_FromString(""), "psname", psname ? psname : PyUnicode_FromString(""));
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
set_options,
|
||||
)
|
||||
from .fonts.box_drawing import set_scale
|
||||
from .fonts.render import set_font_family
|
||||
from .fonts.render import dump_font_debug, set_font_family
|
||||
from .options.types import Options
|
||||
from .options.utils import DELETE_ENV_VAR
|
||||
from .os_window_size import edge_spacing, initial_window_size_func
|
||||
|
|
@ -225,6 +225,8 @@ def _run_app(opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = (),
|
|||
wincls, wstate, load_all_shaders, disallow_override_title=bool(args.title), layer_shell_config=run_app.layer_shell_config)
|
||||
boss = Boss(opts, args, cached_values, global_shortcuts, talk_fd)
|
||||
boss.start(window_id, startup_sessions)
|
||||
if args.debug_font_fallback:
|
||||
dump_font_debug()
|
||||
if bad_lines or boss.misc_config_errors:
|
||||
boss.show_bad_config_lines(bad_lines, boss.misc_config_errors)
|
||||
boss.misc_config_errors = []
|
||||
|
|
@ -246,7 +248,7 @@ def __call__(self, opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine]
|
|||
set_scale(opts.box_drawing_scale)
|
||||
set_options(opts, is_wayland(), args.debug_rendering, args.debug_font_fallback)
|
||||
try:
|
||||
set_font_family(opts, debug_font_matching=args.debug_font_fallback)
|
||||
set_font_family(opts)
|
||||
_run_app(opts, args, bad_lines, talk_fd)
|
||||
finally:
|
||||
set_options(None)
|
||||
|
|
|
|||
Loading…
Reference in a new issue