From 42c108a850fb88e78b9a3d990d826ce536a0d6e0 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 16 Jul 2025 20:43:18 -0400 Subject: [PATCH] Deprecate setting text kerning factor to any non-None value This factor existed only to preserve test images, but as of #29816, it is set to 0 (i.e., disabled and providing default behaviour). In the future, with libraqm, it will have no effect no matter its setting (because we won't be applying kerning ourselves at all.) --- .../deprecations/30322-ES.rst | 7 +++++ doc/users/prev_whats_new/whats_new_3.2.0.rst | 29 ++++++------------- lib/matplotlib/__init__.py | 2 ++ lib/matplotlib/ft2font.pyi | 2 +- lib/matplotlib/mpl-data/matplotlibrc | 7 ++--- lib/matplotlib/rcsetup.py | 2 +- lib/matplotlib/tests/test_ft2font.py | 20 ++++++++----- src/ft2font_wrapper.cpp | 20 +++++++------ 8 files changed, 47 insertions(+), 42 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/30322-ES.rst diff --git a/doc/api/next_api_changes/deprecations/30322-ES.rst b/doc/api/next_api_changes/deprecations/30322-ES.rst new file mode 100644 index 000000000000..b9c4964e58c8 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/30322-ES.rst @@ -0,0 +1,7 @@ +Font kerning factor is deprecated +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Due to internal changes to support complex text rendering, the kerning factor on fonts is +no longer used. Setting the ``text.kerning_factor`` rcParam (which existed only for +backwards-compatibility) to any value other than None is deprecated, and the rcParam will +be removed in the future. diff --git a/doc/users/prev_whats_new/whats_new_3.2.0.rst b/doc/users/prev_whats_new/whats_new_3.2.0.rst index 12d7fab3af90..3519245642a8 100644 --- a/doc/users/prev_whats_new/whats_new_3.2.0.rst +++ b/doc/users/prev_whats_new/whats_new_3.2.0.rst @@ -52,26 +52,15 @@ triangle meshes. Kerning adjustments now use correct values ------------------------------------------ -Due to an error in how kerning adjustments were applied, previous versions of -Matplotlib would under-correct kerning. This version will now correctly apply -kerning (for fonts supported by FreeType). To restore the old behavior (e.g., -for test images), you may set :rc:`text.kerning_factor` to 6 (instead of 0). -Other values have undefined behavior. - -.. plot:: - - import matplotlib.pyplot as plt - - # Use old kerning values: - plt.rcParams['text.kerning_factor'] = 6 - fig, ax = plt.subplots() - ax.text(0.0, 0.05, 'BRAVO\nAWKWARD\nVAT\nW.Test', fontsize=56) - ax.set_title('Before (text.kerning_factor = 6)') - -Note how the spacing between characters is uniform between their bounding boxes -(above). With corrected kerning (below), slanted characters (e.g., AV or VA) -will be spaced closer together, as well as various other character pairs, -depending on font support (e.g., T and e, or the period after the W). +Due to an error in how kerning adjustments were applied, previous versions of Matplotlib +would under-correct kerning. This version will now correctly apply kerning (for fonts +supported by FreeType). To restore the old behavior (e.g., for test images), you may set +the ``text.kerning_factor`` rcParam to 6 (instead of 0). Other values have undefined +behavior. + +With corrected kerning (below), slanted characters (e.g., AV or VA) will be spaced closer +together, as well as various other character pairs, depending on font support (e.g., T +and e, or the period after the W). .. plot:: diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 008d4de77a3b..e343e60b5fa1 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -751,6 +751,8 @@ def __setitem__(self, key, val): f"a list of valid parameters)") from err except ValueError as ve: raise ValueError(f"Key {key}: {ve}") from None + if key == "text.kerning_factor" and cval is not None: + _api.warn_deprecated("3.11", name="text.kerning_factor", obj_type="rcParam") self._set(key, cval) def __getitem__(self, key): diff --git a/lib/matplotlib/ft2font.pyi b/lib/matplotlib/ft2font.pyi index a413cd3c1a76..5257893b380a 100644 --- a/lib/matplotlib/ft2font.pyi +++ b/lib/matplotlib/ft2font.pyi @@ -191,7 +191,7 @@ class FT2Font(Buffer): hinting_factor: int = ..., *, _fallback_list: list[FT2Font] | None = ..., - _kerning_factor: int = ... + _kerning_factor: int | None = ... ) -> None: ... if sys.version_info[:2] >= (3, 12): def __buffer__(self, flags: int) -> memoryview: ... diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index 0ab5cfadf291..fb96656c5303 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -306,10 +306,9 @@ #text.hinting_factor: 1 # Specifies the amount of softness for hinting in the # horizontal direction. A value of 1 will hint to full # pixels. A value of 2 will hint to half pixels etc. -#text.kerning_factor: 0 # Specifies the scaling factor for kerning values. This - # is provided solely to allow old test images to remain - # unchanged. Set to 6 to obtain previous behavior. - # Values other than 0 or 6 have no defined meaning. +#text.kerning_factor: None # Specifies the scaling factor for kerning values. Values + # other than 0, 6, or None have no defined meaning. + # This setting is deprecated. #text.antialiased: True # If True (default), the text will be antialiased. # This only affects raster outputs. #text.parse_math: True # Use mathtext if there is an even number of unescaped diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 80d25659888e..b4224d169815 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -1042,7 +1042,7 @@ def _convert_validator_spec(key, conv): "text.hinting": ["default", "no_autohint", "force_autohint", "no_hinting", "auto", "native", "either", "none"], "text.hinting_factor": validate_int, - "text.kerning_factor": validate_int, + "text.kerning_factor": validate_int_or_None, "text.antialiased": validate_bool, "text.parse_math": validate_bool, diff --git a/lib/matplotlib/tests/test_ft2font.py b/lib/matplotlib/tests/test_ft2font.py index b39df1f52996..5dd96ce9cafe 100644 --- a/lib/matplotlib/tests/test_ft2font.py +++ b/lib/matplotlib/tests/test_ft2font.py @@ -168,6 +168,12 @@ def test_ft2font_invalid_args(tmp_path): # kerning_factor argument. with pytest.raises(TypeError, match='incompatible constructor arguments'): ft2font.FT2Font(file, _kerning_factor=1.3) + with pytest.warns(mpl.MatplotlibDeprecationWarning, + match='text.kerning_factor rcParam was deprecated .+ 3.11'): + mpl.rcParams['text.kerning_factor'] = 0 + with pytest.warns(mpl.MatplotlibDeprecationWarning, + match='_kerning_factor parameter was deprecated .+ 3.11'): + ft2font.FT2Font(file, _kerning_factor=123) def test_ft2font_clear(): @@ -188,7 +194,7 @@ def test_ft2font_clear(): def test_ft2font_set_size(): file = fm.findfont('DejaVu Sans') - font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=1) + font = ft2font.FT2Font(file, hinting_factor=1) font.set_size(12, 72) font.set_text('ABabCDcd') orig = font.get_width_height() @@ -717,7 +723,7 @@ def test_ft2font_get_sfnt_table(font_name, header): def test_ft2font_get_kerning(left, right, unscaled, unfitted, default): file = fm.findfont('DejaVu Sans') # With unscaled, these settings should produce exact values found in FontForge. - font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0) + font = ft2font.FT2Font(file, hinting_factor=1) font.set_size(100, 100) assert font.get_kerning(font.get_char_index(ord(left)), font.get_char_index(ord(right)), @@ -756,7 +762,7 @@ def test_ft2font_get_kerning(left, right, unscaled, unfitted, default): def test_ft2font_set_text(): file = fm.findfont('DejaVu Sans') - font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0) + font = ft2font.FT2Font(file, hinting_factor=1) font.set_size(12, 72) xys = font.set_text('') np.testing.assert_array_equal(xys, np.empty((0, 2))) @@ -778,7 +784,7 @@ def test_ft2font_set_text(): def test_ft2font_loading(): file = fm.findfont('DejaVu Sans') - font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0) + font = ft2font.FT2Font(file, hinting_factor=1) font.set_size(12, 72) for glyph in [font.load_char(ord('M')), font.load_glyph(font.get_char_index(ord('M')))]: @@ -819,13 +825,13 @@ def test_ft2font_drawing(): ]) expected *= 255 file = fm.findfont('DejaVu Sans') - font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0) + font = ft2font.FT2Font(file, hinting_factor=1) font.set_size(12, 72) font.set_text('M') font.draw_glyphs_to_bitmap(antialiased=False) image = font.get_image() np.testing.assert_array_equal(image, expected) - font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0) + font = ft2font.FT2Font(file, hinting_factor=1) font.set_size(12, 72) glyph = font.load_char(ord('M')) image = np.zeros(expected.shape, np.uint8) @@ -835,7 +841,7 @@ def test_ft2font_drawing(): def test_ft2font_get_path(): file = fm.findfont('DejaVu Sans') - font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0) + font = ft2font.FT2Font(file, hinting_factor=1) font.set_size(12, 72) vertices, codes = font.get_path() assert vertices.shape == (0, 2) diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index cb816efff9a9..5ba4bec36874 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -432,12 +432,6 @@ const char *PyFT2Font_init__doc__ = R"""( .. warning:: This API is both private and provisional: do not use it directly. - _kerning_factor : int, optional - Used to adjust the degree of kerning. - - .. warning:: - This API is private: do not use it directly. - _warn_if_used : bool, optional Used to trigger missing glyph warnings. @@ -448,11 +442,19 @@ const char *PyFT2Font_init__doc__ = R"""( static PyFT2Font * PyFT2Font_init(py::object filename, long hinting_factor = 8, std::optional> fallback_list = std::nullopt, - int kerning_factor = 0, bool warn_if_used = false) + std::optional kerning_factor = std::nullopt, + bool warn_if_used = false) { if (hinting_factor <= 0) { throw py::value_error("hinting_factor must be greater than 0"); } + if (kerning_factor) { + auto api = py::module_::import("matplotlib._api"); + auto warn = api.attr("warn_deprecated"); + warn("since"_a="3.11", "name"_a="_kerning_factor", "obj_type"_a="parameter"); + } else { + kerning_factor = 0; + } PyFT2Font *self = new PyFT2Font(); self->x = nullptr; @@ -500,7 +502,7 @@ PyFT2Font_init(py::object filename, long hinting_factor = 8, self->x = new FT2Font(open_args, hinting_factor, fallback_fonts, ft_glyph_warn, warn_if_used); - self->x->set_kerning_factor(kerning_factor); + self->x->set_kerning_factor(*kerning_factor); return self; } @@ -1605,7 +1607,7 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) PyFT2Font__doc__) .def(py::init(&PyFT2Font_init), "filename"_a, "hinting_factor"_a=8, py::kw_only(), - "_fallback_list"_a=py::none(), "_kerning_factor"_a=0, + "_fallback_list"_a=py::none(), "_kerning_factor"_a=py::none(), "_warn_if_used"_a=false, PyFT2Font_init__doc__) .def("clear", &PyFT2Font_clear, PyFT2Font_clear__doc__)