fonts: make the color emoji regression test environment-independent

The coverage threshold (0.5 in fbd4e5ad1, lowered to 0.3 in d4106ef2d to
get CI green) is environment-dependent: a correctly sized emoji covers
~0.84 of its cell on one box but ~0.39 in CI, while the buggy render is
~0.28, so the margin is thin and font/cell dependent.

Render the same font two ways at one size instead: via its fontconfig
descriptor (which carries the size-fixup matrix) and via its file path
(which does not). They must come out the same size; the bug shrinks the
descriptor one. Only the matrix differs, so the check no longer depends
on the environment or the emoji artwork.
This commit is contained in:
Strykar
2026-06-14 21:00:12 +05:30
parent d4106ef2db
commit fc23ef57ec

View File

@@ -392,28 +392,27 @@ class Rendering(FontBaseTest):
def test_color_emoji_not_shrunk(self): def test_color_emoji_not_shrunk(self):
# Regression test for https://github.com/kovidgoyal/kitty/issues/10144. # Regression test for https://github.com/kovidgoyal/kitty/issues/10144.
# fontconfig encodes the pixel-size fixup of fixed-size color faces (such # fontconfig gives fixed-size color faces (e.g. Noto Color Emoji) a
# as Noto Color Emoji, a ~109px bitmap strike) as FC_MATRIX. That scale # pixel-size fixup encoded as FC_MATRIX. That scale must not reach the
# must not reach the cairo font matrix, where glyph size is owned by # cairo font matrix used for color glyphs; applying it shrinks color emoji
# cairo_set_font_size() + fit_cairo_glyph(); applying it there shrinks the # to a dot (ee937bdd1b). Render the same font two ways at the same size:
# glyph by the fixup factor (requested_px / strike_px), and fit_cairo_glyph() # from its fontconfig descriptor, which carries the fixup matrix, and from
# only shrinks so it never grows it back (ee937bdd1b). The shrink ranges # its file path, which does not. A correct build renders both at the same
# from ~9x at tiny cells to ~1x near the strike; at 48pt/96dpi (~64px) the # size; the bug shrinks the descriptor one. Comparing the two is
# bug gives ~0.28 coverage versus ~0.84 when correct. # environment-independent since only the matrix differs.
if is_macos: if is_macos:
self.skipTest('this is a fontconfig FC_MATRIX issue, not present on macOS') self.skipTest('FC_MATRIX is a fontconfig feature, not used on macOS')
from kitty.fonts.fontconfig import fc_match from kitty.fonts.fontconfig import fc_match
emoji = fc_match('emoji', False, False, 0) desc = dict(fc_match('emoji', False, False, 0))
if not (emoji.get('color') and emoji.get('matrix')): if not (desc.get('color') and desc.get('matrix')):
# Only a fixed-size color face that fontconfig gives a fixup matrix can
# trigger this. A scalable COLR emoji font has none, so a pass there
# would be a false negative rather than a real check.
self.skipTest('no fixed-size color emoji font with a fontconfig fixup matrix') self.skipTest('no fixed-size color emoji font with a fontconfig fixup matrix')
cells = render_string('\U0001F40D', 'monospace', 48.0, 96.0)[2] with_matrix = face_from_descriptor(desc)
pixels = array.array('I', b''.join(cells)) with_matrix.set_size(64, 96, 96)
coverage = sum(1 for p in pixels if p) / max(len(pixels), 1) without_matrix = create_face(desc['path'])
del cells, pixels without_matrix.set_size(64, 96, 96)
self.assertGreater(coverage, 0.3, f'color emoji coverage {coverage:.2f} too low, likely shrunk (#10144)') _, mw, mh = with_matrix.render_codepoint(0x1F40D)
_, rw, rh = without_matrix.render_codepoint(0x1F40D)
self.assertGreater(mh, 0.5 * rh, f'color emoji shrunk by FC_MATRIX: {mh}px vs {rh}px (#10144)')
def test_shaping(self): def test_shaping(self):