Merge branch 'render-time-matrix' of https://github.com/Strykar/kitty

This commit is contained in:
Kovid Goyal 2026-06-13 12:49:05 +05:30
commit aa4e94cef5
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
3 changed files with 71 additions and 1 deletions

View file

@ -458,6 +458,10 @@ specialize_font_descriptor(PyObject *base_descriptor, double font_sz_in_pts, dou
if (axes) {
if (PyDict_SetItemString(ans, "axes", axes) != 0) return NULL;
}
PyObject *matrix = PyDict_GetItemString(base_descriptor, "matrix");
if (matrix) {
if (PyDict_SetItemString(ans, "matrix", matrix) != 0) return NULL;
}
PyObject *ff = PyDict_GetItemString(ans, "fontfeatures");
if (ff && PyList_GET_SIZE(ff)) {
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(ff); i++) {

View file

@ -401,7 +401,38 @@ def get_font_files(opts: Options) -> FontFiles:
font = medium_font
key = kd[(bold, italic)]
ans[key] = font
return {'medium': ans['medium'], 'bold': ans['bold'], 'italic': ans['italic'], 'bi': ans['bi']}
def apply_synthetic_matrix(font: Descriptor, bold: bool, italic: bool) -> Descriptor:
# fontconfig's FcFontList (used by find_best_match) omits FC_MATRIX from
# its object set, so a roman font found there carries no synthetic-italic
# shear and its "italic" renders upright. Fira Code is the case (it ships
# no italic), in both its static and variable builds. The italic intent
# exists only here at selection finalize, not at face construction, so
# recover the matrix now: for an italic slot whose chosen face is upright
# (no slant) and has no matrix yet, ask fc_match what fontconfig would do.
# fc_match returns a synthetic matrix only when there is no real italic to
# use (no italic face and no slanted named instance or variable slant
# axis); when a real italic exists it returns no matrix, so a font that is
# already italic, static or variable, is never double-slanted. Face construction applies the matrix
# via FT_Set_Transform; specialize_font_descriptor preserves it when the
# descriptor is sized for rendering. Only the matrix is taken, so
# selection is unchanged. Covers the four configured faces; fc_match
# re-matches by family name (see commit message).
if (italic and font['descriptor_type'] == 'fontconfig'
and not font.get('matrix') and not font.get('slant')):
from kitty.fast_data_types import FC_MONO
from kitty.fonts.fontconfig import fc_match
mtx = fc_match(font['family'], bold, italic, FC_MONO if is_monospace(font) else -1).get('matrix')
if mtx:
new_font = font.copy()
new_font['matrix'] = mtx
return new_font
return font
return {
'medium': ans['medium'], 'bold': ans['bold'],
'italic': apply_synthetic_matrix(ans['italic'], False, True),
'bi': apply_synthetic_matrix(ans['bi'], True, True),
}
def axis_values_are_equal(defaults: dict[str, float], a: dict[str, float], b: dict[str, float]) -> bool:

View file

@ -173,6 +173,41 @@ class Selection(BaseTest):
self.ae(face_from_descriptor(ff['medium']).applied_features(), {'dlig': 'dlig', 'test': 'test=3'})
self.ae(face_from_descriptor(ff['bold']).applied_features(), {'dlig': 'dlig', 'test': 'test=3'})
def test_synthetic_italic_matrix(self):
# A roman-only font that find_best_match finds (e.g. Fira Code, which ships
# no italic face) must get fontconfig's synthetic-italic FC_MATRIX
# (90-synthetic.conf) attached, so its italic renders slanted rather than
# upright; real-italic faces must not. The shear value is fontconfig's, not
# ours, so assert the invariant (a non-identity matrix is present), not the
# exact tuple, for cross-config stability.
if is_macos:
self.skipTest('synthetic-italic FC_MATRIX is a fontconfig feature')
from kitty.fonts.fontconfig import FC_MONO, fc_match
names = set(all_fonts_map(True)['family_map']) | set(all_fonts_map(True)['variable_map'])
if family_name_to_key('fira code') not in names:
self.skipTest('Fira Code not installed')
# Probe fc_match directly so we can tell "environment lacks the rule" (skip)
# from "code did not attach the matrix" (fail).
if fc_match('Fira Code', False, True, FC_MONO).get('matrix') is None:
self.skipTest('fontconfig 90-synthetic.conf not active; no synthetic-italic matrix')
opts = Options()
opts.font_family = parse_font_spec('Fira Code')
ff = get_font_files(opts)
self.assertIsNone(ff['medium'].get('matrix')) # upright stays upright
mi = ff['italic'].get('matrix')
self.assertIsNotNone(mi) # roman, no italic -> sheared
self.assertNotEqual(mi[1], 0.0) # actually slanted, not identity
# Faces are built from a size-specialized descriptor at render time; the
# matrix must survive specialize_font_descriptor or the glyphs render
# upright despite the descriptor above being correct.
from kitty.fast_data_types import specialize_font_descriptor
sd = specialize_font_descriptor(dict(ff['italic']), 12.0, 96.0, 96.0)
self.ae(sd.get('matrix'), mi)
if family_name_to_key('liberation mono') in names: # real-italic control
opts.font_family = parse_font_spec('Liberation Mono')
self.assertIsNone(get_font_files(opts)['italic'].get('matrix'))
def block_helpers(s, sprites, cell_width, cell_height):
mr = {}
actual = b''