8000 Add language parameter to Text objects · matplotlib/matplotlib@669ba8f · GitHub
[go: up one dir, main page]

Skip to content

Commit 669ba8f

Browse files
committed
Add language parameter to Text objects
1 parent a7c08c8 commit 669ba8f

File tree

17 files changed

+175
-17
lines changed

17 files changed

+175
-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 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)])

0 commit comments

Comments
 (0)
0