GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — [Doc]: 3.11 text shaping silently reverses the arabic_reshaper + python-bidi workaround · Issue #32262 · matplotlib/matplotlib

[Doc]: 3.11 text shaping silently reverses the arabic_reshaper + python-bidi workaround #32262

New issue
Copy link
New issue
Copy link
Closed
#32263
Closed
[Doc]: 3.11 text shaping silently reverses the arabic_reshaper + python-bidi workaround#32262
#32263
Copy link
Labels
first-contribution
Milestone
v3.11-doc

Description

@Syamjith-NK
Syamjith-NK
opened on Aug 30, 2026
Issue body actions

Documentation Link

https://matplotlib.org/devdocs/users/explain/text/text_intro.html

Problem

Since #30000 landed, matplotlib shapes and reorders complex scripts itself. That makes the workaround recommended in essentially every "Arabic text in matplotlib" answer actively wrong:

text = get_display(arabic_reshaper.reshape(text))   # necessary before 3.11, reversed now

The string is already in visual order, matplotlib applies the bidi algorithm again, and the label renders reversed. Nothing raises, and to a reader who does not read Arabic the output still looks like Arabic in the right place, which is why I think it is worth a note rather than leaving it to be discovered.

arabic_reshaper appears nowhere in the docs, so nothing currently tells someone upgrading that the pre-processing should be removed.

Measured on matplotlib 3.11.0, libraqm 0.10.5, macOS, using SFArabic.ttf and the string الإمارات. Mean absolute pixel difference, normalised to 64px height, against a Pillow/Raqm render of the same string in the same font:

render difference
raw logical string 7.75 (antialiasing only, i.e. matching)
get_display(reshape(...)) 76.28 (reversed)

One extra detail worth knowing: the reshape collapses lam-alef, so الإمارات goes from 8 codepoints to 7. Where an application stored the pre-shaped string rather than only rendering it, mapping the presentation forms back does not recover the original, because that one codepoint expands in logical order while its neighbours are still in visual order. السلام comes back as السالم, a real but different word.

Self-contained reproducer
import matplotlib
matplotlib.use("Agg")
import arabic_reshaper, numpy as np
import matplotlib.pyplot as plt, matplotlib.ft2font as ft
from bidi.algorithm import get_display
from matplotlib import font_manager
from PIL import Image, ImageDraw, ImageFont, features

FONT = "/System/Library/Fonts/SFArabic.ttf"   # any Arabic-capable TTF
TEXT = "الإمارات"
PRE = get_display(arabic_reshaper.reshape(TEXT))
print("matplotlib", matplotlib.__version__, "libraqm", ft.__libraqm_version__)
print("codepoints:", len(TEXT), "->", len(PRE))

fp = font_manager.FontProperties(fname=FONT)

def mpl(text, path):
    fig = plt.figure(figsize=(6, 1.3), dpi=100)
    fig.text(0.04, 0.3, text, fontproperties=fp, fontsize=48)
    fig.savefig(path, facecolor="white"); plt.close(fig)

def pil(text, path, px=67):
    font = ImageFont.truetype(FONT, px, layout_engine=ImageFont.Layout.RAQM)
    img = Image.new("L", (600, 130), 255)
    ImageDraw.Draw(img).text((25, 15), text, font=font, fill=0); img.save(path)

mpl(TEXT, "a_raw.png"); mpl(PRE, "b_preshaped.png"); pil(TEXT, "c_reference.png")

def normalised(path, h=64):
    a = 255 - np.asarray(Image.open(path).convert("L"), float)
    ys, xs = np.nonzero(a > 40)
    im = Image.fromarray((255 - a[ys.min():ys.max()+1, xs.min():xs.max()+1]).astype("uint8"))
    return np.asarray(im.resize((int(im.width * h / im.height), h)), float)

def diff(p, q):
    a, b = normalised(p), normalised(q)
    w = min(a.shape[1], b.shape[1])
    return float(np.abs(a[:, :w] - b[:, :w]).mean())

print("raw       vs reference:", round(diff("a_raw.png", "c_reference.png"), 2))
print("preshaped vs reference:", round(diff("b_preshaped.png", "c_reference.png"), 2))

Open a_raw.png and b_preshaped.png side by side, the second is the first read backwards.

Suggested improvement

A documentation-only note, in the text guide and in the 3.11 release note: pass the logical string and remove any arabic_reshaper / python-bidi pre-processing, because matplotlib now does the shaping and reordering.

Worth saying plainly that this is unconditional, unlike the equivalent situation in Pillow. I checked extern/meson.build before writing this: libraqm is a required dependency, resolved either from the system or from the vendored static subproject, so there is no build in which the old pre-processing is still correct.

Happy to open that PR if you would like it. I did the equivalent for Pillow in python-pillow/Pillow#9925.

matplotlib Version

3.11.0

Operating system

macOS

Activity

  1. github-actions
    added
    first-contribution
    on Aug 30, 2026
  2. rcomer commented on Aug 30, 2026

    @rcomer
    rcomer
    on Aug 30, 2026
    Member
    More actions

    If you search the Matplotlib docs for "Arabic", you get this changenote
    https://matplotlib.org/stable/release/prev_whats_new/whats_new_3.11.0.html#complex-text-layout-with-libraqm

    Although it does not explicitly say that workarounds should be removed, the fact that it's now natively supported implies they are at least unnecessary.

    Prompted by this issue, I just went and edited some StackOverflow answers to clarify those workarounds are only needed for older versions.

  3. story645 commented on Aug 30, 2026

    @story645
    story645
    on Aug 30, 2026
    Member
    More actions

    Although it does not explicitly say that workarounds should be removed,

    I think a PR that says that explicitly inside an admonition in the change notes is fine since folks have had to do the workaround for so long and there are so many docs about it.

    Not sure about putting it in main notes if we don't already mention rtl languages but otherwise an entry on rtl languages somewhere in the docs might be helpful.

  4. tacaswell commented on Aug 30, 2026

    @tacaswell
    tacaswell
    on Aug 30, 2026
    Member
    More actions

    I left comments with code on #32263 . TL;DR if you wrap the text in unicode LRO / PDF then pre-processed text will render correctly with all versions of mpl.

  5. Syamjith-NK commented on Sep 2, 2026

    @Syamjith-NK
    Syamjith-NK
    on Sep 2, 2026
    ContributorAuthor
    More actions

    Answered on #32263 rather than here, so linking it for anyone reading this thread.

    I tested the LRO/PDF wrapping across three fonts and nine strings. It reads correctly in all nine — including a lam-alef ligature and Latin digits mixed in — so the recommendation holds. It does not always render identically: the wrapped string draws the font's static Presentation Forms glyphs instead of the font's own shaping, so joins loosen and the line widens wherever those differ. Arial Unicode was unaffected; Geeza Pro differed on every string tested.

    Measurements and the per-font table are in #32263.

  6. ksunden
    closed this as completedin #32263on Sep 4, 2026
  7. QuLogic
    added this to the v3.11-doc milestone on Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    first-contribution

    Type

    No type

    Projects

    No projects

      Milestone

      • v3.11-docNo due date

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.