CoreText: Fix selection of font file with multi-file variant font

Also prefer semibold and use similar scorer as fontconfig
This commit is contained in:
Kovid Goyal
2024-05-15 12:58:35 +05:30
parent 1780028420
commit 46f391ae63
4 changed files with 38 additions and 11 deletions

View File

@@ -156,10 +156,9 @@ class FontSpec(NamedTuple):
class Score(NamedTuple):
variable_score: int
style_score: int
style_score: Union[int, float]
monospace_score: int
width_score: int
weight_distance_from_medium: float = 0
Descriptor = Union[FontConfigPattern, CoreTextFont]

View File

@@ -26,6 +26,7 @@ if TYPE_CHECKING:
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: ...
def dump_sorted_candidates(bold: bool, italic: bool, candidates: List[Descriptor], scorer: Scorer) -> None: ...
else:
FontCollectionMapType = FontMap = None
if is_macos:
@@ -33,6 +34,7 @@ else:
from kitty.fonts.core_text import (
all_fonts_map,
create_scorer,
dump_sorted_candidates,
find_best_match,
find_last_resort_text_font,
is_monospace,
@@ -45,6 +47,7 @@ else:
from kitty.fonts.fontconfig import (
all_fonts_map,
create_scorer,
dump_sorted_candidates,
find_best_match,
find_last_resort_text_font,
is_monospace,
@@ -55,6 +58,7 @@ else:
def face_from_descriptor(descriptor: Descriptor) -> Face: return Face(descriptor=descriptor)
dump_sorted_candidates
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'}

View File

@@ -79,15 +79,22 @@ def create_scorer(bold: bool = False, italic: bool = False, monospaced: bool = T
def score(candidate: Descriptor) -> Score:
assert candidate['descriptor_type'] == 'core_text'
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
monospace_match = 1 if candidate['monospace'] else 0
bold_score = candidate['weight'] # -1 to 1 with 0 being normal
if bold_score < 0: # thinner than normal, reject
bold_score = 2.0
else:
if bold:
# prefer semibold=0.3 to full bold = 0.4
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
if italic:
if italic_score < 0:
italic_score = 2.0
else:
italic_score = abs(1 - italic_score)
monospace_match = 0 if candidate['monospace'] else 1
is_regular_width = not candidate['expanded'] and not candidate['condensed']
# prefer semi-bold to bold to heavy, less bold means less chance of
# overflow
weight_distance_from_medium = abs(candidate['weight'])
return Score(variable_score, 1 - style_match, 1 - monospace_match, 1 - is_regular_width, weight_distance_from_medium)
return Score(variable_score, bold_score + italic_score, monospace_match, 0 if is_regular_width else 1)
return score
@@ -99,6 +106,14 @@ def find_last_resort_text_font(bold: bool = False, italic: bool = False, monospa
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(
family: str, bold: bool = False, italic: bool = False, monospaced: bool = True, ignore_face: Optional[CoreTextFont] = None,
prefer_variable: bool = False
@@ -121,7 +136,8 @@ def find_best_match(
log_error(f'The font {family} was not found, falling back to Menlo')
q = 'menlo'
candidates = font_map['family_map'][q]
return sorted(candidates, key=scorer)[0]
candidates.sort(key=scorer)
return candidates[0]
def get_font_from_spec(

View File

@@ -97,6 +97,14 @@ def create_scorer(bold: bool = False, italic: bool = False, monospaced: bool = T
return score
def dump_sorted_candidates(bold: bool, italic: bool, candidates: List[FontConfigPattern], scorer: Scorer) -> None:
print(f'{bold=} {italic=}')
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:
# Use fc-match with a generic family
family = 'monospace' if monospaced else 'sans-serif'