Marc Van Daele
Marc Van Daele

Reputation: 2734

How to render RTL text correct on older Android versions

I've used Google Translate to translate the following string "I think CompanyXYZ is a great company in 2014 and beyond" to Hebrew and this resulted in enter image description here

When I display this text in a TextView on a Galaxy S3 (Android 4.3), I get enter image description here

which seems to be correct.

When I run the same program on a Galaxy Tab 7 (running Android 2.2), I get enter image description here

which is obviously not correct.

Can I use android.support.v4.text.BidiFormatter and/or java.text.Bidi to render this correct?
When running the following code

    Bidi bidi = new Bidi(text, Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT);
    for (int i = 0; i < bidi.getRunCount(); i++)
    {
        int start = bidi.getRunStart(i);
        int level = bidi.getRunLevel(i);
        int limit = bidi.getRunLimit(i);
        Log.d("RTL", "bidi.run["+i+"] = "+start+";"+level+";"+limit+";"+ text.substring(start, limit));
    }

I do get the following 5 runs

bidi.run[0] = 0;1;9;אני חושב 
bidi.run[1] = 9;2;19;CompanyXYZ
bidi.run[2] = 19;1;36; היא חברה גדולה ב
bidi.run[3] = 36;2;40;2014
bidi.run[4] = 40;1;46; ומעבר

Hence all the information seems to be available to render this string correct but I don't know how to proceed. Can I use BidiFormatter? Or should I override TextView.draw()?

Upvotes: 1

Views: 1690

Answers (3)

Ali Bagheri
Ali Bagheri

Reputation: 3409

I think below code can help you

public static Spannable getRtlBidiString(String input)
{
    return getBidiString(Spannable.Factory.getInstance().newSpannable(input), Bidi.DIRECTION_RIGHT_TO_LEFT);
}

public static Spannable getForceRtlBidiString(String input)
{
    return getForceBidiString(Spannable.Factory.getInstance().newSpannable(input), Bidi.DIRECTION_RIGHT_TO_LEFT);
}

public static Spannable getRtlBidiString(Spannable input)
{
    return getBidiString(input, Bidi.DIRECTION_RIGHT_TO_LEFT);
}

public static Spannable getLtrBidiString(Spannable input)
{
    return getBidiString(input, Bidi.DIRECTION_LEFT_TO_RIGHT);
}

public static Spannable getLtrBidiString(String input)
{
    return getBidiString(Spannable.Factory.getInstance().newSpannable(input), Bidi.DIRECTION_LEFT_TO_RIGHT);
}

public static Spannable getBidiString(Spannable input, int direction)
{
    boolean isRtl;
    int baseDirection;
    TextDirectionHeuristicCompat heu;

    if (direction == Bidi.DIRECTION_LEFT_TO_RIGHT)
    {
        isRtl = false;
        baseDirection = Bidi.DIRECTION_LEFT_TO_RIGHT;
        heu = TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR;
    }
    else  /* if (lang == Util.Lang.HE) */
    {
        isRtl = true;
        baseDirection = Bidi.DIRECTION_RIGHT_TO_LEFT;
        heu = TextDirectionHeuristicsCompat.FIRSTSTRONG_RTL;
    }

    Bidi bidi = new Bidi(input.toString(), baseDirection);

    if (!bidi.isMixed())
        return input;

    BidiFormatter bidiFormatter = BidiFormatter.getInstance(isRtl);
    SpannableStringBuilder bidiBuilder = new SpannableStringBuilder();

    for (int i = 0; i < bidi.getRunCount(); i++)
    {
        int start = bidi.getRunStart(i);
        int level = bidi.getRunLevel(i);
        int limit = bidi.getRunLimit(i);

        CharSequence run = input.subSequence(start, limit);

        if (level != baseDirection)
        {
            run = bidiFormatter.unicodeWrap(run, heu, !isRtl).toString();
        }

        bidiBuilder.append(run);
    }

    return bidiBuilder;
}

public static Spannable getForceBidiString(Spannable input, int direction)
{
    boolean isRtl;
    int baseDirection;
    TextDirectionHeuristicCompat heu;

    if (direction == Bidi.DIRECTION_LEFT_TO_RIGHT)
    {
        isRtl = false;
        baseDirection = Bidi.DIRECTION_LEFT_TO_RIGHT;
        heu = TextDirectionHeuristicsCompat.ANYRTL_LTR;
    }
    else  /* if (lang == Util.Lang.HE) */
    {
        isRtl = true;
        baseDirection = Bidi.DIRECTION_RIGHT_TO_LEFT;
        heu = TextDirectionHeuristicsCompat.ANYRTL_LTR;
    }

    Bidi bidi = new Bidi(input.toString(), baseDirection);

    if (!bidi.isMixed())
        return input;

    BidiFormatter bidiFormatter = BidiFormatter.getInstance(isRtl);
    SpannableStringBuilder bidiBuilder = new SpannableStringBuilder();

    for (int i = 0; i < bidi.getRunCount(); i++)
    {
        int start = bidi.getRunStart(i);
        int level = bidi.getRunLevel(i);
        int limit = bidi.getRunLimit(i);

        CharSequence run = input.subSequence(start, limit);

        if (level != baseDirection)
        {
            run = bidiFormatter.unicodeWrap(run, heu, !isRtl).toString();
        }

        bidiBuilder.append(run);
    }

    return bidiBuilder;
}

Upvotes: 0

Noah Santacruz
Noah Santacruz

Reputation: 460

Ok, I realize it's been a while...

I made a function for my app which gets bidirectional strings, depending on the main direction which I thought would be helpful.

public static String getBidiString(String input, int direction) {
    boolean rtlContext;
    int defaultBidiDirection;
    if (direction == Bidi.DIRECTION_LEFT_TO_RIGHT) {
        rtlContext = false;
        defaultBidiDirection = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
    } else /* if (lang == Util.Lang.HE) */{
        rtlContext = true;
        defaultBidiDirection = Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT;
    }

    Bidi bidi = new Bidi(input,defaultBidiDirection);
    if (!bidi.isMixed()) return input;



    BidiFormatter bidiFormatter = BidiFormatter.getInstance(rtlContext);
    StringBuilder bidiTestBuilder = new StringBuilder();
    for (int i = 0; i < bidi.getRunCount(); i++)
    {
        int start = bidi.getRunStart(i);
        int level = bidi.getRunLevel(i);
        int limit = bidi.getRunLimit(i);
        String run = input.substring(start, limit);

        if (level != direction) {
            run = bidiFormatter.unicodeWrap(run,!rtlContext) + " ";
        }
        bidiTestBuilder.append(run);
    }

    return bidiTestBuilder.toString();
}

the direction parameter should be either Bidi.DIRECTION_LEFT_TO_RIGHT or Bidi.DIRECTION_RIGHT_TO_LEFT depending on the default direction of the text.

I certainly am not claiming that I implemented this completely correctly, but this code works for my use cases.

Upvotes: 2

Alex Cohn
Alex Cohn

Reputation: 57173

Rendering on your 2.2 device is not incorrect. The difference is that the paragraph direction is LTR. It is very common to set paragraph orientation "by context", which means - according to the first bid I run. But it is legitimate to set the direction otherwise. For example, Windows textbox allows the end user switch this direction by pressing left ctrlshift or right ctrlshift (left or right shift, does not matter). Usually, alignment follows this direction.

Official support for RTL layout direction was introduced in 4.2. But even before, since 2011 (it seems to be 4.0.1), there has been a @hide method View.setLayoutDirection() credits to Du Shunpeng, who published his answer a year ago.

Unfortunately, 2.2 is even older, and this non-public API was not available. Consider using WebView, it does support dir=rtl for div and text input.

Note that even back then, the ME versions of Android, including the Tab 7 device, were often customized by the manufacturer or distributor to provide some level of BiDi support, so it's important to test on the device that will be used by your audience.

Upvotes: 1

Related Questions