Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 76745268 authored by Eran's avatar Eran
Browse files

Fix RTL support, specifically fixes the combination of RTL text with numbers and LTR text.

Additionally changes the behavior to align any block with RTL text to the right.
Tested heavily with hebrew.

Fix by: Eran Mizrahi and Ron Regev

Change-Id: I689a39d28efa6874e844669af29e895e93c6cd23
parent 0a17f45e
Loading
Loading
Loading
Loading
+2 −33
Original line number Diff line number Diff line
@@ -1818,43 +1818,12 @@ public abstract class Layout {
        // are left-to-right, the others are right-to-left.  So, for example,
        // a line that starts with a right-to-left run has 0 at mDirections[0],
        // since the 'first' (ltr) run is zero length.
        //
        // The code currently assumes that each run is adjacent to the previous
        // one, progressing in the base line direction.  This isn't sufficient
        // to handle nested runs, for example numeric text in an rtl context
        // in an ltr paragraph.
        /* package */ Directions(short[] dirs) {
            mDirections = dirs;
        }

        static int baseDirection(Directions dir,int length) {
            if (dir == DIRS_ALL_LEFT_TO_RIGHT) {
                return DIR_LEFT_TO_RIGHT;
            } else if (dir == DIRS_ALL_RIGHT_TO_LEFT) {
                return DIR_RIGHT_TO_LEFT;
            } 

            int sum=0;
            int lastSwitch=0;
            int i=0;
            while ((i+1) < dir.mDirections.length) {
                sum+=dir.mDirections[i];//-lastSwitch;
                sum-=dir.mDirections[i+1];//-dir.mDirections[i];
                lastSwitch=dir.mDirections[i+1];
                i+=2;
            }

            if ((i+1)==dir.mDirections.length) {
                sum+=dir.mDirections[i];//-lastSwitch);
            } else if (i==dir.mDirections.length) {
                sum-=length-lastSwitch;
            }

            if (sum>=0) {
                return DIR_LEFT_TO_RIGHT;
            } else {
                return DIR_RIGHT_TO_LEFT;
            }
        boolean hasRTL() {
            return mDirections.length>1 && mDirections[1]>0;
        }
    }

+77 −33
Original line number Diff line number Diff line
@@ -233,10 +233,20 @@ extends Layout
                }
            }

            if (!easy) {
                // XXX put override flags, etc. into chdirs
                dir = bidi(dir >= 0 ? DIR_REQUEST_DEFAULT_LTR : DIR_REQUEST_DEFAULT_RTL,
                           chs, chdirs, n, false);
            }

            // Ensure that none of the underlying characters are treated
            // as viable breakpoints, and that the entire run gets the
            // same bidi direction.

            final byte SOR = dir == DIR_LEFT_TO_RIGHT ?
                Character.DIRECTIONALITY_LEFT_TO_RIGHT :
                Character.DIRECTIONALITY_RIGHT_TO_LEFT;

            if (source instanceof Spanned) {
                Spanned sp = (Spanned) source;
                ReplacementSpan[] spans = sp.getSpans(start, end, ReplacementSpan.class);
@@ -246,14 +256,13 @@ extends Layout
                    int b = sp.getSpanEnd(spans[y]);

                    for (int x = a; x < b; x++) {
                        chdirs[x - start] = SOR;
                        chs[x - start] = '\uFFFC';
                    }
                }
            }

            if (!easy) {
                // XXX put override flags, etc. into chdirs
                dir = bidi(dir, chs, chdirs, n, false);

                // Do mirroring for right-to-left segments

@@ -638,26 +647,16 @@ extends Layout
         * Determine primary paragraph direction if not specified
         */
        if (dir != DIR_REQUEST_LTR && dir != DIR_REQUEST_RTL) {
            // set up default
            dir = dir >= 0 ? DIR_LEFT_TO_RIGHT : DIR_RIGHT_TO_LEFT;
            for (int j = 0; j < n; j++) {
                int d = chInfo[j];

                if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT) {
            // Heuristic - LTR unless paragraph contains any RTL chars
            dir = DIR_LEFT_TO_RIGHT;
                    break;
                }
                if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
            for (int j = 0; j < n; j++) {
                if (chInfo[j] == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
                    dir = DIR_RIGHT_TO_LEFT;
                    break;
                }
            }
        }

        final byte SOR = dir == DIR_LEFT_TO_RIGHT ?
                Character.DIRECTIONALITY_LEFT_TO_RIGHT :
                Character.DIRECTIONALITY_RIGHT_TO_LEFT;

        /*
         * XXX Explicit overrides should go here
         */
@@ -666,7 +665,11 @@ extends Layout
         * Weak type resolution
         */

        // dump(chdirs, n, "initial");
        final byte SOR = dir == DIR_LEFT_TO_RIGHT ?
                Character.DIRECTIONALITY_LEFT_TO_RIGHT :
                Character.DIRECTIONALITY_RIGHT_TO_LEFT;

        // dump(chInfo, n, "initial");

        // W1 non spacing marks
        for (int j = 0; j < n; j++) {
@@ -678,7 +681,7 @@ extends Layout
            }
        }

        // dump(chdirs, n, "W1");
        // dump(chInfo, n, "W1");

        // W2 european numbers
        byte cur = SOR;
@@ -693,11 +696,10 @@ extends Layout
                 if (cur ==
                    Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
                    chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
		else chInfo[j] = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
            }
        }

        // dump(chdirs, n, "W2");
        // dump(chInfo, n, "W2");

        // W3 arabic letters
        for (int j = 0; j < n; j++) {
@@ -705,7 +707,7 @@ extends Layout
                chInfo[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
        }

        // dump(chdirs, n, "W3");
        // dump(chInfo, n, "W3");

        // W4 single separator between numbers
        for (int j = 1; j < n - 1; j++) {
@@ -713,6 +715,9 @@ extends Layout
            byte prev = chInfo[j - 1];
            byte next = chInfo[j + 1];

            boolean isSpace = Character.isWhitespace(chs[j]);
            boolean nextIsSpace = Character.isWhitespace(chs[j + 1]);

            if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR) {
                if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
                    next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
@@ -724,10 +729,31 @@ extends Layout
                if (prev == Character.DIRECTIONALITY_ARABIC_NUMBER &&
                    next == Character.DIRECTIONALITY_ARABIC_NUMBER)
                    chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
                // add condition for spaces following the separator
                if (nextIsSpace &&
                            (   prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER
                             || prev == Character.DIRECTIONALITY_ARABIC_NUMBER  ) )
                            chInfo[j] = SOR;
            }
            // add condition if the separator is a space
            else if (isSpace && prev != SOR &&
                            (   next == Character.DIRECTIONALITY_EUROPEAN_NUMBER
                             || next == Character.DIRECTIONALITY_ARABIC_NUMBER  ) ) {
                chInfo[j] = SOR;
                for (int k=j+1; k < n; ++k) {
                    if (chInfo[k] == Character.DIRECTIONALITY_LEFT_TO_RIGHT) {
                        chInfo[j] = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
                        break;
                    }
                    if (chInfo[k] == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
                        chInfo[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
                        break;
                    }
                }
            }
        }

        // dump(chdirs, n, "W4");
        // dump(chInfo, n, "W4");

        // W5 european number terminators
        boolean adjacent = false;
@@ -742,7 +768,7 @@ extends Layout
                adjacent = false;
        }

        //dump(chdirs, n, "W5");
        //dump(chInfo, n, "W5");

        // W5 european number terminators part 2,
        // W6 separators and terminators
@@ -769,7 +795,7 @@ extends Layout
            }
        }

        // dump(chdirs, n, "W6");
        // dump(chInfo, n, "W6");

        // W7 strong direction of european numbers
        cur = SOR;
@@ -785,7 +811,7 @@ extends Layout
                chInfo[j] = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
        }

        // dump(chdirs, n, "W7");
        // dump(chInfo, n, "W7");

        // N1, N2 neutrals
        cur = SOR;
@@ -795,9 +821,8 @@ extends Layout
            if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
                d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
                cur = d;
            } else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) {
                cur = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
            } else if (d == Character.DIRECTIONALITY_ARABIC_NUMBER) {
            } else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
                       d == Character.DIRECTIONALITY_ARABIC_NUMBER) {
                cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
            } else {
                byte dd = SOR;
@@ -810,12 +835,10 @@ extends Layout
                        dd == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
                        break;
                    }
                    if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER) {
                    if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
                        dd == Character.DIRECTIONALITY_ARABIC_NUMBER) {
                        dd = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
                        break;
                    } else if (dd == Character.DIRECTIONALITY_ARABIC_NUMBER) {
                        dd = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
                        break;
                    }
                }

@@ -830,7 +853,7 @@ extends Layout
            }
        }

        // dump(chdirs, n, "final");
        // dump(chInfo, n, "final");

        // extra: enforce that all tabs and surrogate characters go the
        // primary direction
@@ -844,6 +867,26 @@ extends Layout
            }
        }
        
        // Deal specifically with special operators (like '+',etc.) ahead of numbers/english inside RTL paragraphs
        for (int j = 0; j < n; j++) {
            switch(chs[j]) {
            case '+':
            // For the following chars it is logical to apply the fix, but it appears
            // it customary only for the "+" and we need to behave similarly to other devices:
            //case '*':
            //case '/':
            //case '@':
            //case '#':
            //case '$':
            //case '%':
            //case '^':
            //case '&':
            //case '_':
            //case '\\':
                chInfo[j] = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
            }
        }

        return dir;
    }

@@ -1268,7 +1311,8 @@ extends Layout
    }

    public int getParagraphDirection(int line) {
        return Directions.baseDirection(mLineDirections[line],getLineEnd(line)-getLineStart(line));
        // LTR unless paragraph contains RTL chars (anywhere)
        return mLineDirections[line].hasRTL() ? DIR_RIGHT_TO_LEFT : DIR_LEFT_TO_RIGHT;
    }

    public boolean getLineContainsTab(int line) {
+70 −96
Original line number Diff line number Diff line
@@ -1267,104 +1267,78 @@ public class Canvas {
        }
        return hasBidi;
    }
    /** @hide */
    private static boolean isPunctuation(char c) {
        return c<='\u002f' || c=='\u0040' || (c>'\u005a' && c<='\u0060') || (c>'\u007a' && c<='\u00BF');
    }
    /** @hide */
    private static boolean isRTL(char c) {
        return c>=FIRST_RIGHT_TO_LEFT && c<=LAST_RIGHT_TO_LEFT;
    }
    /**
    * A lightweight BiDi processing to make all draw text work with RTL languages.
    * written from scratch by David Kohen (kohen dot d at gmail dot com) - 2010
    * @hide 
    **/
    public static char[] bidiProcess(char[] text,int start,int srcCount) {

        boolean hasBidi=false;
        char[] destCharArray=new char[srcCount];

    	char[] buf = TemporaryBuffer.obtain(srcCount);
        System.arraycopy(text,start, buf, 0, srcCount);

        // I'm doing the processing from the end of the string, since it worked well this way.
        int count=0,srcIndex=0;
        boolean rtlMode=true;
        for (int i=0;i<srcCount;i++){
            srcIndex=srcCount-1-i;
            if (buf[srcIndex]>=FIRST_RIGHT_TO_LEFT&&buf[srcIndex]<=LAST_RIGHT_TO_LEFT){
                destCharArray[i]=buf[srcIndex];
                // In rtl mode I'm mirroring glyphs.
                rtlMode=true;
            }
            else {
                srcIndex=srcCount-1-i;
                if (count==0) {
                    // Direction neutral characters
                    if (buf[srcIndex]<='\u002f' ||
                        (buf[srcIndex]>'\u0039' && buf[srcIndex]<='\u0040') ||
                        (buf[srcIndex]>'\u005a' && buf[srcIndex]<='\u0060')||
                        (buf[srcIndex]>'\u007a' && buf[srcIndex]<='\u00BF')) {

                        if (rtlMode){
                            switch (buf[srcIndex]) {
    private static char reverseParen(char c) {
        switch (c) {
        case '[':
                                destCharArray[i]=']';
            c=']';
            break;
        case ']':
                                destCharArray[i]='[';
            c='[';
            break;
        case '}':
                                destCharArray[i]='{';
            c='{';
            break;
        case '{':
                                destCharArray[i]='}';
            c='}';
            break;
        case '(':
                                destCharArray[i]=')';
            c=')';
            break;
        case ')':
                                destCharArray[i]='(';
            c='(';
            break;
        case '>':
                                destCharArray[i]='<';
            c='<';
            break;
        case '<':
                                destCharArray[i]='>';
                                break;
                            default:
                                destCharArray[i]=buf[srcIndex];
            c='>';
            break;
        }
                        } else destCharArray[i]=buf[srcIndex];
                    } else {
                        // Handling LTR embedded strings.
                        while (((srcIndex-count)>=0)&&((buf[srcIndex-count]<FIRST_RIGHT_TO_LEFT)||(buf[srcIndex-count]>LAST_RIGHT_TO_LEFT))){
                            count++;
                        }
                        int index=0;
                        int punctuationMarks=0;

                        // Handling direction neutral characters in the middle of LTR
                        while (count>0 && (srcIndex-(count)>=0) &&
                                (buf[srcIndex-(count-1)]<='\u002f' ||
                                        (buf[srcIndex-(count-1)]>'\u0039' && buf[srcIndex-(count-1)]<='\u0040') ||
                                        (buf[srcIndex-(count-1)]>'\u005a' && buf[srcIndex-(count-1)]<='\u0060')||
                                        (buf[srcIndex-(count-1)]>'\u007a' && buf[srcIndex-(count-1)]<='\u00BF'))){
                            destCharArray[i+(count-1)]=buf[srcIndex-(count-1)];
                            count--;
                            punctuationMarks++;
                        }

                        while (count>0){
                            destCharArray[i+index]=buf[srcIndex-(count-1)];
                            count--;
                            index++;
                        }
                        count=index+punctuationMarks-1;
        return c;
    }
    /** @hide */
    public static char[] bidiProcess(char[] text,int start,int count) {
        String cut=new String(text,start,count);
        char[] tt=new char[count];
        cut.getChars(0, count, tt, 0);
        boolean hasRTL=false;
        for (int ii=0; ii<count; ++ii)
            if (isRTL(tt[ii])) {
                hasRTL = true;
                break;
            }
                else {
                    // Avoiding spaghetti code and mangling of loop counter 
                    count--;
        if (hasRTL) {
            char[] rev=new char[count];
            for(int ii=0; ii<count; ++ii)
                rev[ii] = tt[count-ii-1];
            // now copy reverse back over tt, but reverse again (fixing) any non-RTL sequences:
            for(int ii=0; ii<count; ) {
                if (isRTL(rev[ii]) || isPunctuation(rev[ii])) {
                    tt[ii] = reverseParen(rev[ii]);
                    ++ii;
                } else {
                    int end=ii+1;
                    while (end<count && !isRTL(rev[end]) && !isPunctuation(rev[end]))
                        ++end;
                    int jj=end;
                    while (ii<end)
                        tt[ii++] = rev[--jj];
                }
                rtlMode=false;
            }
        }
        return destCharArray;
        return tt;
    }

    /** @hide **/