AttributedString::ReadingDirection is ignored

juce::AttributedString::ReadingDirection is ignored - the reading direction seems to be determined purely by the first character in the string.

/* BEGIN_JUCE_PIP_METADATA

  dependencies:     juce_core, juce_data_structures, juce_events, juce_graphics,
                    juce_gui_basics
  exporters:        xcode_mac, vs2022, linux_make

  type:             Component
  mainClass:        MainComponent

 END_JUCE_PIP_METADATA */

#pragma once

class MainComponent final : public juce::Component {
public:
  MainComponent() { setSize(400, 200); }

  void paint(juce::Graphics &g) override {
    g.fillAll(juce::Colours::white);

    const juce::String arabic =
        juce::CharPointer_UTF8{"\xd9\x85\xd8\xb1\xd8\xad\xd8\xa8\xd8\xa7."};

    auto bounds = getLocalBounds().reduced(10).toFloat();

    using ReadingDirection = juce::AttributedString::ReadingDirection;
    drawRow(g, bounds.removeFromTop(50.0f), "natural", arabic,
            ReadingDirection::natural);
    drawRow(g, bounds.removeFromTop(50.0f), "left-to-right", arabic,
            ReadingDirection::leftToRight);
    drawRow(g, bounds.removeFromTop(50.0f), "right-to-left", arabic,
            ReadingDirection::rightToLeft);
  }

private:
  static void drawRow(juce::Graphics &g, juce::Rectangle<float> bounds,
                      const juce::String &label, const juce::String &text,
                      juce::AttributedString::ReadingDirection direction) {
    g.setColour(juce::Colours::black);
    g.setFont(16.0f);
    g.drawText(label, bounds.removeFromLeft(170.0f),
               juce::Justification::centredLeft);

    juce::AttributedString string;
    string.append(text, juce::FontOptions{32.0f}, juce::Colours::black);
    string.setJustification(juce::Justification::centred);
    string.setReadingDirection(direction);

    juce::TextLayout layout;
    layout.createLayout(string, bounds.getWidth());
    layout.draw(g, bounds);
  }
};


This commit appears to be the cause - the comments in the SheenBidi source explain that the SBLevelDefaultLTR and SBLevelDefaultRTL constants are only used if there’s no “strong” character.

Maybe the enum could have some extra options to allow for the fallback vs forced approaches?

enum ReadingDirection
{
    natural,

    leftToRight, // Falls back to LTR if there's no "strong" character at the start
    rightToLeft, // Falls back to RTL if there's no "strong" character at the start

    leftToRightForced, // Always LTR, reguardless of the string's content
    rightToLeftForced, // Always RTL, reguardless of the string's content
};