Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 931423b

Browse filesBrowse files
committed
Add language parameter to Text objects
1 parent 6db18d5 commit 931423b
Copy full SHA for 931423b

14 files changed

+128
-17
lines changed

‎lib/matplotlib/_text_helpers.py

Copy file name to clipboardExpand all lines: lib/matplotlib/_text_helpers.py
+5-2Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_agg.py
+2-1Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_pdf.py
+4-2Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_ps.py
+2-1Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/ft2font.pyi
+6-1Lines 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/tests/test_ft2font.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_ft2font.py
+21Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_text.py
+16Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/text.py
+32Lines changed: 32 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,37 @@ 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]]
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+
_api.check_isinstance((list, str, None), language=language)
1441+
if isinstance(language, list):
1442+
for val in language:
1443+
if not isinstance(val, tuple) or len(val) != 3:
1444+
raise TypeError('language must be list of tuple, not {language!r}')
1445+
sublang, start, end = val
1446+
if not isinstance(sublang, str):
1447+
raise TypeError(
1448+
'sub-language specification must be str, not {sublang!r}')
1449+
if not isinstance(start, int):
1450+
raise TypeError('start location must be int, not {start!r}')
1451+
if not isinstance(end, int):
1452+
raise TypeError('end location must be int, not {end!r}')
1453+
1454+
self._language = language
1455+
self.stale = True
1456+
14251457

14261458
class OffsetFrom:
14271459
"""Callable helper class for working with `Annotation`."""

‎lib/matplotlib/text.pyi

Copy file name to clipboardExpand all lines: lib/matplotlib/text.pyi
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ class Text(Artist):
108108
def set_antialiased(self, antialiased: bool) -> None: ...
109109
def _ha_for_angle(self, angle: Any) -> Literal['center', 'right', 'left'] | None: ...
110110
def _va_for_angle(self, angle: Any) -> Literal['center', 'top', 'baseline'] | None: ...
111+
def get_language(self) -> str | list[tuple[str, int, int]] | None: ...
112+
def set_language(self, language: str | list[tuple[str, int, int]] | None) -> None: ...
111113

112114
class OffsetFrom:
113115
def __init__(

‎lib/matplotlib/textpath.py

Copy file name to clipboardExpand all lines: lib/matplotlib/textpath.py
+8-4Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def get_text_width_height_descent(self, s, prop, ismath):
6969
d /= 64.0
7070
return w * scale, h * scale, d * scale
7171

72-
def get_text_path(self, prop, s, ismath=False):
72+
def get_text_path(self, prop, s, ismath=False, *, language=None):
7373
"""
7474
Convert text *s* to path (a tuple of vertices and codes for
7575
matplotlib.path.Path).
@@ -82,6 +82,9 @@ def get_text_path(self, prop, s, ismath=False):
8282
The text to be converted.
8383
ismath : {False, True, "TeX"}
8484
If True, use mathtext parser. If "TeX", use tex for rendering.
85+
language : str or list of tuples of (str, int, int), optional
86+
The language of the text in a format accepted by libraqm, namely `a BCP47
87+
language code <https://www.w3.org/International/articles/language-tags/>`_.
8588
8689
Returns
8790
-------
@@ -109,7 +112,8 @@ def get_text_path(self, prop, s, ismath=False):
109112
glyph_info, glyph_map, rects = self.get_glyphs_tex(prop, s)
110113
elif not ismath:
111114
font = self._get_font(prop)
112-
glyph_info, glyph_map, rects = self.get_glyphs_with_font(font, s)
115+
glyph_info, glyph_map, rects = self.get_glyphs_with_font(font, s,
116+
language=language)
113117
else:
114118
glyph_info, glyph_map, rects = self.get_glyphs_mathtext(prop, s)
115119

@@ -130,7 +134,7 @@ def get_text_path(self, prop, s, ismath=False):
130134
return verts, codes
131135

132136
def get_glyphs_with_font(self, font, s, glyph_map=None,
133-
return_new_glyphs_only=False):
137+
return_new_glyphs_only=False, *, language=None):
134138
"""
135139
Convert string *s* to vertices and codes using the provided ttf font.
136140
"""
@@ -145,7 +149,7 @@ def get_glyphs_with_font(self, font, s, glyph_map=None,
145149

146150
xpositions = []
147151
glyph_ids = []
148-
for item in _text_helpers.layout(s, font):
152+
for item in _text_helpers.layout(s, font, language=language):
149153
char_id = self._get_char_id(item.ft_object, ord(item.char))
150154
glyph_ids.append(char_id)
151155
xpositions.append(item.x)

‎lib/matplotlib/textpath.pyi

Copy file name to clipboardExpand all lines: lib/matplotlib/textpath.pyi
+4-1Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,17 @@ class TextToPath:
1616
self, s: str, prop: FontProperties, ismath: bool | Literal["TeX"]
1717
) -> tuple[float, float, float]: ...
1818
def get_text_path(
19-
self, prop: FontProperties, s: str, ismath: bool | Literal["TeX"] = ...
19+
self, prop: FontProperties, s: str, ismath: bool | Literal["TeX"] = ..., *,
20+
language: str | list[tuple[str, int, int]] | None = ...,
2021
) -> list[np.ndarray]: ...
2122
def get_glyphs_with_font(
2223
self,
2324
font: FT2Font,
2425
s: str,
2526
glyph_map: dict[str, tuple[np.ndarray, np.ndarray]] | None = ...,
2627
return_new_glyphs_only: bool = ...,
28+
*,
29+
language: str | list[tuple[str, int, int]] | None = ...,
2730
) -> tuple[
2831
list[tuple[str, float, float, float]],
2932
dict[str, tuple[np.ndarray, np.ndarray]],

‎src/ft2font.cpp

Copy file name to clipboardExpand all lines: src/ft2font.cpp
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,8 @@ void FT2Font::set_kerning_factor(int factor)
397397
}
398398

399399
void FT2Font::set_text(
400-
std::u32string_view text, double angle, FT_Int32 flags, std::vector<double> &xys)
400+
std::u32string_view text, double angle, FT_Int32 flags, LanguageType languages,
401+
std::vector<double> &xys)
401402
{
402403
FT_Matrix matrix; /* transformation matrix */
403404

‎src/ft2font.h

Copy file name to clipboardExpand all lines: src/ft2font.h
+5-1Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#ifndef MPL_FT2FONT_H
77
#define MPL_FT2FONT_H
88

9+
#include <optional>
910
#include <set>
1011
#include <string>
1112
#include <string_view>
@@ -70,6 +71,9 @@ class FT2Font
7071
typedef void (*WarnFunc)(FT_ULong charcode, std::set<FT_String*> family_names);
7172

7273
public:
74+
using LanguageRange = std::tuple<std::string, int, int>;
75+
using LanguageType = std::optional<std::vector<LanguageRange>>;
76+
7377
FT2Font(FT_Open_Args &open_args, long hinting_factor,
7478
std::vector<FT2Font *> &fallback_list,
7579
WarnFunc warn, bool warn_if_used);
@@ -79,7 +83,7 @@ class FT2Font
7983
void set_charmap(int i);
8084
void select_charmap(unsigned long i);
8185
void set_text(std::u32string_view codepoints, double angle, FT_Int32 flags,
82-
std::vector<double> &xys);
86+
LanguageType languages, std::vector<double> &xys);
8387
int get_kerning(FT_UInt left, FT_UInt right, FT_Kerning_Mode mode, bool fallback);
8488
int get_kerning(FT_UInt left, FT_UInt right, FT_Kerning_Mode mode, FT_Vector &delta);
8589
void set_kerning_factor(int factor);

‎src/ft2font_wrapper.cpp

Copy file name to clipboardExpand all lines: src/ft2font_wrapper.cpp
+19-3Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,8 @@ const char *PyFT2Font_set_text__doc__ = R"""(
712712

713713
static py::array_t<double>
714714
PyFT2Font_set_text(PyFT2Font *self, std::u32string_view text, double angle = 0.0,
715-
std::variant<LoadFlags, FT_Int32> flags_or_int = LoadFlags::FORCE_AUTOHINT)
715+
std::variant<LoadFlags, FT_Int32> flags_or_int = LoadFlags::FORCE_AUTOHINT,
716+
std::variant<FT2Font::LanguageType, std::string> languages_or_str = nullptr)
716717
{
717718
std::vector<double> xys;
718719
LoadFlags flags;
@@ -732,7 +733,21 @@ PyFT2Font_set_text(PyFT2Font *self, std::u32string_view text, double angle = 0.0
732733
throw py::type_error("flags must be LoadFlags or int");
733734
}
734735

735-
self->x->set_text(text, angle, static_cast<FT_Int32>(flags), xys);
736+
FT2Font::LanguageType languages;
737+
if (auto value = std::get_if<FT2Font::LanguageType>(&languages_or_str)) {
738+
languages = std::move(*value);
739+
} else if (auto value = std::get_if<std::string>(&languages_or_str)) {
740+
languages = std::vector<FT2Font::LanguageRange>{
741+
FT2Font::LanguageRange{*value, 0, text.size()}
742+
};
743+
} else {
744+
// NOTE: this can never happen as pybind11 would have checked the type in the
745+
// Python wrapper before calling this function, but we need to keep the
746+
// std::get_if instead of std::get for macOS 10.12 compatibility.
747+
throw py::type_error("languages must be str or list of tuple");
748+
}
749+
750+
self->x->set_text(text, angle, static_cast<FT_Int32>(flags), languages, xys);
736751

737752
py::ssize_t dims[] = { static_cast<py::ssize_t>(xys.size()) / 2, 2 };
738753
py::array_t<double> result(dims);
@@ -1621,7 +1636,8 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
16211636
.def("get_kerning", &PyFT2Font_get_kerning, "left"_a, "right"_a, "mode"_a,
16221637
PyFT2Font_get_kerning__doc__)
16231638
.def("set_text", &PyFT2Font_set_text,
1624-
"string"_a, "angle"_a=0.0, "flags"_a=LoadFlags::FORCE_AUTOHINT,
1639+
"string"_a, "angle"_a=0.0, "flags"_a=LoadFlags::FORCE_AUTOHINT, py::kw_only(),
1640+
"language"_a=nullptr,
16251641
PyFT2Font_set_text__doc__)
16261642
.def("_get_fontmap", &PyFT2Font_get_fontmap, "string"_a,
16271643
PyFT2Font_get_fontmap__doc__)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.