Skip to content
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
79 changes: 79 additions & 0 deletions doc/api/next_api_changes/removals/31879-ES.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
ft2font module-level constants replaced by enums
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The `.ft2font`-level constants have been converted to `enum` classes, and all API using
them now take/return the new types. Any access to the old module-level names has been
removed.

The following constants are now part of `.ft2font.Kerning` (without the ``KERNING_``
prefix):

- ``KERNING_DEFAULT``
- ``KERNING_UNFITTED``
- ``KERNING_UNSCALED``

The following constants are now part of `.ft2font.LoadFlags` (without the ``LOAD_``
prefix):

- ``LOAD_DEFAULT``
- ``LOAD_NO_SCALE``
- ``LOAD_NO_HINTING``
- ``LOAD_RENDER``
- ``LOAD_NO_BITMAP``
- ``LOAD_VERTICAL_LAYOUT``
- ``LOAD_FORCE_AUTOHINT``
- ``LOAD_CROP_BITMAP``
- ``LOAD_PEDANTIC``
- ``LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH``
- ``LOAD_NO_RECURSE``
- ``LOAD_IGNORE_TRANSFORM``
- ``LOAD_MONOCHROME``
- ``LOAD_LINEAR_DESIGN``
- ``LOAD_NO_AUTOHINT``
- ``LOAD_TARGET_NORMAL``
- ``LOAD_TARGET_LIGHT``
- ``LOAD_TARGET_MONO``
- ``LOAD_TARGET_LCD``
- ``LOAD_TARGET_LCD_V``

The following constants are now part of `.ft2font.FaceFlags`:

- ``EXTERNAL_STREAM``
- ``FAST_GLYPHS``
- ``FIXED_SIZES``
- ``FIXED_WIDTH``
- ``GLYPH_NAMES``
- ``HORIZONTAL``
- ``KERNING``
- ``MULTIPLE_MASTERS``
- ``SCALABLE``
- ``SFNT``
- ``VERTICAL``

The following constants are now part of `.ft2font.StyleFlags`:

- ``ITALIC``
- ``BOLD``

FontProperties initialization
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

`.FontProperties` initialization is limited to the two call patterns:

- single positional parameter, interpreted as fontconfig pattern
- only keyword parameters for setting individual properties

All other previously supported call patterns are no longer supported.

Passing floating-point values to ``RendererAgg.draw_text_image``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Any floating-point values passed to the *x* and *y* parameters were truncated to integers
silently. This behaviour is no longer allowed, and only `int` values should be used.

Passing floating-point values to ``FT2Image``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Any floating-point values passed to the `.FT2Image` constructor, or the *x0*, *y0*, *x1*,
and *y1* parameters of `.FT2Image.draw_rect_filled` were truncated to integers silently.
This behaviour is no longer allowed, and only `int` values should be used.
73 changes: 12 additions & 61 deletions lib/matplotlib/font_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
from base64 import b64encode
import dataclasses
from functools import cache, lru_cache
import functools
from io import BytesIO
import json
import logging
Expand Down Expand Up @@ -695,57 +694,6 @@ def afmFontProperty(fontpath, font):
return FontEntry(fontpath, 0, name, style, variant, weight, stretch, size)


def _cleanup_fontproperties_init(init_method):
"""
A decorator to limit the call signature to a single positional argument
or alternatively only keyword arguments.

We still accept but deprecate all other call signatures.

When the deprecation expires we can switch the signature to::

__init__(self, pattern=None, /, *, family=None, style=None, ...)

plus a runtime check that pattern is not used alongside with the
keyword arguments. This results eventually in the two possible
call signatures::

FontProperties(pattern)
FontProperties(family=..., size=..., ...)

"""
@functools.wraps(init_method)
def wrapper(self, *args, **kwargs):
# multiple args with at least some positional ones
if len(args) > 1 or len(args) == 1 and kwargs:
# Note: Both cases were previously handled as individual properties.
# Therefore, we do not mention the case of font properties here.
_api.warn_deprecated(
"3.10",
message="Passing individual properties to FontProperties() "
"positionally was deprecated in Matplotlib %(since)s and "
"will be removed in %(removal)s. Please pass all properties "
"via keyword arguments."
)
# single non-string arg -> clearly a family not a pattern
if len(args) == 1 and not kwargs and not cbook.is_scalar_or_string(args[0]):
# Case font-family list passed as single argument
_api.warn_deprecated(
"3.10",
message="Passing family as positional argument to FontProperties() "
"was deprecated in Matplotlib %(since)s and will be removed "
"in %(removal)s. Please pass family names as keyword"
"argument."
)
# Note on single string arg:
# This has been interpreted as pattern so far. We are already raising if a
# non-pattern compatible family string was given. Therefore, we do not need
# to warn for this case.
return init_method(self, *args, **kwargs)

return wrapper


class FontProperties:
"""
A class for storing and manipulating font properties.
Expand Down Expand Up @@ -814,11 +762,17 @@ class FontProperties:
fontconfig.
"""

@_cleanup_fontproperties_init
def __init__(self, family=None, style=None, variant=None, weight=None,
def __init__(self, pattern=None, /, *,
family=None, style=None, variant=None, weight=None,
stretch=None, size=None,
fname=None, # if set, it's a hardcoded filename to use
math_fontfamily=None):
if pattern is not None:
if not (family is None and style is None and variant is None and
weight is None and stretch is None and size is None and
fname is None):
raise TypeError("Passing both a fontconfig pattern and individual "
"properties to FontProperties() is invalid")
self.set_family(family)
self.set_style(style)
self.set_variant(variant)
Expand All @@ -827,13 +781,10 @@ def __init__(self, family=None, style=None, variant=None, weight=None,
self.set_file(fname)
self.set_size(size)
self.set_math_fontfamily(math_fontfamily)
# Treat family as a fontconfig pattern if it is the only parameter
# provided. Even in that case, call the other setters first to set
# attributes not specified by the pattern to the rcParams defaults.
if (isinstance(family, str)
and style is None and variant is None and weight is None
and stretch is None and size is None and fname is None):
self.set_fontconfig_pattern(family)
# Even in the case a fontconfig pattern is provided, call the other setters
# first to set attributes not specified by the pattern to the rcParams defaults.
if pattern is not None:
self.set_fontconfig_pattern(pattern)

@classmethod
def _from_any(cls, arg):
Expand Down
7 changes: 5 additions & 2 deletions lib/matplotlib/font_manager.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ from dataclasses import dataclass
from numbers import Integral
import os
from pathlib import Path
from typing import Any, Final, Literal
from typing import Any, Final, Literal, overload

from matplotlib._afm import AFM
from matplotlib import ft2font
Expand Down Expand Up @@ -62,8 +62,11 @@ def ttfFontProperty(font: ft2font.FT2Font) -> FontEntry: ...
def afmFontProperty(fontpath: str, font: AFM) -> FontEntry: ...

class FontProperties:
@overload
def __init__(self, pattern: str | None, /) -> None: ...
@overload
def __init__(
self,
self, *,
family: str | Iterable[str] | None = ...,
style: Literal["normal", "italic", "oblique"] | None = ...,
variant: Literal["normal", "small-caps"] | None = ...,
Expand Down
18 changes: 3 additions & 15 deletions lib/matplotlib/tests/test_font_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,35 +564,23 @@ def test_fontproperties_init_deprecation():
which calls do and do not issue deprecation warnings. Behavior is still
tested via the existing regular tests.
"""
with pytest.warns(mpl.MatplotlibDeprecationWarning):
with pytest.raises(TypeError):
# multiple positional arguments
FontProperties("Times", "italic")

with pytest.warns(mpl.MatplotlibDeprecationWarning):
with pytest.raises(TypeError):
# Mixed positional and keyword arguments
FontProperties("Times", size=10)

with pytest.warns(mpl.MatplotlibDeprecationWarning):
with pytest.raises(TypeError):
# passing a family list positionally
FontProperties(["Times"])

# still accepted:
FontProperties(family="Times", style="italic")
FontProperties(family="Times")
FontProperties("Times") # works as pattern and family
FontProperties("serif-24:style=oblique:weight=bold") # pattern

# also still accepted:
# passing as pattern via family kwarg was not covered by the docs but
# historically worked. This is left unchanged for now.
# AFAICT, we cannot detect this: We can determine whether a string
# works as pattern, but that doesn't help, because there are strings
# that are both pattern and family. We would need to identify, whether
# a string is *not* a valid family.
# Since this case is not covered by docs, I've refrained from jumping
# extra hoops to detect this possible API misuse.
FontProperties(family="serif-24:style=oblique:weight=bold")


def test_normalize_weights():
assert _normalize_weight(300) == 300 # passthrough
Expand Down
24 changes: 0 additions & 24 deletions lib/matplotlib/tests/test_ft2font.py
Original file line number Diff line number Diff line change
Expand Up @@ -814,30 +814,6 @@ def test_ft2font_get_kerning(left, right, unscaled, unfitted, default):
assert font.get_kerning(font.get_char_index(ord(left)),
font.get_char_index(ord(right)),
ft2font.Kerning.DEFAULT) == default
with pytest.warns(mpl.MatplotlibDeprecationWarning,
match='Use Kerning.UNSCALED instead'):
k = ft2font.KERNING_UNSCALED
with pytest.warns(mpl.MatplotlibDeprecationWarning,
match='Use Kerning enum values instead'):
assert font.get_kerning(font.get_char_index(ord(left)),
font.get_char_index(ord(right)),
int(k)) == unscaled
with pytest.warns(mpl.MatplotlibDeprecationWarning,
match='Use Kerning.UNFITTED instead'):
k = ft2font.KERNING_UNFITTED
with pytest.warns(mpl.MatplotlibDeprecationWarning,
match='Use Kerning enum values instead'):
assert font.get_kerning(font.get_char_index(ord(left)),
font.get_char_index(ord(right)),
int(k)) == unfitted
with pytest.warns(mpl.MatplotlibDeprecationWarning,
match='Use Kerning.DEFAULT instead'):
k = ft2font.KERNING_DEFAULT
with pytest.warns(mpl.MatplotlibDeprecationWarning,
match='Use Kerning enum values instead'):
assert font.get_kerning(font.get_char_index(ord(left)),
font.get_char_index(ord(right)),
int(k)) == default


def test_ft2font_set_text():
Expand Down
31 changes: 1 addition & 30 deletions src/_backend_agg_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,37 +58,8 @@ PyRendererAgg_draw_path(RendererAgg *self,
static void
PyRendererAgg_draw_text_image(RendererAgg *self,
py::array_t<agg::int8u, py::array::c_style | py::array::forcecast> image_obj,
std::variant<int, double> vx,
std::variant<int, double> vy,
double angle,
GCAgg &gc)
int x, int y, double angle, GCAgg &gc)
{
int x, y;

if (auto value = std::get_if<double>(&vx)) {
auto api = py::module_::import("matplotlib._api");
auto warn = api.attr("warn_deprecated");
warn("since"_a="3.10", "name"_a="x", "obj_type"_a="parameter as float",
"alternative"_a="int(x)");
x = static_cast<int>(*value);
} else if (auto value = std::get_if<int>(&vx)) {
x = *value;
} else {
throw std::runtime_error("Should not happen");
}

if (auto value = std::get_if<double>(&vy)) {
auto api = py::module_::import("matplotlib._api");
auto warn = api.attr("warn_deprecated");
warn("since"_a="3.10", "name"_a="y", "obj_type"_a="parameter as float",
"alternative"_a="int(y)");
y = static_cast<int>(*value);
} else if (auto value = std::get_if<int>(&vy)) {
y = *value;
} else {
throw std::runtime_error("Should not happen");
}

// TODO: This really shouldn't be mutable, but Agg's renderer buffers aren't const.
auto image = image_obj.mutable_unchecked<2>();

Expand Down
Loading
Loading