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

Skip to content

Commit 3c188df

Browse files
committed
Add language parameter to Text objects
1 parent 6db18d5 commit 3c188df

File tree

14 files changed

+128
-17
lines changed
  • tests
  • src
  • 14 files changed

    +128
    -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/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: 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

    Lines 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

    Lines 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):
    149153
    char_id = self._get_char_id(item.ft_object, ord(item.char))
    150154
    glyph_ids.append(char_id)
    151155
    xpositions.append(item.x)

    0 commit comments

    Comments
     (0)
    0