Enable nested bidi levels in a paragraph.

Changes the internal representation of direction information in the Directions object to be a visually-ordered list of start/length+direction pairs instead of a list of directionality inversion offsets.

Rewrite Layout.getOffsetToLeft/RightOf to use run information instead of width metrics.

Remove java Bidi, use native.  Switch bidi tests to test native, expect levels instead of dirs.

Add test of directionality.  Leave in switch to turn new code off and restore previous behavior for now.

Change-Id: Iea8bb46c678a18820e237c90f76007a084c83051
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 38ac9b7..44f3572 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -16,25 +16,31 @@
 
 package android.text;
 
+import com.android.internal.util.ArrayUtils;
+
 import android.emoji.EmojiFactory;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Path;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.graphics.Path;
-import com.android.internal.util.ArrayUtils;
-
-import junit.framework.Assert;
-import android.text.style.*;
 import android.text.method.TextKeyListener;
+import android.text.style.AlignmentSpan;
+import android.text.style.LeadingMarginSpan;
+import android.text.style.LineBackgroundSpan;
+import android.text.style.ParagraphStyle;
+import android.text.style.ReplacementSpan;
+import android.text.style.TabStopSpan;
 import android.view.KeyEvent;
 
+import junit.framework.Assert;
+
 /**
- * A base class that manages text layout in visual elements on 
- * the screen. 
- * <p>For text that will be edited, use a {@link DynamicLayout}, 
- * which will be updated as the text changes.  
+ * A base class that manages text layout in visual elements on
+ * the screen.
+ * <p>For text that will be edited, use a {@link DynamicLayout},
+ * which will be updated as the text changes.
  * For text that will not change, use a {@link StaticLayout}.
  */
 public abstract class Layout {
@@ -66,7 +72,7 @@
                                         TextPaint paint) {
         return getDesiredWidth(source, 0, source.length(), paint);
     }
-    
+
     /**
      * Return how wide a layout must be in order to display the
      * specified text slice with one line per paragraph.
@@ -185,13 +191,13 @@
         if (dbottom < bottom) {
             bottom = dbottom;
         }
-        
-        int first = getLineForVertical(top); 
+
+        int first = getLineForVertical(top);
         int last = getLineForVertical(bottom);
-        
+
         int previousLineBottom = getLineTop(first);
         int previousLineEnd = getLineStart(first);
-        
+
         TextPaint paint = mPaint;
         CharSequence buf = mText;
         int width = mWidth;
@@ -238,7 +244,7 @@
             previousLineBottom = getLineTop(first);
             previousLineEnd = getLineStart(first);
             spans = NO_PARA_SPANS;
-        } 
+        }
 
         // There can be a highlight even without spans if we are drawing
         // a non-spanned transformation of a spanned editing buffer.
@@ -255,7 +261,7 @@
         }
 
         Alignment align = mAlignment;
-        
+
         // Next draw the lines, one at a time.
         // the baseline is the top of the following line minus the current
         // line's descent.
@@ -271,7 +277,7 @@
             int lbaseline = lbottom - getLineDescent(i);
 
             boolean isFirstParaLine = false;
-            if (spannedText) { 
+            if (spannedText) {
                 if (start == 0 || buf.charAt(start - 1) == '\n') {
                     isFirstParaLine = true;
                 }
@@ -282,7 +288,7 @@
                     spanend = sp.nextSpanTransition(start, textLength,
                                                     ParagraphStyle.class);
                     spans = sp.getSpans(start, spanend, ParagraphStyle.class);
-                    
+
                     align = mAlignment;
                     for (int n = spans.length-1; n >= 0; n--) {
                         if (spans[n] instanceof AlignmentSpan) {
@@ -292,7 +298,7 @@
                     }
                 }
             }
-            
+
             int dir = getParagraphDirection(i);
             int left = 0;
             int right = mWidth;
@@ -309,7 +315,7 @@
                             margin.drawLeadingMargin(c, paint, right, dir, ltop,
                                                      lbaseline, lbottom, buf,
                                                      start, end, isFirstParaLine, this);
-                                
+
                             right -= margin.getLeadingMargin(isFirstParaLine);
                         } else {
                             margin.drawLeadingMargin(c, paint, left, dir, ltop,
@@ -417,7 +423,7 @@
 
         mWidth = wid;
     }
-    
+
     /**
      * Return the total height of this layout.
      */
@@ -450,7 +456,7 @@
      * Return the number of lines of text in this layout.
      */
     public abstract int getLineCount();
-    
+
     /**
      * Return the baseline for the specified line (0&hellip;getLineCount() - 1)
      * If bounds is not null, return the top, left, right, bottom extents
@@ -524,13 +530,85 @@
      */
     public abstract int getBottomPadding();
 
+    // return the level of the character at offset.
+    // XXX remove if not needed
+    private int getRunLevelAtOffset(int offset) {
+        int line = getLineForOffset(offset);
+        int lineStart = getLineStart(line);
+        int lineEnd = getLineVisibleEnd(line);
+        int[] runs = getLineDirections(line).mDirections;
+        for (int i = 0; i < runs.length; i += 2) {
+            int start = runs[i];
+            if (offset >= start) {
+               int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
+               if (limit > lineEnd) {
+                   limit = lineEnd;
+               }
+               if (offset < limit) {
+                   return (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
+               }
+            }
+        }
+        return getParagraphDirection(line) == 1 ? 0 : 1;
+    }
+
+    private boolean primaryIsTrailingPrevious(int offset) {
+        int line = getLineForOffset(offset);
+        int lineStart = getLineStart(line);
+        int lineEnd = getLineVisibleEnd(line);
+        int[] runs = getLineDirections(line).mDirections;
+
+        int levelAt = -1;
+        for (int i = 0; i < runs.length; i += 2) {
+            int start = lineStart + runs[i];
+            int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
+            if (limit > lineEnd) {
+                limit = lineEnd;
+            }
+            if (offset >= start && offset < limit) {
+                if (offset > start) {
+                    // Previous character is at same level, so don't use trailing.
+                    return false;
+                }
+                levelAt = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
+                break;
+            }
+        }
+        if (levelAt == -1) {
+            // Offset was limit of line.
+            levelAt = getParagraphDirection(line) == 1 ? 0 : 1;
+        }
+
+        // At level boundary, check previous level.
+        int levelBefore = -1;
+        if (offset == lineStart) {
+            levelBefore = getParagraphDirection(line) == 1 ? 0 : 1;
+        } else {
+            offset -= 1;
+            for (int i = 0; i < runs.length; i += 2) {
+                int start = lineStart + runs[i];
+                int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
+                if (limit > lineEnd) {
+                    limit = lineEnd;
+                }
+                if (offset >= start && offset < limit) {
+                    levelBefore = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
+                    break;
+                }
+            }
+        }
+
+        return levelBefore < levelAt;
+    }
+
     /**
      * Get the primary horizontal position for the specified text offset.
      * This is the location where a new character would be inserted in
      * the paragraph's primary direction.
      */
     public float getPrimaryHorizontal(int offset) {
-        return getHorizontal(offset, false, true);
+        boolean trailing = primaryIsTrailingPrevious(offset);
+        return getHorizontal(offset, trailing);
     }
 
     /**
@@ -539,17 +617,17 @@
      * the direction other than the paragraph's primary direction.
      */
     public float getSecondaryHorizontal(int offset) {
-        return getHorizontal(offset, true, true);
+        boolean trailing = primaryIsTrailingPrevious(offset);
+        return getHorizontal(offset, !trailing);
     }
 
-    private float getHorizontal(int offset, boolean trailing, boolean alt) {
+    private float getHorizontal(int offset, boolean trailing) {
         int line = getLineForOffset(offset);
 
-        return getHorizontal(offset, trailing, alt, line);
+        return getHorizontal(offset, trailing, line);
     }
 
-    private float getHorizontal(int offset, boolean trailing, boolean alt,
-                                int line) {
+    private float getHorizontal(int offset, boolean trailing, int line) {
         int start = getLineStart(line);
         int end = getLineVisibleEnd(line);
         int dir = getParagraphDirection(line);
@@ -562,7 +640,7 @@
         }
 
         float wid = measureText(mPaint, mWorkPaint, mText, start, offset, end,
-                                dir, directions, trailing, alt, tab, tabs);
+                                dir, directions, trailing, tab, tabs);
 
         if (offset > end) {
             if (dir == DIR_RIGHT_TO_LEFT)
@@ -679,7 +757,7 @@
             end = getLineEnd(line);
         } else {
             end = getLineVisibleEnd(line);
-        } 
+        }
         boolean tab = getLineContainsTab(line);
 
         if (tabs == null && tab && mText instanceof Spanned) {
@@ -738,7 +816,7 @@
     }
 
     /**
-     * Get the character offset on the specfied line whose position is
+     * Get the character offset on the specified line whose position is
      * closest to the specified horizontal position.
      */
     public int getOffsetForHorizontal(int line, float horiz) {
@@ -752,14 +830,13 @@
         int best = min;
         float bestdist = Math.abs(getPrimaryHorizontal(best) - horiz);
 
-        int here = min;
-        for (int i = 0; i < dirs.mDirections.length; i++) {
-            int there = here + dirs.mDirections[i];
-            int swap = ((i & 1) == 0) ? 1 : -1;
+        for (int i = 0; i < dirs.mDirections.length; i += 2) {
+            int here = min + dirs.mDirections[i];
+            int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
+            int swap = (dirs.mDirections[i+1] & RUN_RTL_FLAG) != 0 ? -1 : 1;
 
             if (there > max)
                 there = max;
-
             int high = there - 1 + 1, low = here + 1 - 1, guess;
 
             while (high - low > 1) {
@@ -792,7 +869,7 @@
 
                 if (dist < bestdist) {
                     bestdist = dist;
-                    best = low;   
+                    best = low;
                 }
             }
 
@@ -802,8 +879,6 @@
                 bestdist = dist;
                 best = here;
             }
-
-            here = there;
         }
 
         float dist = Math.abs(getPrimaryHorizontal(max) - horiz);
@@ -823,14 +898,14 @@
         return getLineStart(line + 1);
     }
 
-    /** 
+    /**
      * Return the text offset after the last visible character (so whitespace
      * is not counted) on the specified line.
      */
     public int getLineVisibleEnd(int line) {
         return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1));
     }
-    
+
     private int getLineVisibleEnd(int line, int start, int end) {
         if (DEBUG) {
             Assert.assertTrue(getLineStart(line) == start && getLineStart(line+1) == end);
@@ -882,207 +957,178 @@
         return getLineTop(line) - (getLineTop(line+1) - getLineDescent(line));
     }
 
-    /**
-     * Return the text offset that would be reached by moving left
-     * (possibly onto another line) from the specified offset.
-     */
     public int getOffsetToLeftOf(int offset) {
-        int line = getLineForOffset(offset);
-        int start = getLineStart(line);
-        int end = getLineEnd(line);
-        Directions dirs = getLineDirections(line);
-
-        if (line != getLineCount() - 1)
-            end--;
-
-        float horiz = getPrimaryHorizontal(offset);
-
-        int best = offset;
-        float besth = Integer.MIN_VALUE;
-        int candidate;
-
-        candidate = TextUtils.getOffsetBefore(mText, offset);
-        if (candidate >= start && candidate <= end) {
-            float h = getPrimaryHorizontal(candidate);
-
-            if (h < horiz && h > besth) {
-                best = candidate;
-                besth = h;
-            }
-        }
-
-        candidate = TextUtils.getOffsetAfter(mText, offset);
-        if (candidate >= start && candidate <= end) {
-            float h = getPrimaryHorizontal(candidate);
-
-            if (h < horiz && h > besth) {
-                best = candidate;
-                besth = h;
-            }
-        }
-
-        int here = start;
-        for (int i = 0; i < dirs.mDirections.length; i++) {
-            int there = here + dirs.mDirections[i];
-            if (there > end)
-                there = end;
-
-            float h = getPrimaryHorizontal(here);
-
-            if (h < horiz && h > besth) {
-                best = here;
-                besth = h;
-            }
-
-            candidate = TextUtils.getOffsetAfter(mText, here);
-            if (candidate >= start && candidate <= end) {
-                h = getPrimaryHorizontal(candidate);
-
-                if (h < horiz && h > besth) {
-                    best = candidate;
-                    besth = h;
-                }
-            }
-
-            candidate = TextUtils.getOffsetBefore(mText, there);
-            if (candidate >= start && candidate <= end) {
-                h = getPrimaryHorizontal(candidate);
-
-                if (h < horiz && h > besth) {
-                    best = candidate;
-                    besth = h;
-                }
-            }
-
-            here = there;
-        }
-
-        float h = getPrimaryHorizontal(end);
-
-        if (h < horiz && h > besth) {
-            best = end;
-            besth = h;
-        }
-
-        if (best != offset)
-            return best;
-
-        int dir = getParagraphDirection(line);
-
-        if (dir > 0) {
-            if (line == 0)
-                return best;
-            else
-                return getOffsetForHorizontal(line - 1, 10000);
-        } else {
-            if (line == getLineCount() - 1)
-                return best;
-            else
-                return getOffsetForHorizontal(line + 1, 10000);
-        }
+        return getOffsetToLeftRightOf(offset, true);
     }
 
-    /**
-     * Return the text offset that would be reached by moving right
-     * (possibly onto another line) from the specified offset.
-     */
     public int getOffsetToRightOf(int offset) {
-        int line = getLineForOffset(offset);
-        int start = getLineStart(line);
-        int end = getLineEnd(line);
-        Directions dirs = getLineDirections(line);
+        return getOffsetToLeftRightOf(offset, false);
+    }
 
-        if (line != getLineCount() - 1)
-            end--;
+    // 1) The caret marks the leading edge of a character. The character
+    // logically before it might be on a different level, and the active caret
+    // position is on the character at the lower level. If that character
+    // was the previous character, the caret is on its trailing edge.
+    // 2) Take this character/edge and move it in the indicated direction.
+    // This gives you a new character and a new edge.
+    // 3) This position is between two visually adjacent characters.  One of
+    // these might be at a lower level.  The active position is on the
+    // character at the lower level.
+    // 4) If the active position is on the trailing edge of the character,
+    // the new caret position is the following logical character, else it
+    // is the character.
+    private int getOffsetToLeftRightOf(int caret, boolean toLeft) {
+        int line = getLineForOffset(caret);
+        int lineStart = getLineStart(line);
+        int lineEnd = getLineEnd(line);
 
-        float horiz = getPrimaryHorizontal(offset);
+        boolean paraIsRtl = getParagraphDirection(line) == -1;
+        int[] runs = getLineDirections(line).mDirections;
 
-        int best = offset;
-        float besth = Integer.MAX_VALUE;
-        int candidate;
+        int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1;
+        boolean trailing = false;
 
-        candidate = TextUtils.getOffsetBefore(mText, offset);
-        if (candidate >= start && candidate <= end) {
-            float h = getPrimaryHorizontal(candidate);
-
-            if (h > horiz && h < besth) {
-                best = candidate;
-                besth = h;
-            }
-        }
-
-        candidate = TextUtils.getOffsetAfter(mText, offset);
-        if (candidate >= start && candidate <= end) {
-            float h = getPrimaryHorizontal(candidate);
-
-            if (h > horiz && h < besth) {
-                best = candidate;
-                besth = h;
-            }
-        }
-
-        int here = start;
-        for (int i = 0; i < dirs.mDirections.length; i++) {
-            int there = here + dirs.mDirections[i];
-            if (there > end)
-                there = end;
-
-            float h = getPrimaryHorizontal(here);
-
-            if (h > horiz && h < besth) {
-                best = here;
-                besth = h;
-            }
-
-            candidate = TextUtils.getOffsetAfter(mText, here);
-            if (candidate >= start && candidate <= end) {
-                h = getPrimaryHorizontal(candidate);
-
-                if (h > horiz && h < besth) {
-                    best = candidate;
-                    besth = h;
-                }
-            }
-
-            candidate = TextUtils.getOffsetBefore(mText, there);
-            if (candidate >= start && candidate <= end) {
-                h = getPrimaryHorizontal(candidate);
-
-                if (h > horiz && h < besth) {
-                    best = candidate;
-                    besth = h;
-                }
-            }
-
-            here = there;
-        }
-
-        float h = getPrimaryHorizontal(end);
-
-        if (h > horiz && h < besth) {
-            best = end;
-            besth = h;
-        }
-
-        if (best != offset)
-            return best;
-
-        int dir = getParagraphDirection(line);
-
-        if (dir > 0) {
-            if (line == getLineCount() - 1)
-                return best;
-            else
-                return getOffsetForHorizontal(line + 1, -10000);
+        if (caret == lineStart) {
+            runIndex = -2;
+        } else if (caret == lineEnd) {
+            runIndex = runs.length;
         } else {
-            if (line == 0)
-                return best;
-            else
-                return getOffsetForHorizontal(line - 1, -10000);
+          // First, get information about the run containing the character with
+          // the active caret.
+          for (runIndex = 0; runIndex < runs.length; runIndex += 2) {
+            runStart = lineStart + runs[runIndex];
+            if (caret >= runStart) {
+              runLimit = runStart + (runs[runIndex+1] & RUN_LENGTH_MASK);
+              if (runLimit > lineEnd) {
+                  runLimit = lineEnd;
+              }
+              if (caret < runLimit) {
+                runLevel = (runs[runIndex+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
+                if (caret == runStart) {
+                  // The caret is on a run boundary, see if we should
+                  // use the position on the trailing edge of the previous
+                  // logical character instead.
+                  int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit;
+                  int pos = caret - 1;
+                  for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) {
+                    prevRunStart = lineStart + runs[prevRunIndex];
+                    if (pos >= prevRunStart) {
+                      prevRunLimit = prevRunStart + (runs[prevRunIndex+1] & RUN_LENGTH_MASK);
+                      if (prevRunLimit > lineEnd) {
+                          prevRunLimit = lineEnd;
+                      }
+                      if (pos < prevRunLimit) {
+                        prevRunLevel = (runs[prevRunIndex+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
+                        if (prevRunLevel < runLevel) {
+                          // Start from logically previous character.
+                          runIndex = prevRunIndex;
+                          runLevel = prevRunLevel;
+                          runStart = prevRunStart;
+                          runLimit = prevRunLimit;
+                          trailing = true;
+                          break;
+                        }
+                      }
+                    }
+                  }
+                }
+                break;
+              }
+            }
+          }
+
+          // caret might be = lineEnd.  This is generally a space or paragraph
+          // separator and has an associated run, but might be the end of
+          // text, in which case it doesn't.  If that happens, we ran off the
+          // end of the run list, and runIndex == runs.length.  In this case,
+          // we are at a run boundary so we skip the below test.
+          if (runIndex != runs.length) {
+              boolean rtlRun = (runLevel & 0x1) != 0;
+              boolean advance = toLeft == rtlRun;
+              if (caret != (advance ? runLimit : runStart) || advance != trailing) {
+                  // Moving within or into the run, so we can move logically.
+                  newCaret = getOffsetBeforeAfter(caret, advance);
+                  // If the new position is internal to the run, we're at the strong
+                  // position already so we're finished.
+                  if (newCaret != (advance ? runLimit : runStart)) {
+                      return newCaret;
+                  }
+              }
+          }
         }
+
+        // If newCaret is -1, we're starting at a run boundary and crossing
+        // into another run. Otherwise we've arrived at a run boundary, and
+        // need to figure out which character to attach to.  Note we might
+        // need to run this twice, if we cross a run boundary and end up at
+        // another run boundary.
+        while (true) {
+          boolean advance = toLeft == paraIsRtl;
+          int otherRunIndex = runIndex + (advance ? 2 : -2);
+          if (otherRunIndex >= 0 && otherRunIndex < runs.length) {
+            int otherRunStart = lineStart + runs[otherRunIndex];
+            int otherRunLimit = otherRunStart + (runs[otherRunIndex+1] & RUN_LENGTH_MASK);
+            if (otherRunLimit > lineEnd) {
+                otherRunLimit = lineEnd;
+            }
+            int otherRunLevel = runs[otherRunIndex+1] >>> RUN_LEVEL_SHIFT & RUN_LEVEL_MASK;
+            boolean otherRunIsRtl = (otherRunLevel & 1) != 0;
+
+            advance = toLeft == otherRunIsRtl;
+            if (newCaret == -1) {
+              newCaret = getOffsetBeforeAfter(advance ? otherRunStart : otherRunLimit, advance);
+              if (newCaret == (advance ? otherRunLimit : otherRunStart)) {
+                // Crossed and ended up at a new boundary, repeat a second and final time.
+                runIndex = otherRunIndex;
+                runLevel = otherRunLevel;
+                continue;
+              }
+              break;
+            }
+
+            // The new caret is at a boundary.
+            if (otherRunLevel < runLevel) {
+              // The strong character is in the other run.
+              newCaret = advance ? otherRunStart : otherRunLimit;
+            }
+            break;
+          }
+
+          if (newCaret == -1) {
+              // We're walking off the end of the line.  The paragraph
+              // level is always equal to or lower than any internal level, so
+              // the boundaries get the strong caret.
+              newCaret = getOffsetBeforeAfter(caret, advance);
+              break;
+          }
+          // Else we've arrived at the end of the line.  That's a strong position.
+          // We might have arrived here by crossing over a run with no internal
+          // breaks and dropping out of the above loop before advancing one final
+          // time, so reset the caret.
+          // Note, we use '<=' below to handle a situation where the only run
+          // on the line is a counter-directional run.  If we're not advancing,
+          // we can end up at the 'lineEnd' position but the caret we want is at
+          // the lineStart.
+          if (newCaret <= lineEnd) {
+              newCaret = advance ? lineEnd : lineStart;
+          }
+          break;
+        }
+
+        return newCaret;
+    }
+
+    // utility, maybe just roll into the above.
+    private int getOffsetBeforeAfter(int offset, boolean after) {
+        if (after) {
+            return TextUtils.getOffsetAfter(mText, offset);
+        }
+        return TextUtils.getOffsetBefore(mText, offset);
     }
 
     private int getOffsetAtStartOf(int offset) {
+        // XXX this probably should skip local reorderings and
+        // zero-width characters, look at callers
         if (offset == 0)
             return 0;
 
@@ -1204,9 +1250,10 @@
         if (lineend > linestart && mText.charAt(lineend - 1) == '\n')
             lineend--;
 
-        int here = linestart;
-        for (int i = 0; i < dirs.mDirections.length; i++) {
-            int there = here + dirs.mDirections[i];
+        for (int i = 0; i < dirs.mDirections.length; i += 2) {
+            int here = linestart + dirs.mDirections[i];
+            int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
+
             if (there > lineend)
                 there = lineend;
 
@@ -1215,14 +1262,12 @@
                 int en = Math.min(end, there);
 
                 if (st != en) {
-                    float h1 = getHorizontal(st, false, false, line);
-                    float h2 = getHorizontal(en, true, false, line);
+                    float h1 = getHorizontal(st, false, line);
+                    float h2 = getHorizontal(en, true, line);
 
                     dest.addRect(h1, top, h2, bottom, Path.Direction.CW);
                 }
             }
-
-            here = there;
         }
     }
 
@@ -1257,7 +1302,7 @@
 
             addSelection(startline, start, getLineEnd(startline),
                          top, getLineBottom(startline), dest);
-            
+
             if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT)
                 dest.addRect(getLineLeft(startline), top,
                               0, getLineBottom(startline), Path.Direction.CW);
@@ -1395,27 +1440,31 @@
 
         float h = 0;
 
-        int here = 0;
-        for (int i = 0; i < directions.mDirections.length; i++) {
-            int there = here + directions.mDirections[i];
-            if (there > end - start)
-                there = end - start;
+        int lastRunIndex = directions.mDirections.length - 2;
+        for (int i = 0; i < directions.mDirections.length; i += 2) {
+            int here = start + directions.mDirections[i];
+            int there = here + (directions.mDirections[i+1] & RUN_LENGTH_MASK);
+            boolean runIsRtl = (directions.mDirections[i+1] & RUN_RTL_FLAG) != 0;
+
+            if (there > end)
+                there = end;
 
             int segstart = here;
             for (int j = hasTabs ? here : there; j <= there; j++) {
-                if (j == there || buf[j] == '\t') {
+                int pj = j - start;
+                if (j == there || buf[pj] == '\t') {
                     h += Styled.drawText(canvas, text,
-                                         start + segstart, start + j,
-                                         dir, (i & 1) != 0, x + h,
+                                         segstart, j,
+                                         dir, runIsRtl, x + h,
                                          top, y, bottom, paint, workPaint,
-                                         start + j != end);
+                                         i != lastRunIndex || j != end);
 
-                    if (j != there && buf[j] == '\t')
+                    if (j != there)
                         h = dir * nextTab(text, start, end, h * dir, parspans);
 
                     segstart = j + 1;
-                } else if (hasTabs && buf[j] >= 0xD800 && buf[j] <= 0xDFFF && j + 1 < there) {
-                    int emoji = Character.codePointAt(buf, j);
+                } else if (hasTabs && buf[pj] >= 0xD800 && buf[pj] <= 0xDFFF && j + 1 < there) {
+                    int emoji = Character.codePointAt(buf, pj);
 
                     if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
                         Bitmap bm = EMOJI_FACTORY.
@@ -1423,10 +1472,10 @@
 
                         if (bm != null) {
                             h += Styled.drawText(canvas, text,
-                                                 start + segstart, start + j,
-                                                 dir, (i & 1) != 0, x + h,
+                                                 segstart, j,
+                                                 dir, runIsRtl, x + h,
                                                  top, y, bottom, paint, workPaint,
-                                                 start + j != end);
+                                                 i != lastRunIndex || j != end);
 
                             if (mEmojiRect == null) {
                                 mEmojiRect = new RectF();
@@ -1434,9 +1483,9 @@
 
                             workPaint.set(paint);
                             Styled.measureText(paint, workPaint, text,
-                                               start + j, start + j + 1,
+                                               j, j + 1,
                                                null);
-                                        
+
                             float bitmapHeight = bm.getHeight();
                             float textHeight = -workPaint.ascent();
                             float scale = textHeight / bitmapHeight;
@@ -1454,21 +1503,24 @@
                     }
                 }
             }
-
-            here = there;
         }
 
         if (hasTabs)
             TextUtils.recycle(buf);
     }
 
-    private static float measureText(TextPaint paint,
+    /**
+     * Get the distance from the margin to the requested edge of the character
+     * at offset on the line from start to end.  Trailing indicates the edge
+     * should be the trailing edge of the character at offset-1.
+     */
+    /* package */ static float measureText(TextPaint paint,
                                      TextPaint workPaint,
                                      CharSequence text,
                                      int start, int offset, int end,
                                      int dir, Directions directions,
-                                     boolean trailing, boolean alt,
-                                     boolean hasTabs, Object[] tabs) {
+                                     boolean trailing, boolean hasTabs,
+                                     Object[] tabs) {
         char[] buf = null;
 
         if (hasTabs) {
@@ -1478,19 +1530,19 @@
 
         float h = 0;
 
-        if (alt) {
-            if (dir == DIR_RIGHT_TO_LEFT)
-                trailing = !trailing;
+        int target = trailing ? offset - 1 : offset;
+        if (target < start) {
+            return 0;
         }
 
-        int here = 0;
-        for (int i = 0; i < directions.mDirections.length; i++) {
-            if (alt)
-                trailing = !trailing;
-
-            int there = here + directions.mDirections[i];
-            if (there > end - start)
-                there = end - start;
+        int[] runs = directions.mDirections;
+        for (int i = 0; i < runs.length; i += 2) {
+            int here = start + runs[i];
+            int there = here + (runs[i+1] & RUN_LENGTH_MASK);
+            if (there > end) {
+                there = end;
+            }
+            boolean runIsRtl = (runs[i+1] & RUN_RTL_FLAG) != 0;
 
             int segstart = here;
             for (int j = hasTabs ? here : there; j <= there; j++) {
@@ -1498,11 +1550,11 @@
                 Bitmap bm = null;
 
                 if (hasTabs && j < there) {
-                    codept = buf[j];
+                    codept = buf[j - start];
                 }
 
                 if (codept >= 0xD800 && codept <= 0xDFFF && j + 1 < there) {
-                    codept = Character.codePointAt(buf, j);
+                    codept = Character.codePointAt(buf, j - start);
 
                     if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) {
                         bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
@@ -1512,33 +1564,34 @@
                 if (j == there || codept == '\t' || bm != null) {
                     float segw;
 
-                    if (offset < start + j ||
-                       (trailing && offset <= start + j)) {
-                        if (dir == DIR_LEFT_TO_RIGHT && (i & 1) == 0) {
+                    boolean inSegment = target >= segstart && target < j;
+
+                    if (inSegment) {
+                        if (dir == DIR_LEFT_TO_RIGHT && !runIsRtl) {
                             h += Styled.measureText(paint, workPaint, text,
-                                                    start + segstart, offset,
+                                                    segstart, offset,
                                                     null);
                             return h;
                         }
 
-                        if (dir == DIR_RIGHT_TO_LEFT && (i & 1) != 0) {
+                        if (dir == DIR_RIGHT_TO_LEFT && runIsRtl) {
                             h -= Styled.measureText(paint, workPaint, text,
-                                                    start + segstart, offset,
+                                                    segstart, offset,
                                                     null);
                             return h;
                         }
                     }
 
+                    // XXX Style.measureText assumes LTR?
                     segw = Styled.measureText(paint, workPaint, text,
-                                              start + segstart, start + j,
+                                              segstart, j,
                                               null);
 
-                    if (offset < start + j ||
-                        (trailing && offset <= start + j)) {
+                    if (inSegment) {
                         if (dir == DIR_LEFT_TO_RIGHT) {
                             h += segw - Styled.measureText(paint, workPaint,
                                                            text,
-                                                           start + segstart,
+                                                           segstart,
                                                            offset, null);
                             return h;
                         }
@@ -1546,7 +1599,7 @@
                         if (dir == DIR_RIGHT_TO_LEFT) {
                             h -= segw - Styled.measureText(paint, workPaint,
                                                            text,
-                                                           start + segstart,
+                                                           segstart,
                                                            offset, null);
                             return h;
                         }
@@ -1557,11 +1610,15 @@
                     else
                         h += segw;
 
-                    if (j != there && buf[j] == '\t') {
-                        if (offset == start + j)
+                    if (j != there && buf[j - start] == '\t') {
+                        if (offset == j)
                             return h;
 
                         h = dir * nextTab(text, start, end, h * dir, tabs);
+
+                        if (target == j) {
+                            return h;
+                        }
                     }
 
                     if (bm != null) {
@@ -1569,7 +1626,7 @@
                         Styled.measureText(paint, workPaint, text,
                                            j, j + 2, null);
 
-                        float wid = (float) bm.getWidth() *
+                        float wid = bm.getWidth() *
                                     -workPaint.ascent() / bm.getHeight();
 
                         if (dir == DIR_RIGHT_TO_LEFT) {
@@ -1584,8 +1641,6 @@
                     segstart = j + 1;
                 }
             }
-
-            here = there;
         }
 
         if (hasTabs)
@@ -1616,7 +1671,7 @@
                                            Paint.FontMetricsInt fm,
                                            boolean hasTabs, Object[] tabs) {
         char[] buf = null;
-  
+
         if (hasTabs) {
             buf = TextUtils.obtain(end - start);
             TextUtils.getChars(text, start, end, buf, 0);
@@ -1652,6 +1707,8 @@
             if (pos == len || codept == '\t' || bm != null) {
                 workPaint.baselineShift = 0;
 
+                // XXX Styled.measureText assumes the run direction is LTR,
+                // but it might not be.  Check this.
                 width += Styled.measureText(paint, workPaint, text,
                                         start + lastPos, start + pos,
                                         fm);
@@ -1683,7 +1740,7 @@
                         Styled.measureText(paint, workPaint, text,
                                            start + pos, start + pos + 1, null);
 
-                        width += (float) bm.getWidth() *
+                        width += bm.getWidth() *
                                     -workPaint.ascent() / bm.getHeight();
 
                         // Since we had an emoji, we bump past the second half
@@ -1804,23 +1861,22 @@
 
     /**
      * Stores information about bidirectional (left-to-right or right-to-left)
-     * text within the layout of a line.  TODO: This work is not complete
-     * or correct and will be fleshed out in a later revision.
+     * text within the layout of a line.
      */
     public static class Directions {
-        private short[] mDirections;
+        // Directions represents directional runs within a line of text.
+        // Runs are pairs of ints listed in visual order, starting from the
+        // leading margin.  The first int of each pair is the offset from
+        // the first character of the line to the start of the run.  The
+        // second int represents both the length and level of the run.
+        // The length is in the lower bits, accessed by masking with
+        // DIR_LENGTH_MASK.  The level is in the higher bits, accessed
+        // by shifting by DIR_LEVEL_SHIFT and masking by DIR_LEVEL_MASK.
+        // To simply test for an RTL direction, test the bit using
+        // DIR_RTL_FLAG, if set then the direction is rtl.
 
-        // The values in mDirections are the offsets from the first character
-        // in the line to the next flip in direction.  Runs at even indices
-        // 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) {
+        /* package */ int[] mDirections;
+        /* package */ Directions(int[] dirs) {
             mDirections = dirs;
         }
     }
@@ -1870,7 +1926,7 @@
         public int length() {
             return mText.length();
         }
-    
+
         public CharSequence subSequence(int start, int end) {
             char[] s = new char[end - start];
             getChars(start, end, s, 0);
@@ -1936,12 +1992,17 @@
 
     public static final int DIR_LEFT_TO_RIGHT = 1;
     public static final int DIR_RIGHT_TO_LEFT = -1;
-    
+
     /* package */ static final int DIR_REQUEST_LTR = 1;
     /* package */ static final int DIR_REQUEST_RTL = -1;
     /* package */ static final int DIR_REQUEST_DEFAULT_LTR = 2;
     /* package */ static final int DIR_REQUEST_DEFAULT_RTL = -2;
 
+    /* package */ static final int RUN_LENGTH_MASK = 0x03ffffff;
+    /* package */ static final int RUN_LEVEL_SHIFT = 26;
+    /* package */ static final int RUN_LEVEL_MASK = 0x3f;
+    /* package */ static final int RUN_RTL_FLAG = 1 << RUN_LEVEL_SHIFT;
+
     public enum Alignment {
         ALIGN_NORMAL,
         ALIGN_OPPOSITE,
@@ -1953,9 +2014,8 @@
     private static final int TAB_INCREMENT = 20;
 
     /* package */ static final Directions DIRS_ALL_LEFT_TO_RIGHT =
-                                       new Directions(new short[] { 32767 });
+        new Directions(new int[] { 0, RUN_LENGTH_MASK });
     /* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT =
-                                       new Directions(new short[] { 0, 32767 });
-
+        new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
 }