mirror of
https://github.com/kovidgoyal/kitty.git
synced 2024-10-05 18:47:27 +03:00
Handle variable fonts like cascadia code that dont have a postfix variation prefix name for some of their faces
This commit is contained in:
parent
815df1e210
commit
b017cc0c1e
@ -20,7 +20,7 @@
|
||||
get_variable_data_for_descriptor,
|
||||
get_variable_data_for_face,
|
||||
is_variable,
|
||||
spec_for_descriptor,
|
||||
spec_for_face,
|
||||
)
|
||||
from kitty.fonts.list import create_family_groups
|
||||
from kitty.fonts.render import display_bitmap
|
||||
@ -134,6 +134,11 @@ def render_family_sample(
|
||||
ResolvedFace = Dict[Literal['family', 'spec'], str]
|
||||
|
||||
|
||||
def spec_for_descriptor(d: Descriptor) -> str:
|
||||
face = face_from_descriptor(d)
|
||||
return spec_for_face(d['family'], face).as_setting
|
||||
|
||||
|
||||
def resolved_faces(opts: Options) -> Dict[OptNames, ResolvedFace]:
|
||||
font_files = get_font_files(opts)
|
||||
ans: Dict[OptNames, ResolvedFace] = {}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"kitty/tools/tui"
|
||||
"kitty/tools/tui/loop"
|
||||
"kitty/tools/utils"
|
||||
"kitty/tools/utils/shlex"
|
||||
"kitty/tools/wcswidth"
|
||||
)
|
||||
|
||||
@ -28,7 +29,8 @@ type face_panel struct {
|
||||
}
|
||||
|
||||
func (self *face_panel) variable_spec(named_style string, axis_overrides map[string]float64) string {
|
||||
ans := fmt.Sprintf(`family="%s" variable_name="%s"`, self.family, self.current_preview.Variable_data.Variations_postscript_name_prefix)
|
||||
vname := self.current_preview.Variable_data.Variations_postscript_name_prefix
|
||||
ans := fmt.Sprintf(`family=%s variable_name=%s`, shlex.Quote(self.family), shlex.Quote(vname))
|
||||
if axis_overrides != nil {
|
||||
axis_values := self.current_preview.current_axis_values()
|
||||
maps.Copy(axis_values, axis_overrides)
|
||||
@ -36,7 +38,7 @@ func (self *face_panel) variable_spec(named_style string, axis_overrides map[str
|
||||
ans += fmt.Sprintf(" %s=%g", tag, val)
|
||||
}
|
||||
} else if named_style != "" {
|
||||
ans += fmt.Sprintf(" style=\"%s\"", named_style)
|
||||
ans += fmt.Sprintf(" style=%s", shlex.Quote(named_style))
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
from enum import Enum, IntEnum, auto
|
||||
from typing import TYPE_CHECKING, Dict, List, Literal, NamedTuple, Sequence, Tuple, TypedDict, TypeVar, Union
|
||||
from typing import TYPE_CHECKING, Dict, List, Literal, NamedTuple, Optional, Sequence, Tuple, TypedDict, TypeVar, Union
|
||||
|
||||
from kitty.types import run_once
|
||||
from kitty.typing import CoreTextFont, FontConfigPattern
|
||||
@ -136,13 +136,13 @@ def __repr__(self) -> str:
|
||||
|
||||
|
||||
class FontSpec(NamedTuple):
|
||||
family: str = ''
|
||||
style: str = ''
|
||||
postscript_name: str = ''
|
||||
full_name: str = ''
|
||||
system: str = ''
|
||||
family: Optional[str] = None
|
||||
style: Optional[str] = None
|
||||
postscript_name: Optional[str] = None
|
||||
full_name: Optional[str] = None
|
||||
system: Optional[str] = None
|
||||
axes: Tuple[Tuple[str, float], ...] = ()
|
||||
variable_name: str = ''
|
||||
variable_name: Optional[str] = None
|
||||
created_from_string: str = ''
|
||||
|
||||
@property
|
||||
@ -153,6 +153,33 @@ def is_system(self) -> bool:
|
||||
def is_auto(self) -> bool:
|
||||
return self.system == 'auto'
|
||||
|
||||
@property
|
||||
def as_setting(self) -> str:
|
||||
if self.created_from_string:
|
||||
return self.created_from_string
|
||||
if self.system:
|
||||
return self.system
|
||||
ans = []
|
||||
from shlex import quote
|
||||
def a(key: str, val: str) -> None:
|
||||
ans.append(f'{key}={quote(val)}')
|
||||
|
||||
if self.family is not None:
|
||||
a('family', self.family)
|
||||
if self.postscript_name is not None:
|
||||
a('postscript_name', self.postscript_name)
|
||||
if self.full_name is not None:
|
||||
a('full_name', self.full_name)
|
||||
if self.variable_name is not None:
|
||||
a('variable_name', self.variable_name)
|
||||
if self.style is not None:
|
||||
a('style', self.style)
|
||||
if self.axes:
|
||||
for (key, val) in self.axes:
|
||||
a(key, f'{val:g}')
|
||||
return ' '.join(ans)
|
||||
|
||||
|
||||
|
||||
Descriptor = Union[FontConfigPattern, CoreTextFont]
|
||||
DescriptorVar = TypeVar('DescriptorVar', FontConfigPattern, CoreTextFont, Descriptor)
|
||||
|
@ -157,7 +157,7 @@ def find_bold_italic_variant(medium: Descriptor, bold: bool, italic: bool) -> De
|
||||
|
||||
|
||||
def find_best_variable_face(spec: FontSpec, bold: bool, italic: bool, monospaced: bool, candidates: List[Descriptor]) -> Descriptor:
|
||||
if spec.variable_name:
|
||||
if spec.variable_name is not None:
|
||||
q = spec.variable_name.lower()
|
||||
for font in candidates:
|
||||
vd = get_variable_data_for_descriptor(font)
|
||||
@ -177,7 +177,7 @@ def find_best_variable_face(spec: FontSpec, bold: bool, italic: bool, monospaced
|
||||
|
||||
|
||||
def get_fine_grained_font(
|
||||
spec: FontSpec, bold: bool = False, italic: bool = False, medium_font_spec: FontSpec = FontSpec(),
|
||||
spec: FontSpec, bold: bool = False, italic: bool = False,
|
||||
resolved_medium_font: Optional[Descriptor] = None, monospaced: bool = True, match_is_more_specific_than_family: Event = Event()
|
||||
) -> Descriptor:
|
||||
font_map = all_fonts_map(monospaced)
|
||||
@ -241,13 +241,13 @@ def apply_variation_to_pattern(pat: Descriptor, spec: FontSpec) -> Tuple[Descrip
|
||||
|
||||
|
||||
def get_font_from_spec(
|
||||
spec: FontSpec, bold: bool = False, italic: bool = False, medium_font_spec: FontSpec = FontSpec(),
|
||||
spec: FontSpec, bold: bool = False, italic: bool = False,
|
||||
resolved_medium_font: Optional[Descriptor] = None, match_is_more_specific_than_family: Event = Event()
|
||||
) -> Descriptor:
|
||||
if not spec.is_system:
|
||||
return get_fine_grained_font(spec, bold, italic, medium_font_spec, resolved_medium_font,
|
||||
return get_fine_grained_font(spec, bold, italic, resolved_medium_font,
|
||||
match_is_more_specific_than_family=match_is_more_specific_than_family)
|
||||
family = spec.system
|
||||
family = spec.system or ''
|
||||
if family == 'auto':
|
||||
if bold or italic:
|
||||
assert resolved_medium_font is not None
|
||||
@ -288,7 +288,7 @@ def get_font_files(opts: Options) -> FontFiles:
|
||||
kd = {(False, False): 'medium', (True, False): 'bold', (False, True): 'italic', (True, True): 'bi'}
|
||||
for (bold, italic), attr in attr_map.items():
|
||||
if bold or italic:
|
||||
font = get_font_from_spec(getattr(opts, attr), bold, italic, medium_font_spec=opts.font_family, resolved_medium_font=medium_font)
|
||||
font = get_font_from_spec(getattr(opts, attr), bold, italic, resolved_medium_font=medium_font)
|
||||
else:
|
||||
font = medium_font
|
||||
key = kd[(bold, italic)]
|
||||
@ -382,21 +382,19 @@ def get_axis_map(face_or_descriptor: Union[Face, Descriptor]) -> Dict[str, float
|
||||
return base_axis_map
|
||||
|
||||
|
||||
def spec_for_descriptor(descriptor: Descriptor) -> str:
|
||||
from shlex import quote as q
|
||||
if is_variable(descriptor):
|
||||
vd = get_variable_data_for_descriptor(descriptor)
|
||||
spec = f'family={q(descriptor["family"])}'
|
||||
if vd['variations_postscript_name_prefix']:
|
||||
spec += f' variable_name={q(vd["variations_postscript_name_prefix"])}'
|
||||
ns = get_named_style(descriptor)
|
||||
if ns is None:
|
||||
for key, val in get_axis_map(descriptor).items():
|
||||
spec += f' {key}={val:g}'
|
||||
else:
|
||||
spec = f'{spec} style={q(ns["psname"])}'
|
||||
return spec
|
||||
return descriptor['postscript_name']
|
||||
def spec_for_face(family: str, face: Face) -> FontSpec:
|
||||
v = face.get_variation()
|
||||
if v is None:
|
||||
return FontSpec(family=family, postscript_name=face.postscript_name())
|
||||
vd = face.get_variable_data()
|
||||
varname = vd['variations_postscript_name_prefix']
|
||||
ns = get_named_style(face)
|
||||
if ns is None:
|
||||
axes = []
|
||||
for key, val in get_axis_map(face).items():
|
||||
axes.append((key, val))
|
||||
return FontSpec(family=family, variable_name=varname, axes=tuple(axes))
|
||||
return FontSpec(family=family, variable_name=varname, style=ns['psname'] or ns['name'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -8,11 +8,10 @@
|
||||
from typing import Dict, Generator, Iterable, List, NamedTuple, Optional, Sequence, Tuple
|
||||
|
||||
from kitty.fast_data_types import CTFace, coretext_all_fonts
|
||||
from kitty.options.types import Options
|
||||
from kitty.typing import CoreTextFont
|
||||
from kitty.utils import log_error
|
||||
|
||||
from . import Descriptor, DescriptorVar, FontSpec, ListedFont, Score, Scorer, VariableData, family_name_to_key
|
||||
from . import Descriptor, DescriptorVar, ListedFont, Score, Scorer, VariableData, family_name_to_key
|
||||
|
||||
attr_map = {(False, False): 'font_family',
|
||||
(True, False): 'bold_font',
|
||||
@ -202,36 +201,6 @@ def find_best_match(
|
||||
return candidates[0]
|
||||
|
||||
|
||||
def get_font_from_spec(
|
||||
spec: FontSpec, bold: bool = False, italic: bool = False, medium_font_spec: FontSpec = FontSpec(),
|
||||
resolved_medium_font: Optional[CoreTextFont] = None
|
||||
) -> CoreTextFont:
|
||||
if not spec.is_system:
|
||||
raise NotImplementedError('TODO: Implement me')
|
||||
family = spec.system
|
||||
if family == 'auto' and (bold or italic):
|
||||
assert resolved_medium_font is not None
|
||||
family = resolved_medium_font['family']
|
||||
return find_best_match(family, bold, italic, ignore_face=resolved_medium_font)
|
||||
|
||||
|
||||
def get_font_files(opts: Options) -> Dict[str, CoreTextFont]:
|
||||
medium_font = get_font_from_spec(opts.font_family)
|
||||
ans: Dict[str, CoreTextFont] = {}
|
||||
kd = {(False, False): 'medium', (True, False): 'bold', (False, True): 'italic', (True, True): 'bi'}
|
||||
for (bold, italic) in sorted(attr_map):
|
||||
attr = attr_map[(bold, italic)]
|
||||
key = kd[(bold, italic)]
|
||||
if bold or italic:
|
||||
font = get_font_from_spec(getattr(opts, attr), bold, italic, medium_font_spec=opts.font_family, resolved_medium_font=medium_font)
|
||||
else:
|
||||
font = medium_font
|
||||
ans[key] = font
|
||||
if key == 'medium':
|
||||
setattr(get_font_files, 'medium_family', font['family'])
|
||||
return ans
|
||||
|
||||
|
||||
def font_for_family(family: str) -> Tuple[CoreTextFont, bool, bool]:
|
||||
ans = find_best_match(family, monospaced=False)
|
||||
return ans, ans['bold'], ans['italic']
|
||||
|
@ -8,10 +8,18 @@
|
||||
from functools import partial
|
||||
|
||||
from kitty.constants import is_macos, read_kitty_resource
|
||||
from kitty.fast_data_types import DECAWM, get_fallback_font, sprite_map_set_layout, sprite_map_set_limits, test_render_line, test_sprite_position_for, wcwidth
|
||||
from kitty.fast_data_types import (
|
||||
DECAWM,
|
||||
get_fallback_font,
|
||||
sprite_map_set_layout,
|
||||
sprite_map_set_limits,
|
||||
test_render_line,
|
||||
test_sprite_position_for,
|
||||
wcwidth,
|
||||
)
|
||||
from kitty.fonts import family_name_to_key
|
||||
from kitty.fonts.box_drawing import box_chars
|
||||
from kitty.fonts.common import all_fonts_map, face_from_descriptor, get_font_files, get_named_style
|
||||
from kitty.fonts.common import FontSpec, all_fonts_map, face_from_descriptor, get_font_files, get_named_style, spec_for_face
|
||||
from kitty.fonts.render import coalesce_symbol_maps, render_string, setup_for_testing, shape_string
|
||||
from kitty.options.types import Options
|
||||
from kitty.options.utils import parse_font_spec
|
||||
@ -93,6 +101,18 @@ def t(family, psprefix, bold='Bold', italic='Italic', bi='', reg='Regular', allo
|
||||
d = get_font_files(opts)['medium']
|
||||
face = face_from_descriptor(d)
|
||||
self.ae(get_named_style(face)['name'], 'Black')
|
||||
if has('cascadia code'):
|
||||
opts = Options()
|
||||
opts.font_family = parse_font_spec('family="cascadia code"')
|
||||
opts.italic_font = parse_font_spec('family="cascadia code" variable_name= style="Light Italic"')
|
||||
ff = get_font_files(opts)
|
||||
|
||||
def t(x, **kw):
|
||||
kw['family'] = 'Cascadia Code'
|
||||
face = face_from_descriptor(ff[x])
|
||||
self.ae(FontSpec(**kw).as_setting, spec_for_face('Cascadia Code', face).as_setting)
|
||||
t('medium', variable_name='CascadiaCodeRoman', style='Regular')
|
||||
t('italic', variable_name='', style='Light Italic')
|
||||
|
||||
|
||||
class Rendering(BaseTest):
|
||||
|
@ -17,6 +17,7 @@ package shlex
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"kitty/tools/utils"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
@ -182,6 +183,16 @@ func Split(s string) (ans []string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func Quote(s string) string {
|
||||
if s == "" {
|
||||
return s
|
||||
}
|
||||
if utils.MustCompile(`[^\w@%+=:,./-]`).MatchString(s) {
|
||||
return "'" + strings.ReplaceAll(s, "'", "'\"'\"'") + "'"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// SplitForCompletion partitions a string into a slice of strings. It differs from Split in being
|
||||
// more relaxed about errors and also adding an empty string at the end if s ends with a Space.
|
||||
func SplitForCompletion(s string) (argv []string, position_of_last_arg int) {
|
||||
|
Loading…
Reference in New Issue
Block a user