Skip to content

Commit 266805d

Browse files
committed
Add language parameter to Text objects
1 parent 7dae1e5 commit 266805d

File tree

17 files changed

+199
-17
lines changed

17 files changed

+199
-17
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
Specifying text language
2+
------------------------
3+
4+
OpenType fonts may support language systems which can be used to select different
5+
typographic conventions, e.g., localized variants of letters that share a single Unicode
6+
code point, or different default font features. The text API now supports setting a
7+
language to be used and may be set/get with:
8+
9+
- `matplotlib.text.Text.set_language` / `matplotlib.text.Text.get_language`
10+
- Any API that creates a `.Text` object by passing the *language* argument (e.g.,
11+
``plt.xlabel(..., language=...)``)
12+
13+
The language of the text must be in a format accepted by libraqm, namely `a BCP47
14+
language code <https://www.w3.org/International/articles/language-tags/>`_. If None or
15+
unset, then no particular language will be implied, and default font settings will be
16+
used.
17+
18+
For example, the default font ``DejaVu Sans`` supports language-specific glyphs in the
19+
Serbian and Macedonian languages in the Cyrillic alphabet, or the Sámi family of
20+
languages in the Latin alphabet.
21+
22+
.. plot::
23+
:include-source:
24+
25+
fig = plt.figure(figsize=(7, 3))
26+
27+
char = '\U00000431'
28+
fig.text(0.5, 0.8, f'\\U{ord(char):08x}', fontsize=40, horizontalalignment='center')
29+
fig.text(0, 0.6, f'Serbian: {char}', fontsize=40, language='sr')
30+
fig.text(1, 0.6, f'Russian: {char}', fontsize=40, language='ru',
31+
horizontalalignment='right')
32+
33+
char = '\U0000014a'
34+
fig.text(0.5, 0.3, f'\\U{ord(char):08x}', fontsize=40, horizontalalignment='center')
35+
fig.text(0, 0.1, f'English: {char}', fontsize=40, language='en')
36+
fig.text(1, 0.1, f'Inari Sámi: {char}', fontsize=40, language='smn',
37+
horizontalalignment='right')

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, 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
@@ -2345,6 +2345,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
23452345
return self.draw_mathtext(gc, x, y, s, prop, angle)
23462346

23472347
fontsize = prop.get_size_in_points()
2348+
language = mtext.get_language() if mtext is not None else None
23482349

23492350
if mpl.rcParams['pdf.use14corefonts']:
23502351
font = self._get_font_afm(prop)
@@ -2355,7 +2356,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
23552356
fonttype = mpl.rcParams['pdf.fonttype']
23562357

23572358
if gc.get_url() is not None:
2358-
font.set_text(s)
2359+
font.set_text(s, language=language)
23592360
width, height = font.get_width_height()
23602361
self.file._annotations[-1][1].append(_get_link_annotation(
23612362
gc, x, y, width / 64, height / 64, angle))
@@ -2389,7 +2390,8 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
23892390
multibyte_glyphs = []
23902391
prev_was_multibyte = True
23912392
prev_font = font
2392-
for item in _text_helpers.layout(s, font, kern_mode=Kerning.UNFITTED):
2393+
for item in _text_helpers.layout(s, font, language=language,
2394+
kern_mode=Kerning.UNFITTED):
23932395
if _font_supports_glyph(fonttype, ord(item.char)):
23942396
if prev_was_multibyte or item.ft_object != prev_font:
23952397
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
@@ -794,9 +794,10 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
794794
thisx += width * scale
795795

796796
else:
797+
language = mtext.get_language() if mtext is not None else None
797798
font = self._get_font_ttf(prop)
798799
self._character_tracker.track(font, s)
799-
for item in _text_helpers.layout(s, font):
800+
for item in _text_helpers.layout(s, font, language=language):
800801
ps_name = (item.ft_object.postscript_name
801802
.encode("ascii", "replace").decode("ascii"))
802803
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
@@ -1045,6 +1045,7 @@ def _convert_validator_spec(key, conv):
10451045
"text.kerning_factor": validate_int,
10461046
"text.antialiased": validate_bool,
10471047
"text.parse_math": validate_bool,
1048+
"text.language": validate_string_or_None,
10481049

10491050
"mathtext.cal": validate_font_properties,
10501051
"mathtext.rm": validate_font_properties,

lib/matplotlib/tests/test_ft2font.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,37 @@ def test_ft2font_set_text():
776776
assert font.get_bitmap_offset() == (6, 0)
777777

778778

779+
@pytest.mark.parametrize(
780+
'input',
781+
[
782+
[1, 2, 3],
783+
[(1, 2)],
784+
[('en', 'foo', 2)],
785+
[('en', 1, 'foo')],
786+
],
787+
ids=[
788+
'nontuple',
789+
'wrong length',
790+
'wrong start type',
791+
'wrong end type',
792+
],
793+
)
794+
def test_ft2font_language_invalid(input):
795+
file = fm.findfont('DejaVu Sans')
796+
font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
797+
with pytest.raises(TypeError):
798+
font.set_text('foo', language=input)
799+
800+
801+
def test_ft2font_language():
802+
# TODO: This is just a smoke test.
803+
file = fm.findfont('DejaVu Sans')
804+
font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
805+
font.set_text('foo')
806+
font.set_text('foo', language='en')
807+
font.set_text('foo', language=[('en', 1, 2)])
808+
809+
779810
def test_ft2font_loading():
780811
file = fm.findfont('DejaVu Sans')
781812
font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)

lib/matplotlib/tests/test_text.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,3 +1194,28 @@ def test_ytick_rotation_mode():
11941194
tick.set_rotation(angle)
11951195

11961196
plt.subplots_adjust(left=0.4, right=0.6, top=.99, bottom=.01)
1197+
1198+
1199+
@pytest.mark.parametrize(
1200+
'input, match',
1201+
[
1202+
([1, 2, 3], 'must be list of tuple'),
1203+
([(1, 2)], 'must be list of tuple'),
1204+
([('en', 'foo', 2)], 'start location must be int'),
1205+
([('en', 1, 'foo')], 'end location must be int'),
1206+
],
1207+
)
1208+
def test_text_language_invalid(input, match):
1209+
with pytest.raises(TypeError, match=match):
1210+
Text(0, 0, 'foo', language=input)
1211+
1212+
1213+
def test_text_language():
1214+
# TODO: This is just a smoke test.
1215+
Text(0, 0, 'foo', language='en')
1216+
Text(0, 0, 'foo').set_language('en')
1217+
Text(0, 0, 'foo', language=[('en', 1, 2)])
1218+
Text(0, 0, 'foo').set_language([('en', 1, 2)])
1219+
# Not documented, but we'll allow it.
1220+
Text(0, 0, 'foo', language=(('en', 1, 2), ))
1221+
Text(0, 0, 'foo').set_language((('en', 1, 2), ))

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