mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-07-07 21:26:39 +08:00
242 lines
6.4 KiB
Python
242 lines
6.4 KiB
Python
from collections.abc import Sequence
|
|
from enum import Enum, IntEnum, auto
|
|
from typing import TYPE_CHECKING, Literal, NamedTuple, TypedDict, TypeVar, Union
|
|
|
|
from kitty.fast_data_types import ParsedFontFeature
|
|
from kitty.types import run_once
|
|
from kitty.typing_compat import CoreTextFont, FontConfigPattern
|
|
from kitty.utils import shlex_split
|
|
|
|
if TYPE_CHECKING:
|
|
import re
|
|
|
|
|
|
class ListedFont(TypedDict):
|
|
family: str
|
|
style: str
|
|
full_name: str
|
|
postscript_name: str
|
|
is_monospace: bool
|
|
is_variable: bool
|
|
descriptor: FontConfigPattern | CoreTextFont
|
|
|
|
|
|
class VariableAxis(TypedDict):
|
|
minimum: float
|
|
maximum: float
|
|
default: float
|
|
hidden: bool
|
|
tag: str
|
|
strid: str # Can be empty string when not present
|
|
|
|
|
|
class NamedStyle(TypedDict):
|
|
axis_values: dict[str, float]
|
|
name: str
|
|
psname: str # can be empty string when not present
|
|
|
|
|
|
class DesignValue1(TypedDict):
|
|
format: Literal[1]
|
|
flags: int
|
|
name: str
|
|
value: float
|
|
|
|
|
|
class DesignValue2(TypedDict):
|
|
format: Literal[2]
|
|
flags: int
|
|
name: str
|
|
value: float
|
|
minimum: float
|
|
maximum: float
|
|
|
|
|
|
class DesignValue3(TypedDict):
|
|
format: Literal[3]
|
|
flags: int
|
|
name: str
|
|
value: float
|
|
linked_value: float
|
|
|
|
|
|
DesignValue = Union[DesignValue1, DesignValue2, DesignValue3]
|
|
|
|
|
|
class DesignAxis(TypedDict):
|
|
name: str
|
|
ordering: int
|
|
tag: str
|
|
values: list[DesignValue]
|
|
|
|
|
|
class AxisValue(TypedDict):
|
|
design_index: int
|
|
value: float
|
|
|
|
|
|
class MultiAxisStyle(TypedDict):
|
|
flags: int
|
|
name: str
|
|
values: tuple[AxisValue, ...]
|
|
|
|
|
|
class VariableData(TypedDict):
|
|
axes: tuple[VariableAxis, ...]
|
|
named_styles: tuple[NamedStyle, ...]
|
|
variations_postscript_name_prefix: str
|
|
elided_fallback_name: str
|
|
design_axes: tuple[DesignAxis, ...]
|
|
multi_axis_styles: tuple[MultiAxisStyle, ...]
|
|
|
|
|
|
class ModificationType(Enum):
|
|
underline_position = auto()
|
|
underline_thickness = auto()
|
|
strikethrough_position = auto()
|
|
strikethrough_thickness = auto()
|
|
cell_width = auto()
|
|
cell_height = auto()
|
|
baseline = auto()
|
|
size = auto()
|
|
|
|
|
|
class ModificationUnit(IntEnum):
|
|
pt = 0
|
|
percent = 1
|
|
pixel = 2
|
|
|
|
|
|
class ModificationValue(NamedTuple):
|
|
val: float
|
|
unit: ModificationUnit
|
|
|
|
def __repr__(self) -> str:
|
|
u = '%' if self.unit is ModificationUnit.percent else ''
|
|
return f'{self.val:g}{u}'
|
|
|
|
|
|
class FontModification(NamedTuple):
|
|
mod_type: ModificationType
|
|
mod_value: ModificationValue
|
|
font_name: str = ''
|
|
|
|
def __repr__(self) -> str:
|
|
fn = f' {self.font_name}' if self.font_name else ''
|
|
return f'{self.mod_type.name}{fn} {self.mod_value}'
|
|
|
|
|
|
class FontSpec(NamedTuple):
|
|
family: str | None = None
|
|
style: str | None = None
|
|
postscript_name: str | None = None
|
|
full_name: str | None = None
|
|
system: str | None = None
|
|
axes: tuple[tuple[str, float], ...] = ()
|
|
variable_name: str | None = None
|
|
features: tuple[ParsedFontFeature, ...] = ()
|
|
created_from_string: str = ''
|
|
|
|
@classmethod
|
|
def from_setting(cls, spec: str) -> 'FontSpec':
|
|
if spec == 'auto':
|
|
return FontSpec(system='auto', created_from_string=spec)
|
|
items = tuple(shlex_split(spec))
|
|
if '=' not in items[0]:
|
|
return FontSpec(system=spec, created_from_string=spec)
|
|
axes = {}
|
|
defined = {}
|
|
features: tuple[ParsedFontFeature, ...] = ()
|
|
for item in items:
|
|
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', 'variable_name'):
|
|
defined[k] = v
|
|
elif k == 'features':
|
|
features += tuple(ParsedFontFeature(x) for x in v.split())
|
|
else:
|
|
try:
|
|
axes[k] = float(v)
|
|
except Exception:
|
|
raise ValueError(f'The font specification: {spec} is not valid as {v} is not a number')
|
|
return FontSpec(axes=tuple(axes.items()), created_from_string=spec, features=features, **defined)
|
|
|
|
@property
|
|
def is_system(self) -> bool:
|
|
return bool(self.system)
|
|
|
|
@property
|
|
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.features:
|
|
a('features', ' '.join(str(f) for f in self.features))
|
|
if self.axes:
|
|
for (key, val) in self.axes:
|
|
a(key, f'{val:g}')
|
|
return ' '.join(ans)
|
|
|
|
def __str__(self) -> str:
|
|
return self.as_setting
|
|
|
|
# Cannot change __repr__ as it will break config generation
|
|
|
|
|
|
Descriptor = Union[FontConfigPattern, CoreTextFont]
|
|
DescriptorVar = TypeVar('DescriptorVar', FontConfigPattern, CoreTextFont, Descriptor)
|
|
|
|
|
|
class Score(NamedTuple):
|
|
variable_score: int
|
|
style_score: float
|
|
monospace_score: int
|
|
width_score: int
|
|
|
|
|
|
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
|
|
def fnname_pat() -> 're.Pattern[str]':
|
|
import re
|
|
return re.compile(r'\s+')
|
|
|
|
|
|
def family_name_to_key(family: str) -> str:
|
|
return fnname_pat().sub(' ', family).strip().lower()
|