Skip to content

Deprecate setting text kerning factor to any non-None value #30322

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: text-overhaul
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/api/next_api_changes/deprecations/30322-ES.rst
Original file line number Diff line number Diff line change
@@ -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.
29 changes: 9 additions & 20 deletions doc/users/prev_whats_new/whats_new_3.2.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines -61 to -71
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to remove this plot from the old what's new, as it will no longer show any difference between it and the plot below (with the correct results) once we switch to libraqm, and so it might be misleading.

(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::

Expand Down
2 changes: 2 additions & 0 deletions lib/matplotlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/ft2font.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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: ...
Expand Down
7 changes: 3 additions & 4 deletions lib/matplotlib/mpl-data/matplotlibrc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down
20 changes: 13 additions & 7 deletions lib/matplotlib/tests/test_ft2font.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This being 1 seems to have been a typo. Since the test checks relative results, it made no difference here.

font = ft2font.FT2Font(file, hinting_factor=1)
font.set_size(12, 72)
font.set_text('ABabCDcd')
orig = font.get_width_height()
Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -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)))
Expand All @@ -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')))]:
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
20 changes: 11 additions & 9 deletions src/ft2font_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -448,11 +442,19 @@ const char *PyFT2Font_init__doc__ = R"""(
static PyFT2Font *
PyFT2Font_init(py::object filename, long hinting_factor = 8,
std::optional<std::vector<PyFT2Font *>> fallback_list = std::nullopt,
int kerning_factor = 0, bool warn_if_used = false)
std::optional<int> 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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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__)
Expand Down
Loading