Skip to content

Commit 46c6239

Browse files
committed
Add language parameter to Text objects
1 parent a7c08c8 commit 46c6239

File tree

16 files changed

+138
-17
lines changed

16 files changed

+138
-17
lines changed

lib/matplotlib/_text_helpers.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def warn_on_missing_glyph(codepoint, fontnames):
4343
f"Matplotlib currently does not support {block} natively.")
4444

4545

46-
def layout(string, font, *, kern_mode=Kerning.DEFAULT):
46+
def layout(string, font, *, language=None, kern_mode=Kerning.DEFAULT):
4747
"""
4848
Render *string* with *font*.
4949
@@ -56,6 +56,9 @@ def layout(string, font, *, kern_mode=Kerning.DEFAULT):
5656
The string to be rendered.
5757
font : FT2Font
5858
The font.
59+
language : str or list of tuples of (str, int, int), optional
60+
The language of the text in a format accepted by libraqm, namely `a BCP47
61+
language code <https://www.w3.org/International/articles/language-tags/>`_.
5962
kern_mode : Kerning
6063
A FreeType kerning mode.
6164
@@ -65,7 +68,7 @@ def layout(string, font, *, kern_mode=Kerning.DEFAULT):
6568
"""
6669
x = 0
6770
prev_glyph_idx = None
68-
char_to_font = font._get_fontmap(string)
71+
char_to_font = font._get_fontmap(string) # TODO: Pass in language.
6972
base_font = font
7073
for char in string:
7174
# This has done the fallback logic

lib/matplotlib/backends/backend_agg.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,8 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
189189
font = self._prepare_font(prop)
190190
# We pass '0' for angle here, since it will be rotated (in raster
191191
# space) in the following call to draw_text_image).
192-
font.set_text(s, 0, flags=get_hinting_flag())
192+
font.set_text(s, 0, flags=get_hinting_flag(),
193+
language=mtext.get_language() if mtext is not None else None)
193194
font.draw_glyphs_to_bitmap(
194195
antialiased=gc.get_antialiased())
195196
d = font.get_descent() / 64.0

lib/matplotlib/backends/backend_pdf.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2338,6 +2338,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
23382338
return self.draw_mathtext(gc, x, y, s, prop, angle)
23392339

23402340
fontsize = prop.get_size_in_points()
2341+
language = mtext.get_language() if mtext is not None else None
23412342

23422343
if mpl.rcParams['pdf.use14corefonts']:
23432344
font = self._get_font_afm(prop)
@@ -2348,7 +2349,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
23482349
fonttype = mpl.rcParams['pdf.fonttype']
23492350

23502351
if gc.get_url() is not None:
2351-
font.set_text(s)
2352+
font.set_text(s, language=language)
23522353
width, height = font.get_width_height()
23532354
self.file._annotations[-1][1].append(_get_link_annotation(
23542355
gc, x, y, width / 64, height / 64, angle))
@@ -2382,7 +2383,8 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
23822383
multibyte_glyphs = []
23832384
prev_was_multibyte = True
23842385
prev_font = font
2385-
for item in _text_helpers.layout(s, font, kern_mode=Kerning.UNFITTED):
2386+
for item in _text_helpers.layout(s, font, language=language,
2387+
kern_mode=Kerning.UNFITTED):
23862388
if _font_supports_glyph(fonttype, ord(item.char)):
23872389
if prev_was_multibyte or item.ft_object != prev_font:
23882390
singlebyte_chunks.append((item.ft_object, item.x, []))

lib/matplotlib/backends/backend_ps.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -795,9 +795,10 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
795795
thisx += width * scale
796796

797797
else:
798+
language = mtext.get_language() if mtext is not None else None
798799
font = self._get_font_ttf(prop)
799800
self._character_tracker.track(font, s)
800-
for item in _text_helpers.layout(s, font):
801+
for item in _text_helpers.layout(s, font, language=language):
801802
ps_name = (item.ft_object.postscript_name
802803
.encode("ascii", "replace").decode("ascii"))
803804
glyph_name = item.ft_object.get_glyph_name(item.glyph_idx)

lib/matplotlib/ft2font.pyi

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,12 @@ class FT2Font(Buffer):
236236
def set_charmap(self, i: int) -> None: ...
237237
def set_size(self, ptsize: float, dpi: float) -> None: ...
238238
def set_text(
239-
self, string: str, angle: float = ..., flags: LoadFlags = ...
239+
self,
240+
string: str,
241+
angle: float = ...,
242+
flags: LoadFlags = ...,
243+
*,
244+
language: str | list[tuple[str, int, int]] | None = ...,
240245
) -> NDArray[np.float64]: ...
241246
@property
242247
def ascender(self) -> int: ...

lib/matplotlib/mpl-data/matplotlibrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,11 @@
292292
## for more information on text properties
293293
#text.color: black
294294

295+
## The language of the text in a format accepted by libraqm, namely `a BCP47 # language
296+
## code <https://www.w3.org/International/articles/language-tags/>`_. If none, then no
297+
## particular language will be implied, and default font settings will be used.
298+
#text.language: none
299+
295300
## FreeType hinting flag ("foo" corresponds to FT_LOAD_FOO); may be one of the
296301
## following (Proprietary Matplotlib-specific synonyms are given in parentheses,
297302
## but their use is discouraged):

lib/matplotlib/rcsetup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,6 +1058,7 @@ def _convert_validator_spec(key, conv):
10581058
"text.kerning_factor": validate_int,
10591059
"text.antialiased": validate_bool,
10601060
"text.parse_math": validate_bool,
1061+
"text.language": validate_string_or_None,
10611062

10621063
"mathtext.cal": validate_font_properties,
10631064
"mathtext.rm": validate_font_properties,

lib/matplotlib/tests/test_ft2font.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,27 @@ def test_ft2font_set_text():
774774
assert font.get_bitmap_offset() == (6, 0)
775775

776776

777+
def test_ft2font_language_invalid():
778+
file = fm.findfont('DejaVu Sans')
779+
font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
780+
with pytest.raises(TypeError):
781+
font.set_text('foo', language=[1, 2, 3])
782+
with pytest.raises(TypeError):
783+
font.set_text('foo', language=[(1, 2)])
784+
with pytest.raises(TypeError):
785+
font.set_text('foo', language=[('en', 'foo', 2)])
786+
with pytest.raises(TypeError):
787+
font.set_text('foo', language=[('en', 1, 'foo')])
788+
789+
790+
def test_ft2font_language():
791+
file = fm.findfont('DejaVu Sans')
792+
font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
793+
font.set_text('foo')
794+
font.set_text('foo', language='en')
795+
font.set_text('foo', language=[('en', 1, 2)])
796+
797+
777798
def test_ft2font_loading():
778799
file = fm.findfont('DejaVu Sans')
779800
font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)

lib/matplotlib/tests/test_text.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,3 +1190,19 @@ def test_ytick_rotation_mode():
11901190
tick.set_rotation(angle)
11911191

11921192
plt.subplots_adjust(left=0.4, right=0.6, top=.99, bottom=.01)
1193+
1194+
1195+
def test_text_language_invalid():
1196+
with pytest.raises(TypeError, match='must be list of tuple'):
1197+
Text(0, 0, 'foo', language=[1, 2, 3])
1198+
with pytest.raises(TypeError, match='must be list of tuple'):
1199+
Text(0, 0, 'foo', language=[(1, 2)])
1200+
with pytest.raises(TypeError, match='start location must be int'):
1201+
Text(0, 0, 'foo', language=[('en', 'foo', 2)])
1202+
with pytest.raises(TypeError, match='end location must be int'):
1203+
Text(0, 0, 'foo', language=[('en', 1, 'foo')])
1204+
1205+
1206+
def test_text_language():
1207+
Text(0, 0, 'foo', language='en')
1208+
Text(0, 0, 'foo', language=[('en', 1, 2)])

lib/matplotlib/text.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ def __init__(self,
136136
super().__init__()
137137
self._x, self._y = x, y
138138
self._text = ''
139+
self._language = None
139140
self._reset_visual_defaults(
140141
text=text,
141142
color=color,
@@ -1422,6 +1423,41 @@ def _va_for_angle(self, angle):
14221423
return 'baseline' if anchor_at_left else 'top'
14231424
return 'top' if anchor_at_left else 'baseline'
14241425

1426+
def get_language(self):
1427+
"""Return the language this Text is in."""
1428+
return self._language
1429+
1430+
def set_language(self, language):
1431+
"""
1432+
Set the language of the text.
1433+
1434+
Parameters
1435+
----------
1436+
language : str or list[tuple[str, int, int]] or None
1437+
The language of the text in a format accepted by libraqm, namely `a BCP47
1438+
language code <https://www.w3.org/International/articles/language-tags/>`_.
1439+
1440+
If None, then defaults to :rc:`text.language`.
1441+
"""
1442+
_api.check_isinstance((list, str, None), language=language)
1443+
language = mpl._val_or_rc(language, 'text.language')
1444+
1445+
if isinstance(language, list):
1446+
for val in language:
1447+
if not isinstance(val, tuple) or len(val) != 3:
1448+
raise TypeError('language must be list of tuple, not {language!r}')
1449+
sublang, start, end = val
1450+
if not isinstance(sublang, str):
1451+
raise TypeError(
1452+
'sub-language specification must be str, not {sublang!r}')
1453+
if not isinstance(start, int):
1454+
raise TypeError('start location must be int, not {start!r}')
1455+
if not isinstance(end, int):
1456+
raise TypeError('end location must be int, not {end!r}')
1457+
1458+
self._language = language
1459+
self.stale = True
1460+
14251461

14261462
class OffsetFrom:
14271463
"""Callable helper class for working with `Annotation`."""

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy