mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 14:18:26 +02:00
Read FC_MATRIX from fontconfig
pattern_as_dict() in fontconfig.c never read FC_MATRIX, so any per-font transform set by fontconfig was silently dropped. fontconfig ships a default rule (90-synthetic.conf) that applies a slant matrix to any roman-only font when italic is requested, which is why italic CJK has been rendering upright in kitty. Read the matrix, carry it on the descriptor as a 4-tuple of doubles, apply it once in face_from_descriptor() via FT_Set_Transform, also inform HarfBuzz via hb_font_set_synthetic_slant + hb_ft_font_changed so shaping reflects the slanted rendering. Extend face_equals_descriptor() to compare the matrix so the per-FontGroup fallback cache returns the right face when upright and italic share a font file. The FT transform is sticky on the face, so subsequent FT_Load_Glyph calls inherit it with no per-call overhead, and the per-Face glyph atlas cache stays correct because the matrix is set at init and never changes. Pure shears (xx=1, yy=1) preserve horizontal advance and do not disturb monospace cell width. The HB synthetic_slant call is gated on HB_VERSION_ATLEAST(4,0,0) since setup.py allows down to 1.5.0. hb_ft_font_changed runs unconditionally to invalidate any populated caches. Refs #9857, #9700.
This commit is contained in:
@@ -41,6 +41,7 @@ static struct {PyObject *face, *descriptor;} builtin_nerd_font = {0};
|
|||||||
#define FcPatternAddInteger dynamically_loaded_fc_symbol.PatternAddInteger
|
#define FcPatternAddInteger dynamically_loaded_fc_symbol.PatternAddInteger
|
||||||
#define FcPatternCreate dynamically_loaded_fc_symbol.PatternCreate
|
#define FcPatternCreate dynamically_loaded_fc_symbol.PatternCreate
|
||||||
#define FcPatternGetBool dynamically_loaded_fc_symbol.PatternGetBool
|
#define FcPatternGetBool dynamically_loaded_fc_symbol.PatternGetBool
|
||||||
|
#define FcPatternGetMatrix dynamically_loaded_fc_symbol.PatternGetMatrix
|
||||||
#define FcPatternAddCharSet dynamically_loaded_fc_symbol.PatternAddCharSet
|
#define FcPatternAddCharSet dynamically_loaded_fc_symbol.PatternAddCharSet
|
||||||
#define FcConfigAppFontAddFile dynamically_loaded_fc_symbol.ConfigAppFontAddFile
|
#define FcConfigAppFontAddFile dynamically_loaded_fc_symbol.ConfigAppFontAddFile
|
||||||
|
|
||||||
@@ -66,6 +67,7 @@ static struct {
|
|||||||
FcBool (*PatternAddInteger) (FcPattern *p, const char *object, int i);
|
FcBool (*PatternAddInteger) (FcPattern *p, const char *object, int i);
|
||||||
FcPattern * (*PatternCreate) (void);
|
FcPattern * (*PatternCreate) (void);
|
||||||
FcResult (*PatternGetBool) (const FcPattern *p, const char *object, int n, FcBool *b);
|
FcResult (*PatternGetBool) (const FcPattern *p, const char *object, int n, FcBool *b);
|
||||||
|
FcResult (*PatternGetMatrix) (const FcPattern *p, const char *object, int n, FcMatrix **m);
|
||||||
FcBool (*PatternAddCharSet) (FcPattern *p, const char *object, const FcCharSet *c);
|
FcBool (*PatternAddCharSet) (FcPattern *p, const char *object, const FcCharSet *c);
|
||||||
FcBool (*ConfigAppFontAddFile) (FcConfig *config, const FcChar8 *file);
|
FcBool (*ConfigAppFontAddFile) (FcConfig *config, const FcChar8 *file);
|
||||||
} dynamically_loaded_fc_symbol = {0};
|
} dynamically_loaded_fc_symbol = {0};
|
||||||
@@ -117,6 +119,7 @@ load_fontconfig_lib(void) {
|
|||||||
LOAD_FUNC(PatternAddInteger);
|
LOAD_FUNC(PatternAddInteger);
|
||||||
LOAD_FUNC(PatternCreate);
|
LOAD_FUNC(PatternCreate);
|
||||||
LOAD_FUNC(PatternGetBool);
|
LOAD_FUNC(PatternGetBool);
|
||||||
|
LOAD_FUNC(PatternGetMatrix);
|
||||||
LOAD_FUNC(PatternAddCharSet);
|
LOAD_FUNC(PatternAddCharSet);
|
||||||
LOAD_FUNC(ConfigAppFontAddFile);
|
LOAD_FUNC(ConfigAppFontAddFile);
|
||||||
}
|
}
|
||||||
@@ -210,6 +213,13 @@ pattern_as_dict(FcPattern *pat) {
|
|||||||
B(FC_OUTLINE, outline);
|
B(FC_OUTLINE, outline);
|
||||||
B(FC_COLOR, color);
|
B(FC_COLOR, color);
|
||||||
E(FC_SPACING, spacing, pyspacing);
|
E(FC_SPACING, spacing, pyspacing);
|
||||||
|
{
|
||||||
|
FcMatrix *mtx = NULL;
|
||||||
|
if (FcPatternGetMatrix(pat, FC_MATRIX, 0, &mtx) == FcResultMatch && mtx) {
|
||||||
|
RAII_PyObject(t, Py_BuildValue("(dddd)", mtx->xx, mtx->xy, mtx->yx, mtx->yy));
|
||||||
|
if (!t || PyDict_SetItemString(ans, "matrix", t) != 0) return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Py_INCREF(ans);
|
Py_INCREF(ans);
|
||||||
return ans;
|
return ans;
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ typedef struct {
|
|||||||
PyObject *name_lookup_table;
|
PyObject *name_lookup_table;
|
||||||
FontFeatures font_features;
|
FontFeatures font_features;
|
||||||
unsigned short dark_palette_index, light_palette_index, palettes_scanned;
|
unsigned short dark_palette_index, light_palette_index, palettes_scanned;
|
||||||
|
FT_Matrix matrix;
|
||||||
|
bool has_matrix;
|
||||||
} Face;
|
} Face;
|
||||||
PyTypeObject Face_Type;
|
PyTypeObject Face_Type;
|
||||||
|
|
||||||
@@ -284,6 +286,29 @@ set_load_error(const char *path, int error) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
read_matrix_from_descriptor(PyObject *descriptor, FT_Matrix *out, bool *out_has) {
|
||||||
|
*out_has = false;
|
||||||
|
PyObject *mt = PyDict_GetItemString(descriptor, "matrix");
|
||||||
|
if (!mt || !PyTuple_Check(mt) || PyTuple_GET_SIZE(mt) != 4) return true;
|
||||||
|
double v[4];
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
v[i] = PyFloat_AsDouble(PyTuple_GET_ITEM(mt, i));
|
||||||
|
if (PyErr_Occurred()) return false;
|
||||||
|
if (!isfinite(v[i])) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "matrix contains non-finite value");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (v[0] == 1.0 && v[1] == 0.0 && v[2] == 0.0 && v[3] == 1.0) return true;
|
||||||
|
out->xx = (FT_Fixed)(v[0] * 0x10000);
|
||||||
|
out->xy = (FT_Fixed)(v[1] * 0x10000);
|
||||||
|
out->yx = (FT_Fixed)(v[2] * 0x10000);
|
||||||
|
out->yy = (FT_Fixed)(v[3] * 0x10000);
|
||||||
|
*out_has = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
face_equals_descriptor(PyObject *face_, PyObject *descriptor) {
|
face_equals_descriptor(PyObject *face_, PyObject *descriptor) {
|
||||||
Face *face = (Face*)face_;
|
Face *face = (Face*)face_;
|
||||||
@@ -292,6 +317,13 @@ face_equals_descriptor(PyObject *face_, PyObject *descriptor) {
|
|||||||
if (PyObject_RichCompareBool(face->path, t, Py_EQ) != 1) return false;
|
if (PyObject_RichCompareBool(face->path, t, Py_EQ) != 1) return false;
|
||||||
t = PyDict_GetItemString(descriptor, "index");
|
t = PyDict_GetItemString(descriptor, "index");
|
||||||
if (t && PyLong_AsLong(t) != face->face->face_index) return false;
|
if (t && PyLong_AsLong(t) != face->face->face_index) return false;
|
||||||
|
FT_Matrix dmat = {0};
|
||||||
|
bool d_has = false;
|
||||||
|
if (!read_matrix_from_descriptor(descriptor, &dmat, &d_has)) { PyErr_Clear(); return false; }
|
||||||
|
if (d_has != face->has_matrix) return false;
|
||||||
|
if (d_has && (
|
||||||
|
face->matrix.xx != dmat.xx || face->matrix.xy != dmat.xy ||
|
||||||
|
face->matrix.yx != dmat.yx || face->matrix.yy != dmat.yy)) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,6 +371,29 @@ face_from_descriptor(PyObject *descriptor, FONTS_DATA_HANDLE fg) {
|
|||||||
if ((error = FT_Set_Var_Design_Coordinates(self->face, sz, coords))) return set_load_error(path, error);
|
if ((error = FT_Set_Var_Design_Coordinates(self->face, sz, coords))) return set_load_error(path, error);
|
||||||
}
|
}
|
||||||
if (!create_features_for_face(postscript_name_for_face((PyObject*)self), PyDict_GetItemString(descriptor, "features"), &self->font_features)) return NULL;
|
if (!create_features_for_face(postscript_name_for_face((PyObject*)self), PyDict_GetItemString(descriptor, "features"), &self->font_features)) return NULL;
|
||||||
|
if (!read_matrix_from_descriptor(descriptor, &self->matrix, &self->has_matrix)) return NULL;
|
||||||
|
if (self->has_matrix) {
|
||||||
|
FT_Set_Transform(self->face, &self->matrix, NULL);
|
||||||
|
if (self->harfbuzz_font) {
|
||||||
|
#if HB_VERSION_ATLEAST(4,0,0)
|
||||||
|
// Inform HarfBuzz so shaping (mark positioning, cluster
|
||||||
|
// boundaries) matches the slanted rendering. The HB API
|
||||||
|
// models a horizontal shear ratio, which equals xy/xx for
|
||||||
|
// matrix [[xx,xy],[yx,yy]]. Both operands are FT_Fixed at
|
||||||
|
// the same 16.16 scale so the factor cancels in the
|
||||||
|
// division. Guard against xx==0 (degenerate transform).
|
||||||
|
// Pure scales (s,0,0,s) yield slant=0, which is correct.
|
||||||
|
// Rotations and shear+rotation composites produce a slant
|
||||||
|
// value HB can't represent faithfully, but stock fontconfig
|
||||||
|
// only ever emits horizontal shear here.
|
||||||
|
if (self->matrix.xx != 0) {
|
||||||
|
float slant = (float)self->matrix.xy / (float)self->matrix.xx;
|
||||||
|
hb_font_set_synthetic_slant(self->harfbuzz_font, slant);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
hb_ft_font_changed(self->harfbuzz_font);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Py_XINCREF(retval);
|
Py_XINCREF(retval);
|
||||||
return retval;
|
return retval;
|
||||||
|
|||||||
Reference in New Issue
Block a user