Skip to content

Commit 69ab01d

Browse files
committed
Merge commit branch 'text-overhaul' into text-overhaul-figures-per-commit
2 parents 31b2b42 + b6be596 commit 69ab01d

File tree

11 files changed

+220
-102
lines changed

11 files changed

+220
-102
lines changed

lib/matplotlib/_mathtext.py

Lines changed: 35 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -482,60 +482,40 @@ def _get_glyph(self, fontname: str, font_class: str,
482482
else:
483483
return self._stix_fallback._get_glyph(fontname, font_class, sym)
484484

485-
# The Bakoma fonts contain many pre-sized alternatives for the
486-
# delimiters. The AutoSizedChar class will use these alternatives
487-
# and select the best (closest sized) glyph.
485+
# The Bakoma fonts contain many pre-sized alternatives for the delimiters. The
486+
# Auto(Height|Width)Char classes will use these alternatives and select the best
487+
# (closest sized) glyph.
488+
_latex_sizes = ('big', 'Big', 'bigg', 'Bigg')
488489
_size_alternatives = {
489-
'(': [('rm', '('), ('ex', '\xa1'), ('ex', '\xb3'),
490-
('ex', '\xb5'), ('ex', '\xc3')],
491-
')': [('rm', ')'), ('ex', '\xa2'), ('ex', '\xb4'),
492-
('ex', '\xb6'), ('ex', '\x21')],
493-
'{': [('cal', '{'), ('ex', '\xa9'), ('ex', '\x6e'),
494-
('ex', '\xbd'), ('ex', '\x28')],
495-
'}': [('cal', '}'), ('ex', '\xaa'), ('ex', '\x6f'),
496-
('ex', '\xbe'), ('ex', '\x29')],
497-
# The fourth size of '[' is mysteriously missing from the BaKoMa
498-
# font, so I've omitted it for both '[' and ']'
499-
'[': [('rm', '['), ('ex', '\xa3'), ('ex', '\x68'),
500-
('ex', '\x22')],
501-
']': [('rm', ']'), ('ex', '\xa4'), ('ex', '\x69'),
502-
('ex', '\x23')],
503-
r'\lfloor': [('ex', '\xa5'), ('ex', '\x6a'),
504-
('ex', '\xb9'), ('ex', '\x24')],
505-
r'\rfloor': [('ex', '\xa6'), ('ex', '\x6b'),
506-
('ex', '\xba'), ('ex', '\x25')],
507-
r'\lceil': [('ex', '\xa7'), ('ex', '\x6c'),
508-
('ex', '\xbb'), ('ex', '\x26')],
509-
r'\rceil': [('ex', '\xa8'), ('ex', '\x6d'),
510-
('ex', '\xbc'), ('ex', '\x27')],
511-
r'\langle': [('ex', '\xad'), ('ex', '\x44'),
512-
('ex', '\xbf'), ('ex', '\x2a')],
513-
r'\rangle': [('ex', '\xae'), ('ex', '\x45'),
514-
('ex', '\xc0'), ('ex', '\x2b')],
515-
r'\__sqrt__': [('ex', '\x70'), ('ex', '\x71'),
516-
('ex', '\x72'), ('ex', '\x73')],
517-
r'\backslash': [('ex', '\xb2'), ('ex', '\x2f'),
518-
('ex', '\xc2'), ('ex', '\x2d')],
519-
r'/': [('rm', '/'), ('ex', '\xb1'), ('ex', '\x2e'),
520-
('ex', '\xcb'), ('ex', '\x2c')],
521-
r'\widehat': [('rm', '\x5e'), ('ex', '\x62'), ('ex', '\x63'),
522-
('ex', '\x64')],
523-
r'\widetilde': [('rm', '\x7e'), ('ex', '\x65'), ('ex', '\x66'),
524-
('ex', '\x67')],
525-
r'<': [('cal', 'h'), ('ex', 'D')],
526-
r'>': [('cal', 'i'), ('ex', 'E')]
527-
}
490+
'(': [('rm', '('), *[('ex', fr'\__parenleft{s}__') for s in _latex_sizes]],
491+
')': [('rm', ')'), *[('ex', fr'\__parenright{s}__') for s in _latex_sizes]],
492+
'{': [('ex', fr'\__braceleft{s}__') for s in _latex_sizes],
493+
'}': [('ex', fr'\__braceright{s}__') for s in _latex_sizes],
494+
'[': [('rm', '['), *[('ex', fr'\__bracketleft{s}__') for s in _latex_sizes]],
495+
']': [('rm', ']'), *[('ex', fr'\__bracketright{s}__') for s in _latex_sizes]],
496+
'<': [('cal', r'\__angbracketleft__'),
497+
*[('ex', fr'\__angbracketleft{s}__') for s in _latex_sizes]],
498+
'>': [('cal', r'\__angbracketright__'),
499+
*[('ex', fr'\__angbracketright{s}__') for s in _latex_sizes]],
500+
r'\lfloor': [('ex', fr'\__floorleft{s}__') for s in _latex_sizes],
501+
r'\rfloor': [('ex', fr'\__floorright{s}__') for s in _latex_sizes],
502+
r'\lceil': [('ex', fr'\__ceilingleft{s}__') for s in _latex_sizes],
503+
r'\rceil': [('ex', fr'\__ceilingright{s}__') for s in _latex_sizes],
504+
r'\__sqrt__': [('ex', fr'\__radical{s}__') for s in _latex_sizes],
505+
r'\backslash': [('ex', fr'\__backslash{s}__') for s in _latex_sizes],
506+
r'/': [('rm', '/'), *[('ex', fr'\__slash{s}__') for s in _latex_sizes]],
507+
r'\widehat': [('rm', '\x5e'), ('ex', r'\__hatwide__'), ('ex', r'\__hatwider__'),
508+
('ex', r'\__hatwidest__')],
509+
r'\widetilde': [('rm', '\x7e'), ('ex', r'\__tildewide__'),
510+
('ex', r'\__tildewider__'), ('ex', r'\__tildewidest__')],
511+
}
528512

529-
for alias, target in [(r'\leftparen', '('),
530-
(r'\rightparen', ')'),
531-
(r'\leftbrace', '{'),
532-
(r'\rightbrace', '}'),
533-
(r'\leftbracket', '['),
534-
(r'\rightbracket', ']'),
535-
(r'\{', '{'),
536-
(r'\}', '}'),
537-
(r'\[', '['),
538-
(r'\]', ']')]:
513+
for alias, target in [(r'\leftparen', '('), (r'\rightparen', ')'),
514+
(r'\leftbrace', '{'), (r'\rightbrace', '}'),
515+
(r'\leftbracket', '['), (r'\rightbracket', ']'),
516+
(r'\langle', '<'), (r'\rangle', '>'),
517+
(r'\{', '{'), (r'\}', '}'),
518+
(r'\[', '['), (r'\]', ']')]:
539519
_size_alternatives[alias] = _size_alternatives[target]
540520

541521
def get_sized_alternatives_for_symbol(self, fontname: str,
@@ -1531,7 +1511,7 @@ class AutoHeightChar(Hlist):
15311511
"""
15321512

15331513
def __init__(self, c: str, height: float, depth: float, state: ParserState,
1534-
always: bool = False, factor: float | None = None):
1514+
factor: float | None = None):
15351515
alternatives = state.fontset.get_sized_alternatives_for_symbol(state.font, c)
15361516

15371517
x_height = state.fontset.get_xheight(state.font, state.fontsize, state.dpi)
@@ -1568,7 +1548,7 @@ class AutoWidthChar(Hlist):
15681548
always just return a scaled version of the glyph.
15691549
"""
15701550

1571-
def __init__(self, c: str, width: float, state: ParserState, always: bool = False,
1551+
def __init__(self, c: str, width: float, state: ParserState,
15721552
char_class: type[Char] = Char):
15731553
alternatives = state.fontset.get_sized_alternatives_for_symbol(state.font, c)
15741554

@@ -2706,7 +2686,7 @@ def sqrt(self, toks: ParseResults) -> T.Any:
27062686
# the height so it doesn't seem cramped
27072687
height = body.height - body.shift_amount + 5 * thickness
27082688
depth = body.depth + body.shift_amount
2709-
check = AutoHeightChar(r'\__sqrt__', height, depth, state, always=True)
2689+
check = AutoHeightChar(r'\__sqrt__', height, depth, state)
27102690
height = check.height - check.shift_amount
27112691
depth = check.depth + check.shift_amount
27122692

lib/matplotlib/_mathtext_data.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,75 @@
3636
'{' : ('cmex10', 0xa9),
3737
'}' : ('cmex10', 0xaa),
3838

39+
'\\__angbracketleft__' : ('cmsy10', 0x68),
40+
'\\__angbracketright__' : ('cmsy10', 0x69),
41+
'\\__angbracketleftbig__' : ('cmex10', 0xad),
42+
'\\__angbracketleftBig__' : ('cmex10', 0x44),
43+
'\\__angbracketleftbigg__' : ('cmex10', 0xbf),
44+
'\\__angbracketleftBigg__' : ('cmex10', 0x2a),
45+
'\\__angbracketrightbig__' : ('cmex10', 0xae),
46+
'\\__angbracketrightBig__' : ('cmex10', 0x45),
47+
'\\__angbracketrightbigg__' : ('cmex10', 0xc0),
48+
'\\__angbracketrightBigg__' : ('cmex10', 0x2b),
49+
'\\__backslashbig__' : ('cmex10', 0xb2),
50+
'\\__backslashBig__' : ('cmex10', 0x2f),
51+
'\\__backslashbigg__' : ('cmex10', 0xc2),
52+
'\\__backslashBigg__' : ('cmex10', 0x2d),
53+
'\\__braceleftbig__' : ('cmex10', 0xa9),
54+
'\\__braceleftBig__' : ('cmex10', 0x6e),
55+
'\\__braceleftbigg__' : ('cmex10', 0xbd),
56+
'\\__braceleftBigg__' : ('cmex10', 0x28),
57+
'\\__bracerightbig__' : ('cmex10', 0xaa),
58+
'\\__bracerightBig__' : ('cmex10', 0x6f),
59+
'\\__bracerightbigg__' : ('cmex10', 0xbe),
60+
'\\__bracerightBigg__' : ('cmex10', 0x29),
61+
'\\__bracketleftbig__' : ('cmex10', 0xa3),
62+
'\\__bracketleftBig__' : ('cmex10', 0x68),
63+
'\\__bracketleftbigg__' : ('cmex10', 0x2219),
64+
'\\__bracketleftBigg__' : ('cmex10', 0x22),
65+
'\\__bracketrightbig__' : ('cmex10', 0xa4),
66+
'\\__bracketrightBig__' : ('cmex10', 0x69),
67+
'\\__bracketrightbigg__' : ('cmex10', 0xb8),
68+
'\\__bracketrightBigg__' : ('cmex10', 0x23),
69+
'\\__ceilingleftbig__' : ('cmex10', 0xa7),
70+
'\\__ceilingleftBig__' : ('cmex10', 0x6c),
71+
'\\__ceilingleftbigg__' : ('cmex10', 0xbb),
72+
'\\__ceilingleftBigg__' : ('cmex10', 0x26),
73+
'\\__ceilingrightbig__' : ('cmex10', 0xa8),
74+
'\\__ceilingrightBig__' : ('cmex10', 0x6d),
75+
'\\__ceilingrightbigg__' : ('cmex10', 0xbc),
76+
'\\__ceilingrightBigg__' : ('cmex10', 0x27),
77+
'\\__floorleftbig__' : ('cmex10', 0xa5),
78+
'\\__floorleftBig__' : ('cmex10', 0x6a),
79+
'\\__floorleftbigg__' : ('cmex10', 0xb9),
80+
'\\__floorleftBigg__' : ('cmex10', 0x24),
81+
'\\__floorrightbig__' : ('cmex10', 0xa6),
82+
'\\__floorrightBig__' : ('cmex10', 0x6b),
83+
'\\__floorrightbigg__' : ('cmex10', 0xba),
84+
'\\__floorrightBigg__' : ('cmex10', 0x25),
85+
'\\__hatwide__' : ('cmex10', 0x62),
86+
'\\__hatwider__' : ('cmex10', 0x63),
87+
'\\__hatwidest__' : ('cmex10', 0x64),
88+
'\\__parenleftbig__' : ('cmex10', 0xa1),
89+
'\\__parenleftBig__' : ('cmex10', 0xb3),
90+
'\\__parenleftbigg__' : ('cmex10', 0xb5),
91+
'\\__parenleftBigg__' : ('cmex10', 0xc3),
92+
'\\__parenrightbig__' : ('cmex10', 0xa2),
93+
'\\__parenrightBig__' : ('cmex10', 0xb4),
94+
'\\__parenrightbigg__' : ('cmex10', 0xb6),
95+
'\\__parenrightBigg__' : ('cmex10', 0x21),
96+
'\\__radicalbig__' : ('cmex10', 0x70),
97+
'\\__radicalBig__' : ('cmex10', 0x71),
98+
'\\__radicalbigg__' : ('cmex10', 0x72),
99+
'\\__radicalBigg__' : ('cmex10', 0x73),
100+
'\\__slashbig__' : ('cmex10', 0xb1),
101+
'\\__slashBig__' : ('cmex10', 0x2e),
102+
'\\__slashbigg__' : ('cmex10', 0xc1),
103+
'\\__slashBigg__' : ('cmex10', 0x2c),
104+
'\\__tildewide__' : ('cmex10', 0x65),
105+
'\\__tildewider__' : ('cmex10', 0x66),
106+
'\\__tildewidest__' : ('cmex10', 0x67),
107+
39108
',' : ('cmmi10', 0x3b),
40109
'.' : ('cmmi10', 0x3a),
41110
'/' : ('cmmi10', 0x3d),
@@ -1112,6 +1181,8 @@
11121181
'|' : 0x2016,
11131182
'}' : 0x7d,
11141183
}
1184+
tex2uni['__angbracketleft__'] = tex2uni['langle']
1185+
tex2uni['__angbracketright__'] = tex2uni['rangle']
11151186

11161187
# Each element is a 4-tuple of the form:
11171188
# src_start, src_end, dst_font, dst_start

lib/matplotlib/font_manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1611,10 +1611,10 @@ def get_font(font_filepaths, hinting_factor=None):
16111611
16121612
Parameters
16131613
----------
1614-
font_filepaths : Iterable[str, Path, bytes], str, Path, bytes
1614+
font_filepaths : Iterable[str, bytes, os.PathLike], str, bytes, os.PathLike
16151615
Relative or absolute paths to the font files to be used.
16161616
1617-
If a single string, bytes, or `pathlib.Path`, then it will be treated
1617+
If a single string, bytes, or `os.PathLike`, then it will be treated
16181618
as a list with that entry only.
16191619
16201620
If more than one filepath is passed, then the returned FT2Font object
@@ -1626,7 +1626,7 @@ def get_font(font_filepaths, hinting_factor=None):
16261626
`.ft2font.FT2Font`
16271627
16281628
"""
1629-
if isinstance(font_filepaths, (str, Path, bytes)):
1629+
if isinstance(font_filepaths, (str, bytes, os.PathLike)):
16301630
paths = (_cached_realpath(font_filepaths),)
16311631
else:
16321632
paths = tuple(_cached_realpath(fname) for fname in font_filepaths)

lib/matplotlib/font_manager.pyi

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def list_fonts(directory: str, extensions: Iterable[str]) -> list[str]: ...
2424
def win32FontDirectory() -> str: ...
2525
def _get_fontconfig_fonts() -> list[Path]: ...
2626
def findSystemFonts(
27-
fontpaths: Iterable[str | os.PathLike | Path] | None = ..., fontext: str = ...
27+
fontpaths: Iterable[str | os.PathLike] | None = ..., fontext: str = ...
2828
) -> list[str]: ...
2929
@dataclass
3030
class FontEntry:
@@ -50,7 +50,7 @@ class FontProperties:
5050
weight: int | str | None = ...,
5151
stretch: int | str | None = ...,
5252
size: float | str | None = ...,
53-
fname: str | os.PathLike | Path | None = ...,
53+
fname: str | os.PathLike | None = ...,
5454
math_fontfamily: str | None = ...,
5555
) -> None: ...
5656
def __hash__(self) -> int: ...
@@ -72,7 +72,7 @@ class FontProperties:
7272
def set_weight(self, weight: int | str | None) -> None: ...
7373
def set_stretch(self, stretch: int | str | None) -> None: ...
7474
def set_size(self, size: float | str | None) -> None: ...
75-
def set_file(self, file: str | os.PathLike | Path | None) -> None: ...
75+
def set_file(self, file: str | os.PathLike | None) -> None: ...
7676
def set_fontconfig_pattern(self, pattern: str) -> None: ...
7777
def get_math_fontfamily(self) -> str: ...
7878
def set_math_fontfamily(self, fontfamily: str | None) -> None: ...
@@ -83,8 +83,8 @@ class FontProperties:
8383
set_slant = set_style
8484
get_size_in_points = get_size
8585

86-
def json_dump(data: FontManager, filename: str | Path | os.PathLike) -> None: ...
87-
def json_load(filename: str | Path | os.PathLike) -> FontManager: ...
86+
def json_dump(data: FontManager, filename: str | os.PathLike) -> None: ...
87+
def json_load(filename: str | os.PathLike) -> FontManager: ...
8888

8989
class FontManager:
9090
__version__: str
@@ -93,7 +93,7 @@ class FontManager:
9393
afmlist: list[FontEntry]
9494
ttflist: list[FontEntry]
9595
def __init__(self, size: float | None = ..., weight: str = ...) -> None: ...
96-
def addfont(self, path: str | Path | os.PathLike) -> None: ...
96+
def addfont(self, path: str | os.PathLike) -> None: ...
9797
@property
9898
def defaultFont(self) -> dict[str, str]: ...
9999
def get_default_weight(self) -> str: ...
@@ -120,7 +120,7 @@ class FontManager:
120120

121121
def is_opentype_cff_font(filename: str) -> bool: ...
122122
def get_font(
123-
font_filepaths: Iterable[str | Path | bytes] | str | Path | bytes,
123+
font_filepaths: Iterable[str | bytes | os.PathLike] | str | bytes | os.PathLike,
124124
hinting_factor: int | None = ...,
125125
) -> ft2font.FT2Font: ...
126126

lib/matplotlib/ft2font.pyi

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from enum import Enum, Flag
2+
from os import PathLike
23
import sys
34
from typing import BinaryIO, Literal, NewType, TypeAlias, TypedDict, cast, final, overload
45
from typing_extensions import Buffer # < Py 3.12
@@ -194,7 +195,7 @@ class _SfntPcltDict(TypedDict):
194195
class FT2Font(Buffer):
195196
def __init__(
196197
self,
197-
filename: str | BinaryIO,
198+
filename: str | bytes | PathLike | BinaryIO,
198199
hinting_factor: int = ...,
199200
*,
200201
_fallback_list: list[FT2Font] | None = ...,
@@ -261,7 +262,7 @@ class FT2Font(Buffer):
261262
@property
262263
def family_name(self) -> str: ...
263264
@property
264-
def fname(self) -> str: ...
265+
def fname(self) -> str | bytes: ...
265266
@property
266267
def height(self) -> int: ...
267268
@property

lib/matplotlib/tests/test_font_manager.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from io import BytesIO, StringIO
1+
from io import BytesIO
22
import gc
33
import multiprocessing
44
import os
@@ -137,6 +137,32 @@ def test_find_noto():
137137
fig.savefig(BytesIO(), format=fmt)
138138

139139

140+
def test_find_valid():
141+
class PathLikeClass:
142+
def __init__(self, filename):
143+
self.filename = filename
144+
145+
def __fspath__(self):
146+
return self.filename
147+
148+
file_str = findfont('DejaVu Sans')
149+
file_bytes = os.fsencode(file_str)
150+
151+
font = get_font(file_str)
152+
assert font.fname == file_str
153+
font = get_font(file_bytes)
154+
assert font.fname == file_bytes
155+
font = get_font(PathLikeClass(file_str))
156+
assert font.fname == file_str
157+
font = get_font(PathLikeClass(file_bytes))
158+
assert font.fname == file_bytes
159+
160+
# Note, fallbacks are not currently accessible.
161+
font = get_font([file_str, file_bytes,
162+
PathLikeClass(file_str), PathLikeClass(file_bytes)])
163+
assert font.fname == file_str
164+
165+
140166
def test_find_invalid(tmp_path):
141167

142168
with pytest.raises(FileNotFoundError):
@@ -148,11 +174,6 @@ def test_find_invalid(tmp_path):
148174
with pytest.raises(FileNotFoundError):
149175
get_font(bytes(tmp_path / 'non-existent-font-name.ttf'))
150176

151-
# Not really public, but get_font doesn't expose non-filename constructor.
152-
from matplotlib.ft2font import FT2Font
153-
with pytest.raises(TypeError, match='font file or a binary-mode file'):
154-
FT2Font(StringIO()) # type: ignore[arg-type]
155-
156177

157178
@pytest.mark.skipif(sys.platform != 'linux' or not has_fclist,
158179
reason='only Linux with fontconfig installed')

lib/matplotlib/tests/test_ft2font.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import itertools
22
import io
3+
import os
34
from pathlib import Path
45
from typing import cast
56

@@ -134,6 +135,27 @@ def test_ft2font_stix_bold_attrs():
134135
assert font.bbox == (4, -355, 1185, 2095)
135136

136137

138+
def test_ft2font_valid_args():
139+
class PathLikeClass:
140+
def __init__(self, filename):
141+
self.filename = filename
142+
143+
def __fspath__(self):
144+
return self.filename
145+
146+
file_str = fm.findfont('DejaVu Sans')
147+
file_bytes = os.fsencode(file_str)
148+
149+
font = ft2font.FT2Font(file_str)
150+
assert font.fname == file_str
151+
font = ft2font.FT2Font(file_bytes)
152+
assert font.fname == file_bytes
153+
font = ft2font.FT2Font(PathLikeClass(file_str))
154+
assert font.fname == file_str
155+
font = ft2font.FT2Font(PathLikeClass(file_bytes))
156+
assert font.fname == file_bytes
157+
158+
137159
def test_ft2font_invalid_args(tmp_path):
138160
# filename argument.
139161
with pytest.raises(TypeError, match='to a font file or a binary-mode file object'):

0 commit comments

Comments
 (0)