mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-27 10:41:58 +02:00
Refactor scoring
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
from enum import Enum, IntEnum, auto
|
from enum import Enum, IntEnum, auto
|
||||||
from typing import TYPE_CHECKING, Callable, Dict, List, Literal, NamedTuple, Tuple, TypedDict, Union
|
from typing import TYPE_CHECKING, Dict, List, Literal, NamedTuple, Sequence, Tuple, TypedDict, TypeVar, Union
|
||||||
|
|
||||||
from kitty.types import run_once
|
from kitty.types import run_once
|
||||||
from kitty.typing import CoreTextFont, FontConfigPattern
|
from kitty.typing import CoreTextFont, FontConfigPattern
|
||||||
@@ -154,15 +154,23 @@ class FontSpec(NamedTuple):
|
|||||||
return self.system == 'auto'
|
return self.system == 'auto'
|
||||||
|
|
||||||
|
|
||||||
class Score(NamedTuple):
|
|
||||||
variable_score: int
|
|
||||||
style_score: Union[int, float]
|
|
||||||
monospace_score: int
|
|
||||||
width_score: int
|
|
||||||
|
|
||||||
|
|
||||||
Descriptor = Union[FontConfigPattern, CoreTextFont]
|
Descriptor = Union[FontConfigPattern, CoreTextFont]
|
||||||
Scorer = Callable[[Descriptor], Score]
|
DescriptorVar = TypeVar('DescriptorVar', FontConfigPattern, CoreTextFont, Descriptor)
|
||||||
|
|
||||||
|
class Scorer:
|
||||||
|
|
||||||
|
def __init__(self, bold: bool = False, italic: bool = False, monospaced: bool = True, prefer_variable: bool = False) -> None:
|
||||||
|
self.bold = bold
|
||||||
|
self.italic = italic
|
||||||
|
self.monospaced = monospaced
|
||||||
|
self.prefer_variable = prefer_variable
|
||||||
|
|
||||||
|
def sorted_candidates(self, candidates: Sequence[DescriptorVar], dump: bool = False) -> List[DescriptorVar]:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'{self.__class__.__name__}(bold={self.bold}, italic={self.italic}, monospaced={self.monospaced}, prefer_variable={self.prefer_variable})'
|
||||||
|
__str__ = __repr__
|
||||||
|
|
||||||
|
|
||||||
@run_once
|
@run_once
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# License: GPLv3 Copyright: 2024, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPLv3 Copyright: 2024, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Sequence, Tuple, TypedDict, Union
|
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple, TypedDict, Union
|
||||||
|
|
||||||
from kitty.constants import is_macos
|
from kitty.constants import is_macos
|
||||||
from kitty.fonts import Descriptor, DesignAxis, FontSpec, NamedStyle, Scorer, VariableData, family_name_to_key
|
from kitty.fonts import Descriptor, DescriptorVar, DesignAxis, FontSpec, NamedStyle, Scorer, VariableData, family_name_to_key
|
||||||
from kitty.options.types import Options
|
from kitty.options.types import Options
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -26,7 +26,6 @@ if TYPE_CHECKING:
|
|||||||
def is_variable(descriptor: Descriptor) -> bool: ...
|
def is_variable(descriptor: Descriptor) -> bool: ...
|
||||||
def set_named_style(name: str, font: Descriptor, vd: VariableData) -> 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: ...
|
def set_axis_values(tag_map: Dict[str, float], font: Descriptor, vd: VariableData) -> bool: ...
|
||||||
def dump_sorted_candidates(bold: bool, italic: bool, candidates: List[Descriptor], scorer: Scorer) -> None: ...
|
|
||||||
else:
|
else:
|
||||||
FontCollectionMapType = FontMap = None
|
FontCollectionMapType = FontMap = None
|
||||||
if is_macos:
|
if is_macos:
|
||||||
@@ -34,7 +33,6 @@ else:
|
|||||||
from kitty.fonts.core_text import (
|
from kitty.fonts.core_text import (
|
||||||
all_fonts_map,
|
all_fonts_map,
|
||||||
create_scorer,
|
create_scorer,
|
||||||
dump_sorted_candidates,
|
|
||||||
find_best_match,
|
find_best_match,
|
||||||
find_last_resort_text_font,
|
find_last_resort_text_font,
|
||||||
is_monospace,
|
is_monospace,
|
||||||
@@ -47,7 +45,6 @@ else:
|
|||||||
from kitty.fonts.fontconfig import (
|
from kitty.fonts.fontconfig import (
|
||||||
all_fonts_map,
|
all_fonts_map,
|
||||||
create_scorer,
|
create_scorer,
|
||||||
dump_sorted_candidates,
|
|
||||||
find_best_match,
|
find_best_match,
|
||||||
find_last_resort_text_font,
|
find_last_resort_text_font,
|
||||||
is_monospace,
|
is_monospace,
|
||||||
@@ -58,7 +55,6 @@ else:
|
|||||||
def face_from_descriptor(descriptor: Descriptor) -> Face: return Face(descriptor=descriptor)
|
def face_from_descriptor(descriptor: Descriptor) -> Face: return Face(descriptor=descriptor)
|
||||||
|
|
||||||
|
|
||||||
dump_sorted_candidates
|
|
||||||
cache_for_variable_data_by_path: Dict[str, VariableData] = {}
|
cache_for_variable_data_by_path: Dict[str, VariableData] = {}
|
||||||
attr_map = {(False, False): 'font_family', (True, False): 'bold_font', (False, True): 'italic_font', (True, True): 'bold_italic_font'}
|
attr_map = {(False, False): 'font_family', (True, False): 'bold_font', (False, True): 'italic_font', (True, True): 'bold_italic_font'}
|
||||||
|
|
||||||
@@ -83,16 +79,15 @@ def get_variable_data_for_face(d: Face) -> VariableData:
|
|||||||
|
|
||||||
|
|
||||||
def find_best_match_in_candidates(
|
def find_best_match_in_candidates(
|
||||||
candidates: Sequence[Descriptor], scorer: Scorer, is_medium_face: bool, ignore_face: Optional[Descriptor] = None
|
candidates: List[DescriptorVar], scorer: Scorer, is_medium_face: bool, ignore_face: Optional[DescriptorVar] = None
|
||||||
) -> Optional[Descriptor]:
|
) -> Optional[DescriptorVar]:
|
||||||
if not candidates:
|
if not candidates:
|
||||||
return None
|
return None
|
||||||
if len(candidates) == 1 and not is_medium_face and candidates[0].get('family') == candidates[0].get('full_name'):
|
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
|
# IBM Plex Mono does this, where the full name of the regular font
|
||||||
# face is the same as its family name
|
# face is the same as its family name
|
||||||
return None
|
return None
|
||||||
candidates = sorted(candidates, key=scorer)
|
for x in scorer.sorted_candidates(candidates):
|
||||||
for x in candidates:
|
|
||||||
if ignore_face is None or x != ignore_face:
|
if ignore_face is None or x != ignore_face:
|
||||||
return x
|
return x
|
||||||
return None
|
return None
|
||||||
@@ -144,9 +139,8 @@ def find_bold_italic_variant(medium: Descriptor, bold: bool, italic: bool) -> De
|
|||||||
# weights in each, so we rely on the OS font matcher to give us the best
|
# weights in each, so we rely on the OS font matcher to give us the best
|
||||||
# font file.
|
# font file.
|
||||||
monospaced = is_monospace(medium)
|
monospaced = is_monospace(medium)
|
||||||
fonts = all_fonts_map(monospaced)['variable_map'][family_name_to_key(medium['family'])]
|
unsorted = all_fonts_map(monospaced)['variable_map'][family_name_to_key(medium['family'])]
|
||||||
scorer = create_scorer(bold, italic, monospaced)
|
fonts = create_scorer(bold, italic, monospaced).sorted_candidates(unsorted)
|
||||||
fonts.sort(key=scorer)
|
|
||||||
vd = get_variable_data_for_descriptor(fonts[0])
|
vd = get_variable_data_for_descriptor(fonts[0])
|
||||||
ans = fonts[0].copy()
|
ans = fonts[0].copy()
|
||||||
# now we need to specialise all axes in ans
|
# now we need to specialise all axes in ans
|
||||||
@@ -179,9 +173,7 @@ def find_best_variable_face(spec: FontSpec, bold: bool, italic: bool, monospaced
|
|||||||
for x in vd['named_styles']:
|
for x in vd['named_styles']:
|
||||||
if x['name'].lower() == q:
|
if x['name'].lower() == q:
|
||||||
return font
|
return font
|
||||||
scorer = create_scorer(bold, italic, monospaced)
|
return create_scorer(bold, italic, monospaced).sorted_candidates(candidates)[0]
|
||||||
candidates.sort(key=scorer)
|
|
||||||
return candidates[0]
|
|
||||||
|
|
||||||
|
|
||||||
def get_fine_grained_font(
|
def get_fine_grained_font(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import itertools
|
|||||||
import operator
|
import operator
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import Dict, Generator, Iterable, List, Optional, Tuple
|
from typing import Dict, Generator, Iterable, List, NamedTuple, Optional, Sequence, Tuple
|
||||||
|
|
||||||
from kitty.fast_data_types import coretext_all_fonts
|
from kitty.fast_data_types import coretext_all_fonts
|
||||||
from kitty.fonts import FontSpec, family_name_to_key
|
from kitty.fonts import FontSpec, family_name_to_key
|
||||||
@@ -13,7 +13,7 @@ from kitty.options.types import Options
|
|||||||
from kitty.typing import CoreTextFont
|
from kitty.typing import CoreTextFont
|
||||||
from kitty.utils import log_error
|
from kitty.utils import log_error
|
||||||
|
|
||||||
from . import Descriptor, ListedFont, Score, Scorer, VariableData
|
from . import Descriptor, DescriptorVar, ListedFont, Scorer, VariableData
|
||||||
|
|
||||||
attr_map = {(False, False): 'font_family',
|
attr_map = {(False, False): 'font_family',
|
||||||
(True, False): 'bold_font',
|
(True, False): 'bold_font',
|
||||||
@@ -74,20 +74,30 @@ def list_fonts() -> Generator[ListedFont, None, None]:
|
|||||||
'is_variable': is_variable(fd), '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:
|
class Score(NamedTuple):
|
||||||
|
variable_score: int
|
||||||
|
style_score: float
|
||||||
|
monospace_score: int
|
||||||
|
width_score: int
|
||||||
|
|
||||||
def score(candidate: Descriptor) -> Score:
|
|
||||||
|
|
||||||
|
class CTScorer(Scorer):
|
||||||
|
medium_weight: float = 0.
|
||||||
|
bold_weight: float = 0.3
|
||||||
|
|
||||||
|
def score(self, candidate: Descriptor) -> Score:
|
||||||
assert candidate['descriptor_type'] == 'core_text'
|
assert candidate['descriptor_type'] == 'core_text'
|
||||||
variable_score = 0 if prefer_variable and candidate['variation'] is not None else 1
|
variable_score = 0 if self.prefer_variable and candidate['variation'] is not None else 1
|
||||||
bold_score = candidate['weight'] # -1 to 1 with 0 being normal
|
bold_score = candidate['weight'] # -1 to 1 with 0 being normal
|
||||||
if bold_score < 0: # thinner than normal, reject
|
if bold_score < 0: # thinner than normal, reject
|
||||||
bold_score = 2.0
|
bold_score = 2.0
|
||||||
else:
|
else:
|
||||||
if bold:
|
if self.bold:
|
||||||
# prefer semibold=0.3 to full bold = 0.4
|
# prefer semibold=0.3 to full bold = 0.4
|
||||||
bold_score = abs(bold_score - 0.3)
|
bold_score = abs(bold_score - 0.3)
|
||||||
italic_score = candidate['slant'] # -1 to 1 with 0 being upright < 0 being backward slant, abs(slant) == 1 implies 30 deg rotation
|
italic_score = candidate['slant'] # -1 to 1 with 0 being upright < 0 being backward slant, abs(slant) == 1 implies 30 deg rotation
|
||||||
if italic:
|
if self.italic:
|
||||||
if italic_score < 0:
|
if italic_score < 0:
|
||||||
italic_score = 2.0
|
italic_score = 2.0
|
||||||
else:
|
else:
|
||||||
@@ -96,22 +106,27 @@ def create_scorer(bold: bool = False, italic: bool = False, monospaced: bool = T
|
|||||||
is_regular_width = not candidate['expanded'] and not candidate['condensed']
|
is_regular_width = not candidate['expanded'] and not candidate['condensed']
|
||||||
return Score(variable_score, bold_score + italic_score, monospace_match, 0 if is_regular_width else 1)
|
return Score(variable_score, bold_score + italic_score, monospace_match, 0 if is_regular_width else 1)
|
||||||
|
|
||||||
return score
|
def sorted_candidates(self, candidates: Sequence[DescriptorVar], dump: bool = False) -> List[DescriptorVar]:
|
||||||
|
candidates = sorted(candidates, key=self.score)
|
||||||
|
if dump:
|
||||||
|
print(self)
|
||||||
|
for x in candidates:
|
||||||
|
assert x['descriptor_type'] == 'core_text'
|
||||||
|
print(x['postscript_name'], f'bold={x["bold"]}', f'italic={x["italic"]}', f'weight={x["weight"]:.2f}', f'slant={x["slant"]:.2f}')
|
||||||
|
print(self.score(x))
|
||||||
|
print()
|
||||||
|
self.medium_weight, self.bold_weight = CTScorer.medium_weight, CTScorer.bold_weight
|
||||||
|
return candidates
|
||||||
|
|
||||||
|
|
||||||
|
def create_scorer(bold: bool = False, italic: bool = False, monospaced: bool = True, prefer_variable: bool = False) -> Scorer:
|
||||||
|
return CTScorer(bold, italic, monospaced, prefer_variable)
|
||||||
|
|
||||||
|
|
||||||
def find_last_resort_text_font(bold: bool = False, italic: bool = False, monospaced: bool = True) -> CoreTextFont:
|
def find_last_resort_text_font(bold: bool = False, italic: bool = False, monospaced: bool = True) -> CoreTextFont:
|
||||||
font_map = all_fonts_map(monospaced)
|
font_map = all_fonts_map(monospaced)
|
||||||
candidates = font_map['family_map']['menlo']
|
candidates = font_map['family_map']['menlo']
|
||||||
scorer = create_scorer(bold, italic, monospaced)
|
return create_scorer(bold, italic, monospaced).sorted_candidates(candidates)[0]
|
||||||
return sorted(candidates, key=scorer)[0]
|
|
||||||
|
|
||||||
|
|
||||||
def dump_sorted_candidates(bold: bool, italic: bool, candidates: List[CoreTextFont], scorer: Scorer) -> None:
|
|
||||||
print(f'{bold=} {italic=}')
|
|
||||||
for x in candidates:
|
|
||||||
print(x['postscript_name'], f'bold={x["bold"]}', f'italic={x["italic"]}', f'weight={x["weight"]:.2f}', f'slant={x["slant"]:.2f}')
|
|
||||||
print(scorer(x))
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
|
||||||
def find_best_match(
|
def find_best_match(
|
||||||
@@ -126,7 +141,8 @@ def find_best_match(
|
|||||||
for selector in ('ps_map', 'full_map'):
|
for selector in ('ps_map', 'full_map'):
|
||||||
candidates = font_map[selector].get(q)
|
candidates = font_map[selector].get(q)
|
||||||
if candidates:
|
if candidates:
|
||||||
possible = sorted(candidates, key=scorer)[0]
|
candidates = scorer.sorted_candidates(candidates)
|
||||||
|
possible = candidates[0]
|
||||||
if possible != ignore_face:
|
if possible != ignore_face:
|
||||||
return possible
|
return possible
|
||||||
|
|
||||||
@@ -135,8 +151,7 @@ def find_best_match(
|
|||||||
if q not in font_map['family_map']:
|
if q not in font_map['family_map']:
|
||||||
log_error(f'The font {family} was not found, falling back to Menlo')
|
log_error(f'The font {family} was not found, falling back to Menlo')
|
||||||
q = 'menlo'
|
q = 'menlo'
|
||||||
candidates = font_map['family_map'][q]
|
candidates = scorer.sorted_candidates(font_map['family_map'][q])
|
||||||
candidates.sort(key=scorer)
|
|
||||||
return candidates[0]
|
return candidates[0]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import Dict, Generator, List, Literal, Optional, Tuple, cast
|
from typing import Dict, Generator, List, Literal, NamedTuple, Optional, Sequence, Tuple, cast
|
||||||
|
|
||||||
from kitty.fast_data_types import (
|
from kitty.fast_data_types import (
|
||||||
FC_DUAL,
|
FC_DUAL,
|
||||||
@@ -19,7 +19,7 @@ from kitty.fast_data_types import (
|
|||||||
from kitty.fast_data_types import fc_match as fc_match_impl
|
from kitty.fast_data_types import fc_match as fc_match_impl
|
||||||
from kitty.typing import FontConfigPattern
|
from kitty.typing import FontConfigPattern
|
||||||
|
|
||||||
from . import Descriptor, ListedFont, Score, Scorer, VariableData, family_name_to_key
|
from . import Descriptor, DescriptorVar, ListedFont, Scorer, VariableData, family_name_to_key
|
||||||
|
|
||||||
FontCollectionMapType = Literal['family_map', 'ps_map', 'full_map', 'variable_map']
|
FontCollectionMapType = Literal['family_map', 'ps_map', 'full_map', 'variable_map']
|
||||||
FontMap = Dict[FontCollectionMapType, Dict[str, List[FontConfigPattern]]]
|
FontMap = Dict[FontCollectionMapType, Dict[str, List[FontConfigPattern]]]
|
||||||
@@ -81,28 +81,43 @@ def fc_match(family: str, bold: bool, italic: bool, spacing: int = FC_MONO) -> F
|
|||||||
return fc_match_impl(family, bold, italic, spacing)
|
return fc_match_impl(family, bold, italic, spacing)
|
||||||
|
|
||||||
|
|
||||||
def create_scorer(bold: bool = False, italic: bool = False, monospaced: bool = True, prefer_variable: bool = False) -> Scorer:
|
class Score(NamedTuple):
|
||||||
|
variable_score: int
|
||||||
|
style_score: int
|
||||||
|
monospace_score: int
|
||||||
|
width_score: int
|
||||||
|
|
||||||
def score(candidate: Descriptor) -> Score:
|
|
||||||
|
|
||||||
|
class FCScorer(Scorer):
|
||||||
|
|
||||||
|
medium_weight: int = FC_WEIGHT_REGULAR
|
||||||
|
bold_weight: int = FC_WEIGHT_BOLD
|
||||||
|
|
||||||
|
def score(self, candidate: Descriptor) -> Score:
|
||||||
assert candidate['descriptor_type'] == 'fontconfig'
|
assert candidate['descriptor_type'] == 'fontconfig'
|
||||||
variable_score = 0 if prefer_variable and candidate['variable'] else 1
|
variable_score = 0 if self.prefer_variable and candidate['variable'] else 1
|
||||||
bold_score = abs((FC_WEIGHT_BOLD if bold else FC_WEIGHT_REGULAR) - candidate['weight'])
|
bold_score = abs((self.bold_weight if self.bold else self.medium_weight) - candidate['weight'])
|
||||||
italic_score = abs((FC_SLANT_ITALIC if italic else FC_SLANT_ROMAN) - candidate['slant'])
|
italic_score = abs((FC_SLANT_ITALIC if self.italic else FC_SLANT_ROMAN) - candidate['slant'])
|
||||||
monospace_match = 0
|
monospace_match = 0
|
||||||
if monospaced:
|
if self.monospaced:
|
||||||
monospace_match = 0 if candidate.get('spacing') == 'MONO' else 1
|
monospace_match = 0 if candidate.get('spacing') == 'MONO' else 1
|
||||||
width_score = abs(candidate['width'] - FC_WIDTH_NORMAL)
|
width_score = abs(candidate['width'] - FC_WIDTH_NORMAL)
|
||||||
return Score(variable_score, bold_score + italic_score, monospace_match, width_score)
|
return Score(variable_score, bold_score + italic_score, monospace_match, width_score)
|
||||||
|
|
||||||
return score
|
def sorted_candidates(self, candidates: Sequence[DescriptorVar], dump: bool = False) -> List[DescriptorVar]:
|
||||||
|
candidates = sorted(candidates, key=self.score)
|
||||||
|
if dump:
|
||||||
|
print(self)
|
||||||
|
for x in candidates:
|
||||||
|
assert x['descriptor_type'] == 'fontconfig'
|
||||||
|
print(x['postscript_name'], f'weight={x["weight"]}', f'slant={x["slant"]}')
|
||||||
|
print()
|
||||||
|
return candidates
|
||||||
|
|
||||||
|
|
||||||
def dump_sorted_candidates(bold: bool, italic: bool, candidates: List[FontConfigPattern], scorer: Scorer) -> None:
|
def create_scorer(bold: bool = False, italic: bool = False, monospaced: bool = True, prefer_variable: bool = False) -> Scorer:
|
||||||
print(f'{bold=} {italic=}')
|
return FCScorer(bold, italic, monospaced, prefer_variable)
|
||||||
for x in candidates:
|
|
||||||
print(x['postscript_name'], f'weight={x["weight"]}', f'slant={x["slant"]}')
|
|
||||||
print(scorer(x))
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
|
||||||
def find_last_resort_text_font(bold: bool = False, italic: bool = False, monospaced: bool = True) -> FontConfigPattern:
|
def find_last_resort_text_font(bold: bool = False, italic: bool = False, monospaced: bool = True) -> FontConfigPattern:
|
||||||
@@ -121,14 +136,16 @@ def find_best_match(
|
|||||||
scorer = create_scorer(bold, italic, monospaced, prefer_variable=prefer_variable)
|
scorer = create_scorer(bold, italic, monospaced, prefer_variable=prefer_variable)
|
||||||
is_medium_face = not bold and not italic
|
is_medium_face = not bold and not italic
|
||||||
# First look for an exact match
|
# First look for an exact match
|
||||||
exact_match = (
|
groups: Tuple[FontCollectionMapType, ...] = ('ps_map', 'full_map', 'family_map')
|
||||||
find_best_match_in_candidates(font_map['ps_map'].get(q, []), scorer, is_medium_face, ignore_face=ignore_face) or
|
for which in groups:
|
||||||
find_best_match_in_candidates(font_map['full_map'].get(q, []), scorer, is_medium_face, ignore_face=ignore_face) or
|
m = font_map[which]
|
||||||
find_best_match_in_candidates(font_map['family_map'].get(q, []), scorer, is_medium_face, ignore_face=ignore_face)
|
cq = m.get(q, [])
|
||||||
)
|
if cq:
|
||||||
if exact_match:
|
exact_match = find_best_match_in_candidates(cq, scorer, is_medium_face, ignore_face=ignore_face)
|
||||||
assert exact_match['descriptor_type'] == 'fontconfig'
|
if exact_match:
|
||||||
return exact_match
|
# dump_sorted_candidates(bold, italic, cq, scorer)
|
||||||
|
assert exact_match['descriptor_type'] == 'fontconfig'
|
||||||
|
return exact_match
|
||||||
|
|
||||||
# Use fc-match to see if we can find a monospaced font that matches family
|
# 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
|
# When aliases are defined, spacing can cause the incorrect font to be
|
||||||
@@ -151,7 +168,7 @@ def find_best_match(
|
|||||||
family_name_candidates = font_map['family_map'].get(family_name_to_key(candidates[0]['family']))
|
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:
|
if family_name_candidates and len(family_name_candidates) > 1:
|
||||||
candidates = family_name_candidates
|
candidates = family_name_candidates
|
||||||
return sorted(candidates, key=scorer)[0]
|
return scorer.sorted_candidates(candidates)[0]
|
||||||
return find_last_resort_text_font(bold, italic, monospaced)
|
return find_last_resort_text_font(bold, italic, monospaced)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class Selection(BaseTest):
|
|||||||
has_source_code_vf = has('sourcecodeVf')
|
has_source_code_vf = has('sourcecodeVf')
|
||||||
has_fira_code = has('Fira Code')
|
has_fira_code = has('Fira Code')
|
||||||
has_hack = has('Hack')
|
has_hack = has('Hack')
|
||||||
|
has_operator_mono = has('Operator Mono')
|
||||||
del fonts_map, has
|
del fonts_map, has
|
||||||
|
|
||||||
def s(family: str, *expected: str) -> None:
|
def s(family: str, *expected: str) -> None:
|
||||||
@@ -52,6 +53,8 @@ class Selection(BaseTest):
|
|||||||
both('fira code', 'FiraCodeRoman-Regular', 'FiraCodeRoman-SemiBold', 'FiraCodeRoman-Regular', 'FiraCodeRoman-SemiBold')
|
both('fira code', 'FiraCodeRoman-Regular', 'FiraCodeRoman-SemiBold', 'FiraCodeRoman-Regular', 'FiraCodeRoman-SemiBold')
|
||||||
if has_hack:
|
if has_hack:
|
||||||
both('hack', 'Hack-Regular', 'Hack-Bold', 'Hack-Italic', 'Hack-BoldItalic')
|
both('hack', 'Hack-Regular', 'Hack-Bold', 'Hack-Italic', 'Hack-BoldItalic')
|
||||||
|
if has_operator_mono:
|
||||||
|
both('operator mono', 'Hack-Regular', 'Hack-Bold', 'Hack-Italic', 'Hack-BoldItalic')
|
||||||
|
|
||||||
|
|
||||||
class Rendering(BaseTest):
|
class Rendering(BaseTest):
|
||||||
|
|||||||
@@ -308,9 +308,14 @@ def env_for_python_tests(report_env: bool = False) -> Iterator[None]:
|
|||||||
print('Python:', python_for_type_check())
|
print('Python:', python_for_type_check())
|
||||||
from kitty.fast_data_types import has_avx2, has_sse4_2
|
from kitty.fast_data_types import has_avx2, has_sse4_2
|
||||||
print(f'Intrinsics: {has_avx2=} {has_sse4_2=}')
|
print(f'Intrinsics: {has_avx2=} {has_sse4_2=}')
|
||||||
|
# we need fonts installed in the user home directory as well, so initialize
|
||||||
|
# fontconfig before nuking $HOME and friends
|
||||||
|
from kitty.fonts.common import all_fonts_map
|
||||||
|
all_fonts_map(True)
|
||||||
|
|
||||||
with TemporaryDirectory() as tdir, env_vars(
|
with TemporaryDirectory() as tdir, env_vars(
|
||||||
HOME=tdir,
|
HOME=tdir,
|
||||||
|
KT_ORIGINAL_HOME=os.path.expanduser('~'),
|
||||||
USERPROFILE=tdir,
|
USERPROFILE=tdir,
|
||||||
PATH=path,
|
PATH=path,
|
||||||
TERM='xterm-kitty',
|
TERM='xterm-kitty',
|
||||||
|
|||||||
Reference in New Issue
Block a user