From b85ef8a5eb7ec3fcb4a49a66575fcb5bdb39a3a5 Mon Sep 17 00:00:00 2001 From: Syamjith NK Date: Sat, 29 Aug 2026 14:01:06 +0400 Subject: [PATCH 1/2] Docs: warn that pre-shaping corrupts text under the Raqm layout engine Running a string through arabic_reshaper + python-bidi before drawing is the standard advice in most tutorials for Arabic with Pillow, and it is correct for Layout.BASIC. Under Layout.RAQM it double-shapes: Pillow already shapes and reorders, so the text renders but is wrong. Which engine is used depends on how Pillow was built, so the same code can be correct on one machine and wrong on another - which is why the pattern persists in projects that no longer need it. Placed in the Layout section rather than on ImageDraw.text because the behaviour is a property of the engine. Refs #9907 --- docs/reference/ImageFont.rst | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index bf9d3a583b2..cbc1dcaf066 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -100,6 +100,34 @@ Constants Requires Raqm, you can check support using :py:func:`PIL.features.check_feature` with ``feature="raqm"``. + .. warning:: Do not pre-shape text for the Raqm engine. + + A widespread pattern for Arabic, Persian and Urdu is to run the string + through ``arabic_reshaper`` and ``python-bidi`` before drawing it. That + is correct **only** for :py:attr:`PIL.ImageFont.Layout.BASIC`, which does no + shaping of its own. + + Under :py:attr:`PIL.ImageFont.Layout.RAQM`, Pillow shapes and reorders the text itself, so a + pre-shaped string is shaped a second time. The result still renders — it + is simply wrong, which is what makes the failure easy to miss. + + Which engine is used depends on how Pillow was built, so the same code + can be correct on one machine and wrong on another. Check at run time + rather than assuming:: + + from PIL import features + + if features.check_feature("raqm"): + draw.text(xy, arabic_text, font=font) + else: + draw.text(xy, reshaped_bidi_text, font=font) + + The damage is not confined to the image. Applications routinely store + the reshaped string, so the corruption is written to the database and + outlives any later fix. It also cannot be undone by mapping the + presentation forms back, because lam-alef ligatures collapse two + characters into one glyph. + .. data:: MAX_STRING_LENGTH Set to 1,000,000, to protect against potential DOS attacks. Pillow will From 02e075e1b3bac046a50ce61314b1e901281bff7c Mon Sep 17 00:00:00 2001 From: Syamjith NK Date: Sun, 30 Aug 2026 15:37:05 +0400 Subject: [PATCH 2/2] Drop out-of-scope paragraph on stored text and presentation-form repair --- docs/reference/ImageFont.rst | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index cbc1dcaf066..149a8ed7827 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -107,9 +107,10 @@ Constants is correct **only** for :py:attr:`PIL.ImageFont.Layout.BASIC`, which does no shaping of its own. - Under :py:attr:`PIL.ImageFont.Layout.RAQM`, Pillow shapes and reorders the text itself, so a - pre-shaped string is shaped a second time. The result still renders — it - is simply wrong, which is what makes the failure easy to miss. + Under :py:attr:`PIL.ImageFont.Layout.RAQM`, Pillow shapes and reorders + the text itself, so a pre-shaped string is shaped a second time. The + result still renders, it is simply wrong, which is what makes the + failure easy to miss. Which engine is used depends on how Pillow was built, so the same code can be correct on one machine and wrong on another. Check at run time @@ -122,12 +123,6 @@ Constants else: draw.text(xy, reshaped_bidi_text, font=font) - The damage is not confined to the image. Applications routinely store - the reshaped string, so the corruption is written to the database and - outlives any later fix. It also cannot be undone by mapping the - presentation forms back, because lam-alef ligatures collapse two - characters into one glyph. - .. data:: MAX_STRING_LENGTH Set to 1,000,000, to protect against potential DOS attacks. Pillow will