Skip to content

Commit b494ace

Browse files
timhoffmrcomer
andcommitted
FIX: set_font() should not do full font reset for str arguments
Closes matplotlib#27608 A str is interpreted as a fontconfig pattern and should only update the specified parts. This is solution 2 from matplotlib#27608 (comment) This is a breaking change, but there is no smooth transition path. I'd label this a bug fix and by that justify the immediate change. Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com>
1 parent 03334a9 commit b494ace

4 files changed

Lines changed: 160 additions & 1 deletion

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
``Text.set_font`` now performs a partial update for string arguments
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
5+
`.Text.set_font` (and its alias ``font=`` in keyword-argument form) previously
6+
behaved identically to `.Text.set_fontproperties`: passing a string caused
7+
**all** font properties to be replaced, resetting size, weight, style, etc. to
8+
their defaults. This was surprising given that all other ``set_font*`` methods
9+
(`~.Text.set_fontfamily`, `~.Text.set_fontsize`, `~.Text.set_fontweight`, ...)
10+
update only the property they describe.
11+
12+
Starting with this release ``set_font`` performs a *partial* update when given
13+
a string:
14+
15+
* The string is interpreted as a fontconfig pattern (same syntax as before).
16+
* Only the properties explicitly named in the pattern are changed.
17+
* All other font properties (size, weight, style, ...) are preserved.
18+
19+
.. code-block:: python
20+
21+
import matplotlib.pyplot as plt
22+
23+
fig, ax = plt.subplots()
24+
t = ax.text(0.5, 0.5, "Hello")
25+
26+
t.set_fontsize(20)
27+
t.set_fontweight("bold")
28+
29+
# Old behaviour: size and weight would be reset to defaults.
30+
# New behaviour: only the family is updated; size=20 and bold weight remain.
31+
t.set_font("DejaVu Serif")
32+
33+
# A rich fontconfig pattern updates exactly those properties:
34+
t.set_font("DejaVu Serif:italic:size=14")
35+
36+
For a complete replacement of all font properties (the previous behaviour)
37+
use `.Text.set_fontproperties` ::
38+
39+
t.set_fontproperties("DejaVu Serif") # resets all other properties

lib/matplotlib/tests/test_text.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,86 @@ def test_fontproperties_kwarg_precedence():
776776
assert text2.get_size() == 40.0
777777

778778

779+
def test_set_font_str_is_partial_update():
780+
"""set_font(str) must update only the family, preserving size/weight/style."""
781+
fig, ax = plt.subplots()
782+
t = ax.text(0.5, 0.5, 'hello')
783+
t.set_fontsize(20)
784+
t.set_fontweight('bold')
785+
t.set_fontstyle('italic')
786+
787+
t.set_font('DejaVu Serif')
788+
789+
assert t.get_fontfamily() == ['DejaVu Serif']
790+
assert t.get_fontsize() == 20, "set_font(str) must not reset the font size"
791+
assert t.get_fontweight() == 'bold', "set_font(str) must not reset the font weight"
792+
assert t.get_fontstyle() == 'italic', "set_font(str) must not reset the font style"
793+
794+
795+
def test_set_font_fontconfig_pattern_is_partial_update():
796+
"""set_font(fontconfig_pattern) updates only the properties named in the pattern."""
797+
fig, ax = plt.subplots()
798+
t = ax.text(0.5, 0.5, 'hello')
799+
t.set_fontsize(20)
800+
t.set_fontweight('bold')
801+
802+
# Pattern specifies family and size but NOT weight.
803+
t.set_font('DejaVu Serif:size=14')
804+
805+
assert t.get_fontfamily() == ['DejaVu Serif']
806+
assert t.get_fontsize() == 14, "set_font(pattern) must update size from pattern"
807+
assert t.get_fontweight() == 'bold', (
808+
"set_font(pattern) must not reset properties absent from the pattern"
809+
)
810+
811+
812+
def test_set_fontproperties_str_resets_all():
813+
"""set_fontproperties(str) must replace ALL font properties (reset to defaults)."""
814+
fig, ax = plt.subplots()
815+
t = ax.text(0.5, 0.5, 'hello')
816+
t.set_fontsize(20)
817+
t.set_fontweight('bold')
818+
819+
default_size = FontProperties().get_size_in_points()
820+
default_weight = FontProperties().get_weight()
821+
822+
t.set_fontproperties('DejaVu Serif')
823+
824+
assert t.get_fontfamily() == ['DejaVu Serif']
825+
assert t.get_fontsize() == default_size, (
826+
"set_fontproperties(str) must reset size to the FontProperties default"
827+
)
828+
assert t.get_fontweight() == default_weight, (
829+
"set_fontproperties(str) must reset weight to the FontProperties default"
830+
)
831+
832+
833+
def test_set_font_with_fontproperties_object_replaces_all():
834+
"""set_font(FontProperties()) falls through to set_fontproperties (full replace)."""
835+
fig, ax = plt.subplots()
836+
t = ax.text(0.5, 0.5, 'hello')
837+
t.set_fontsize(20)
838+
t.set_fontweight('bold')
839+
840+
fp = FontProperties(family='DejaVu Serif', size=12)
841+
t.set_font(fp)
842+
843+
assert t.get_fontfamily() == ['DejaVu Serif']
844+
assert t.get_fontsize() == 12
845+
# weight was not set in fp → reset to its default
846+
assert t.get_fontweight() == FontProperties().get_weight()
847+
848+
849+
def test_set_font_via_kwarg():
850+
"""The ``font`` keyword argument must reach set_font (partial-update path)."""
851+
fig, ax = plt.subplots()
852+
t = ax.text(0.5, 0.5, 'hello', fontsize=20, font='DejaVu Serif:bold')
853+
854+
assert t.get_fontfamily() == ['DejaVu Serif']
855+
assert t.get_fontweight() == 'bold'
856+
assert t.get_fontsize() == 20, "font= kwarg must not reset size"
857+
858+
779859
def test_transform_rotates_text():
780860
ax = plt.gca()
781861
transform = mtransforms.Affine2D().rotate_deg(30)

lib/matplotlib/text.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ def _text_metrics(text, fontprop, ismath, dpi):
134134
@_docstring.interpd
135135
@_api.define_aliases({
136136
"color": ["c"],
137-
"fontproperties": ["font", "font_properties"],
137+
"fontproperties": ["font_properties"],
138138
"fontfamily": ["family"],
139139
"fontname": ["name"],
140140
"fontsize": ["size"],
@@ -1479,6 +1479,9 @@ def set_fontproperties(self, fp):
14791479
"""
14801480
Set the font properties that control the text.
14811481
1482+
Unlike `set_font`, this method *replaces* all font properties,
1483+
resetting any unspecified attributes to their defaults.
1484+
14821485
Parameters
14831486
----------
14841487
fp : `.font_manager.FontProperties` or `str` or `pathlib.Path`
@@ -1489,6 +1492,42 @@ def set_fontproperties(self, fp):
14891492
self._fontproperties = FontProperties._from_any(fp).copy()
14901493
self.stale = True
14911494

1495+
def set_font(self, fp):
1496+
"""
1497+
Set the font.
1498+
1499+
.. versionchanged:: 3.12
1500+
1501+
If font is a str, this now only updates aspects of the font that are
1502+
specified in the pattern. Previously this was equivalent to
1503+
`.set_fontproperties`, which resets all non-specified aspectes to
1504+
their default.
1505+
1506+
If you want to have the full reset, please use `.set_fontproperties`
1507+
instead.
1508+
1509+
Parameters
1510+
----------
1511+
fp : `.font_manager.FontProperties` or `str` or `pathlib.Path` or dict
1512+
1513+
- If a `str`, it is parsed as a fontconfig pattern (see
1514+
`.FontProperties.set_fontconfig_pattern`). Only the properties
1515+
explicitly stated in the pattern are changed; everything else is
1516+
preserved.
1517+
- If a `.FontProperties`, `pathlib.Path`, or `dict`, all font
1518+
properties are replaced (same as `set_fontproperties`).
1519+
1520+
See Also
1521+
--------
1522+
set_fontproperties : Replace *all* font properties at once.
1523+
set_fontfamily : Change only the font family.
1524+
"""
1525+
if isinstance(fp, str):
1526+
self._fontproperties.set_fontconfig_pattern(fp)
1527+
self.stale = True
1528+
else:
1529+
self.set_fontproperties(fp)
1530+
14921531
@_docstring.kwarg_doc("bool, default: :rc:`text.usetex`")
14931532
def set_usetex(self, usetex):
14941533
"""

lib/matplotlib/text.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ class Text(Artist):
102102
) -> None: ...
103103
def set_text(self, s: Any) -> None: ...
104104
def set_fontproperties(self, fp: FontProperties | str | Path | None) -> None: ...
105+
def set_font(self, fp: FontProperties | str | Path | None) -> None: ...
105106
def set_usetex(self, usetex: bool | None) -> None: ...
106107
def get_usetex(self) -> bool: ...
107108
def set_parse_math(self, parse_math: bool) -> None: ...

0 commit comments

Comments
 (0)