Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Doc: tell 3.11 upgraders to remove the pre-shaping workaround
Moves the note into an admonition on the 3.11 change note, per review,
and covers the three cases: pin to 3.11, version-gate, or wrap the
pre-processed string in LRO/PDF when the call cannot be changed.
  • Loading branch information
Syamjith NK
Syamjith NK committed Sep 1, 2026
commit 4441b4c0d984d005df970b9784ea5e297aa79a27
29 changes: 29 additions & 0 deletions doc/release/prev_whats_new/whats_new_3.11.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,35 @@ Text support has been extended to include complex text layout. This support incl
Note, all advanced features require corresponding font support, and may require
additional fonts over the builtin DejaVu Sans.

.. admonition:: Remove any pre-shaping workaround
:class: important

Because Matplotlib did not previously reorder text, the usual workaround for
Arabic, Persian, Urdu and Hebrew was to reorder the string before passing it in,
typically with ``arabic_reshaper`` and ``python-bidi``::

ax.set_title(get_display(arabic_reshaper.reshape(text)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ax.set_title(get_display(arabic_reshaper.reshape(text)))
preprocessed = get_display(arabic_reshaper.reshape(text))
ax.set_title(preprocessed)

so it's clear you're using the same thing below with the direction overrides.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied, thanks — that was a real gap and not just style: the override snippet further down already referred to preprocessed, so with the expression inlined above it the name was never bound and the two examples did not read as the same value.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't reply by copy-pasting an LLM; it just repeated what I said with more words, and isn't even right about what it's saying.


Matplotlib now reorders the string itself, so a string that arrives already in
visual order is reordered a second time and is drawn backwards. Nothing is
raised, and to a reader who does not read the script the result still looks
like correct text, so this is easy to ship without noticing.

- If you can require Matplotlib 3.11, pass the logical string and delete the
pre-processing.
- If you support older versions as well, branch on the Matplotlib version and
pre-process only on the older one.
- If you cannot change the call at all, because the text is handed to a
third-party library that calls Matplotlib for you, wrap the pre-processed
string in ``LEFT-TO-RIGHT OVERRIDE`` and ``POP DIRECTIONAL FORMATTING``::

text = ('\N{LEFT-TO-RIGHT OVERRIDE}' + preprocessed +
'\N{POP DIRECTIONAL FORMATTING}')

That reads correctly on every version. It does not always render
identically, because it draws the font's presentation-form glyphs rather
than the font's own shaping, and some fonts space those differently.

Specifying font feature tags
----------------------------

Expand Down
23 changes: 0 additions & 23 deletions galleries/users_explain/text/fonts.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,27 +183,4 @@

A majority of this work was done by Aitik Gupta supported by Google Summer of
Code 2021.

Text shaping
------------

Some scripts cannot be drawn by taking a string one character at a time. Arabic
letters change shape depending on their neighbours, and right-to-left text is
stored in logical order but drawn in visual order. As of Matplotlib 3.11 this is
handled for you: FreeType is driven through libraqm, which uses HarfBuzz to shape
the string and a bidi implementation to reorder it.

Pass the logical string, the one you would read aloud::

ax.set_title("الإمارات")

Before 3.11 no shaping was done, and the usual workaround was to pre-process the
string with ``arabic_reshaper`` and ``python-bidi``::

ax.set_title(get_display(arabic_reshaper.reshape("الإمارات")))

That pre-processing must now be removed. The string it produces is already in
visual order, so Matplotlib reorders it a second time and the label is drawn
backwards. Nothing is raised, and the output still looks like Arabic to a reader
who does not read Arabic.
""" # noqa: E501
Loading