From ee937bdd1b86a23df4bc1698f01128ea979b753c Mon Sep 17 00:00:00 2001 From: Strykar <2946372+Strykar@users.noreply.github.com> Date: Wed, 13 May 2026 19:36:36 +0530 Subject: [PATCH] freetype: route FC_MATRIX through cairo for color glyphs Color glyphs (COLR/CBDT/SVG) go through cairo on a second FT_Face, self->face_for_cairo, opened in ensure_cairo_resources. That face never receives the FT_Set_Transform installed on self->face in face_from_descriptor (#9990): cairo owns FT_Set_Transform on its own face and derives it from the font matrix on every render (_cairo_ft_unscaled_font_set_scale in cairo-ft-font.c). The only channel that reaches color-glyph rasterization is the cairo font matrix, not the FT face transform, so FC_MATRIX is silently dropped on that path. Stock fontconfig rules do not apply FC_MATRIX to color fonts, so this is a hand-built config edge case. Factor the two cairo_set_font_size() call sites in set_cairo_font_size and fit_cairo_glyph into apply_cairo_font_size(), which calls cairo_set_font_matrix() with size * FC_MATRIX when has_matrix is set, and falls through to cairo_set_font_size() otherwise. The non-matrix path is bit-identical to before. For pure shears (xx=1, yy=1) cairo_scaled_font_glyph_extents reads the post-shear bbox so the shrink loop bounds the destination correctly; the user-visible effect is that a sheared color glyph reports a wider bbox and shrinks more aggressively to fit a cell than its upright sibling. Acceptable for the hand-built edge case this PR scopes to. FT_Matrix stores xx,xy,yx,yy in row-major order; cairo_matrix_init takes xx,yx,xy,yy. Same matrix, transposed argument order - pinned with a comment because it is easy to flip. Refs: #9990 --- kitty/freetype.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/kitty/freetype.c b/kitty/freetype.c index 7da2b1dab..c1e6a189b 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -871,11 +871,33 @@ pt_to_px(double pt, double dpi) { return ((long)round((pt * (dpi / 72.0)))); } +static void +apply_cairo_font_size(Face *self, unsigned sz_px) { + // The cairo path uses self->face_for_cairo (a second FT_Face opened in + // ensure_cairo_resources), which never receives the FT_Set_Transform set + // on self->face in face_from_descriptor. cairo owns FT_Set_Transform on + // its face and derives it from the font matrix on every render + // (_cairo_ft_unscaled_font_set_scale in cairo-ft-font.c), so the only + // channel that reaches glyph rasterization is the cairo font matrix + // itself. Encode FC_MATRIX there. + // FT_Matrix is xx,xy,yx,yy (row-major); cairo_matrix_init takes + // xx,yx,xy,yy. Same matrix, transposed argument order. + if (!self->has_matrix) { cairo_set_font_size(self->cairo.cr, sz_px); return; } + double s = (double)sz_px; + double xx = self->matrix.xx / 65536.0; + double xy = self->matrix.xy / 65536.0; + double yx = self->matrix.yx / 65536.0; + double yy = self->matrix.yy / 65536.0; + cairo_matrix_t m; + cairo_matrix_init(&m, xx * s, yx * s, xy * s, yy * s, 0, 0); + cairo_set_font_matrix(self->cairo.cr, &m); +} + static void set_cairo_font_size(Face *self, double size_in_pts) { unsigned sz_px = pt_to_px(size_in_pts, (self->xdpi + self->ydpi) / 2.0); if (self->cairo.size_in_px == sz_px) return; - cairo_set_font_size(self->cairo.cr, sz_px); + apply_cairo_font_size(self, sz_px); self->cairo.size_in_px = sz_px; } @@ -885,7 +907,7 @@ fit_cairo_glyph(Face *self, cairo_glyph_t *g, cairo_text_extents_t *bb, cairo_sc double ratio = MIN(width / bb->width, height / bb->height); unsigned sz = (unsigned)(ratio * self->cairo.size_in_px); if (sz >= self->cairo.size_in_px) sz = self->cairo.size_in_px - 2; - cairo_set_font_size(self->cairo.cr, sz); + apply_cairo_font_size(self, sz); sf = cairo_get_scaled_font(self->cairo.cr); cairo_scaled_font_glyph_extents(sf, g, 1, bb); self->cairo.size_in_px = sz;