mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-05-13 08:26:56 +00: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:
parent
21d8b2bcc0
commit
b3e7c3e717
2 changed files with 65 additions and 0 deletions
|
|
@ -41,6 +41,7 @@ static struct {PyObject *face, *descriptor;} builtin_nerd_font = {0};
|
|||
#define FcPatternAddInteger dynamically_loaded_fc_symbol.PatternAddInteger
|
||||
#define FcPatternCreate dynamically_loaded_fc_symbol.PatternCreate
|
||||
#define FcPatternGetBool dynamically_loaded_fc_symbol.PatternGetBool
|
||||
#define FcPatternGetMatrix dynamically_loaded_fc_symbol.PatternGetMatrix
|
||||
#define FcPatternAddCharSet dynamically_loaded_fc_symbol.PatternAddCharSet
|
||||
#define FcConfigAppFontAddFile dynamically_loaded_fc_symbol.ConfigAppFontAddFile
|
||||
|
||||
|
|
@ -66,6 +67,7 @@ static struct {
|
|||
FcBool (*PatternAddInteger) (FcPattern *p, const char *object, int i);
|
||||
FcPattern * (*PatternCreate) (void);
|
||||
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 (*ConfigAppFontAddFile) (FcConfig *config, const FcChar8 *file);
|
||||
} dynamically_loaded_fc_symbol = {0};
|
||||
|
|
@ -117,6 +119,7 @@ load_fontconfig_lib(void) {
|
|||
LOAD_FUNC(PatternAddInteger);
|
||||
LOAD_FUNC(PatternCreate);
|
||||
LOAD_FUNC(PatternGetBool);
|
||||
LOAD_FUNC(PatternGetMatrix);
|
||||
LOAD_FUNC(PatternAddCharSet);
|
||||
LOAD_FUNC(ConfigAppFontAddFile);
|
||||
}
|
||||
|
|
@ -210,6 +213,13 @@ pattern_as_dict(FcPattern *pat) {
|
|||
B(FC_OUTLINE, outline);
|
||||
B(FC_COLOR, color);
|
||||
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);
|
||||
return ans;
|
||||
|
|
|
|||
|
|
@ -66,6 +66,8 @@ typedef struct {
|
|||
PyObject *name_lookup_table;
|
||||
FontFeatures font_features;
|
||||
unsigned short dark_palette_index, light_palette_index, palettes_scanned;
|
||||
FT_Matrix matrix;
|
||||
bool has_matrix;
|
||||
} Face;
|
||||
PyTypeObject Face_Type;
|
||||
|
||||
|
|
@ -284,6 +286,29 @@ set_load_error(const char *path, int error) {
|
|||
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
|
||||
face_equals_descriptor(PyObject *face_, PyObject *descriptor) {
|
||||
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;
|
||||
t = PyDict_GetItemString(descriptor, "index");
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -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 (!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);
|
||||
return retval;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue