Revert "Revert "Refactor TextLine class""

This reverts commit 3c097aa45e1fc8c03e09aea09a0dc3596b82c761.

Reason for revert: we thought this CL caused b/119659731 but apparently it is not.

Change-Id: Id375c4c8f2bffb7c42dc36bc053d536c6ea57a8d
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index e5fd292..c8e0dd2 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -2360,6 +2360,52 @@
         public Directions(int[] dirs) {
             mDirections = dirs;
         }
+
+        /**
+         * Returns number of BiDi runs.
+         *
+         * @hide
+         */
+        public @IntRange(from = 0) int getRunCount() {
+            return mDirections.length / 2;
+        }
+
+        /**
+         * Returns the start offset of the BiDi run.
+         *
+         * @param runIndex the index of the BiDi run
+         * @return the start offset of the BiDi run.
+         * @hide
+         */
+        public @IntRange(from = 0) int getRunStart(@IntRange(from = 0) int runIndex) {
+            return mDirections[runIndex * 2];
+        }
+
+        /**
+         * Returns the length of the BiDi run.
+         *
+         * Note that this method may return too large number due to reducing the number of object
+         * allocations. The too large number means the remaining part is assigned to this run. The
+         * caller must clamp the returned value.
+         *
+         * @param runIndex the index of the BiDi run
+         * @return the length of the BiDi run.
+         * @hide
+         */
+        public @IntRange(from = 0) int getRunLength(@IntRange(from = 0) int runIndex) {
+            return mDirections[runIndex * 2 + 1] & RUN_LENGTH_MASK;
+        }
+
+        /**
+         * Returns true if the BiDi run is RTL.
+         *
+         * @param runIndex the index of the BiDi run
+         * @return true if the BiDi run is RTL.
+         * @hide
+         */
+        public boolean isRunRtl(int runIndex) {
+            return (mDirections[runIndex * 2 + 1] & RUN_RTL_FLAG) != 0;
+        }
     }
 
     /**
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 44dfd11..7b638b4 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -16,6 +16,7 @@
 
 package android.text;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
@@ -51,6 +52,8 @@
 public class TextLine {
     private static final boolean DEBUG = false;
 
+    private static final char TAB_CHAR = '\t';
+
     private TextPaint mPaint;
     @UnsupportedAppUsage
     private CharSequence mText;
@@ -232,6 +235,10 @@
         mEllipsisEnd = ellipsisStart != ellipsisEnd ? ellipsisEnd : 0;
     }
 
+    private char charAt(int i) {
+        return mCharsValid ? mChars[i] : mText.charAt(i + mStart);
+    }
+
     /**
      * Justify the line to the given width.
      */
@@ -261,51 +268,23 @@
      * @param bottom the bottom of the line
      */
     void draw(Canvas c, float x, int top, int y, int bottom) {
-        if (!mHasTabs) {
-            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
-                drawRun(c, 0, mLen, false, x, top, y, bottom, false);
-                return;
-            }
-            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
-                drawRun(c, 0, mLen, true, x, top, y, bottom, false);
-                return;
-            }
-        }
-
         float h = 0;
-        int[] runs = mDirections.mDirections;
+        final int runCount = mDirections.getRunCount();
+        for (int runIndex = 0; runIndex < runCount; runIndex++) {
+            final int runStart = mDirections.getRunStart(runIndex);
+            final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+            final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
-        int lastRunIndex = runs.length - 2;
-        for (int i = 0; i < runs.length; i += 2) {
-            int runStart = runs[i];
-            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
-            if (runLimit > mLen) {
-                runLimit = mLen;
-            }
-            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
-
-            int segstart = runStart;
+            int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
-                int codept = 0;
-                if (mHasTabs && j < runLimit) {
-                    codept = mChars[j];
-                    if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
-                        codept = Character.codePointAt(mChars, j);
-                        if (codept > 0xFFFF) {
-                            ++j;
-                            continue;
-                        }
-                    }
-                }
+                if (j == runLimit || charAt(j) == TAB_CHAR) {
+                    h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
+                            runIndex != (runCount - 1) || j != mLen);
 
-                if (j == runLimit || codept == '\t') {
-                    h += drawRun(c, segstart, j, runIsRtl, x+h, top, y, bottom,
-                            i != lastRunIndex || j != mLen);
-
-                    if (codept == '\t') {
+                    if (j != runLimit) {  // charAt(j) == TAB_CHAR
                         h = mDir * nextTab(h * mDir);
                     }
-                    segstart = j + 1;
+                    segStart = j + 1;
                 }
             }
         }
@@ -323,75 +302,81 @@
     }
 
     /**
-     * Returns information about a position on the line.
+     * Returns the signed graphical offset from the leading margin.
      *
-     * @param offset the line-relative character offset, between 0 and the
-     * line length, inclusive
-     * @param trailing true to measure the trailing edge of the character
-     * before offset, false to measure the leading edge of the character
-     * at offset.
-     * @param fmi receives metrics information about the requested
-     * character, can be null.
-     * @return the signed offset from the leading margin to the requested
-     * character edge.
+     * Following examples are all for measuring offset=3. LX(e.g. L0, L1, ...) denotes a
+     * character which has LTR BiDi property. On the other hand, RX(e.g. R0, R1, ...) denotes a
+     * character which has RTL BiDi property. Assuming all character has 1em width.
+     *
+     * Example 1: All LTR chars within LTR context
+     *   Input Text (logical)  :   L0 L1 L2 L3 L4 L5 L6 L7 L8
+     *   Input Text (visual)   :   L0 L1 L2 L3 L4 L5 L6 L7 L8
+     *   Output(trailing=true) :  |--------| (Returns 3em)
+     *   Output(trailing=false):  |--------| (Returns 3em)
+     *
+     * Example 2: All RTL chars within RTL context.
+     *   Input Text (logical)  :   R0 R1 R2 R3 R4 R5 R6 R7 R8
+     *   Input Text (visual)   :   R8 R7 R6 R5 R4 R3 R2 R1 R0
+     *   Output(trailing=true) :                    |--------| (Returns -3em)
+     *   Output(trailing=false):                    |--------| (Returns -3em)
+     *
+     * Example 3: BiDi chars within LTR context.
+     *   Input Text (logical)  :   L0 L1 L2 R3 R4 R5 L6 L7 L8
+     *   Input Text (visual)   :   L0 L1 L2 R5 R4 R3 L6 L7 L8
+     *   Output(trailing=true) :  |-----------------| (Returns 6em)
+     *   Output(trailing=false):  |--------| (Returns 3em)
+     *
+     * Example 4: BiDi chars within RTL context.
+     *   Input Text (logical)  :   L0 L1 L2 R3 R4 R5 L6 L7 L8
+     *   Input Text (visual)   :   L6 L7 L8 R5 R4 R3 L0 L1 L2
+     *   Output(trailing=true) :           |-----------------| (Returns -6em)
+     *   Output(trailing=false):                    |--------| (Returns -3em)
+     *
+     * @param offset the line-relative character offset, between 0 and the line length, inclusive
+     * @param trailing no effect if the offset is not on the BiDi transition offset. If the offset
+     *                 is on the BiDi transition offset and true is passed, the offset is regarded
+     *                 as the edge of the trailing run's edge. If false, the offset is regarded as
+     *                 the edge of the preceding run's edge. See example above.
+     * @param fmi receives metrics information about the requested character, can be null
+     * @return the signed graphical offset from the leading margin to the requested character edge.
+     *         The positive value means the offset is right from the leading edge. The negative
+     *         value means the offset is left from the leading edge.
      */
-    public float measure(int offset, boolean trailing, FontMetricsInt fmi) {
-        int target = trailing ? offset - 1 : offset;
+    public float measure(@IntRange(from = 0) int offset, boolean trailing,
+            @NonNull FontMetricsInt fmi) {
+        if (offset > mLen) {
+            throw new IndexOutOfBoundsException(
+                    "offset(" + offset + ") should be less than line limit(" + mLen + ")");
+        }
+        final int target = trailing ? offset - 1 : offset;
         if (target < 0) {
             return 0;
         }
 
         float h = 0;
+        for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+            final int runStart = mDirections.getRunStart(runIndex);
+            final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+            final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
-        if (!mHasTabs) {
-            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
-                return measureRun(0, offset, mLen, false, fmi);
-            }
-            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
-                return measureRun(0, offset, mLen, true, fmi);
-            }
-        }
-
-        char[] chars = mChars;
-        int[] runs = mDirections.mDirections;
-        for (int i = 0; i < runs.length; i += 2) {
-            int runStart = runs[i];
-            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
-            if (runLimit > mLen) {
-                runLimit = mLen;
-            }
-            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
-
-            int segstart = runStart;
+            int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
-                int codept = 0;
-                if (mHasTabs && j < runLimit) {
-                    codept = chars[j];
-                    if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
-                        codept = Character.codePointAt(chars, j);
-                        if (codept > 0xFFFF) {
-                            ++j;
-                            continue;
-                        }
-                    }
-                }
+                if (j == runLimit || charAt(j) == TAB_CHAR) {
+                    final boolean targetIsInThisSegment = target >= segStart && target < j;
+                    final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
 
-                if (j == runLimit || codept == '\t') {
-                    boolean inSegment = target >= segstart && target < j;
-
-                    boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
-                    if (inSegment && advance) {
-                        return h + measureRun(segstart, offset, j, runIsRtl, fmi);
+                    if (targetIsInThisSegment && sameDirection) {
+                        return h + measureRun(segStart, offset, j, runIsRtl, fmi);
                     }
 
-                    float w = measureRun(segstart, j, j, runIsRtl, fmi);
-                    h += advance ? w : -w;
+                    final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi);
+                    h += sameDirection ? segmentWidth : -segmentWidth;
 
-                    if (inSegment) {
-                        return h + measureRun(segstart, offset, j, runIsRtl, null);
+                    if (targetIsInThisSegment) {
+                        return h + measureRun(segStart, offset, j, runIsRtl, null);
                     }
 
-                    if (codept == '\t') {
+                    if (j != runLimit) {  // charAt(j) == TAB_CHAR
                         if (offset == j) {
                             return h;
                         }
@@ -401,7 +386,7 @@
                         }
                     }
 
-                    segstart = j + 1;
+                    segStart = j + 1;
                 }
             }
         }
@@ -426,62 +411,29 @@
         }
 
         float h = 0;
+        for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+            final int runStart = mDirections.getRunStart(runIndex);
+            final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+            final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
-        if (!mHasTabs) {
-            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
-                for (int offset = 0; offset <= mLen; ++offset) {
-                    measurement[offset] = measureRun(0, offset, mLen, false, fmi);
-                }
-                return measurement;
-            }
-            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
-                for (int offset = 0; offset <= mLen; ++offset) {
-                    measurement[offset] = measureRun(0, offset, mLen, true, fmi);
-                }
-                return measurement;
-            }
-        }
-
-        char[] chars = mChars;
-        int[] runs = mDirections.mDirections;
-        for (int i = 0; i < runs.length; i += 2) {
-            int runStart = runs[i];
-            int runLimit = runStart + (runs[i + 1] & Layout.RUN_LENGTH_MASK);
-            if (runLimit > mLen) {
-                runLimit = mLen;
-            }
-            boolean runIsRtl = (runs[i + 1] & Layout.RUN_RTL_FLAG) != 0;
-
-            int segstart = runStart;
+            int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; ++j) {
-                int codept = 0;
-                if (mHasTabs && j < runLimit) {
-                    codept = chars[j];
-                    if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
-                        codept = Character.codePointAt(chars, j);
-                        if (codept > 0xFFFF) {
-                            ++j;
-                            continue;
-                        }
-                    }
-                }
-
-                if (j == runLimit || codept == '\t') {
-                    float oldh = h;
-                    boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
-                    float w = measureRun(segstart, j, j, runIsRtl, fmi);
+                if (j == runLimit || charAt(j) == TAB_CHAR) {
+                    final  float oldh = h;
+                    final boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
+                    final float w = measureRun(segStart, j, j, runIsRtl, fmi);
                     h += advance ? w : -w;
 
-                    float baseh = advance ? oldh : h;
+                    final float baseh = advance ? oldh : h;
                     FontMetricsInt crtfmi = advance ? fmi : null;
-                    for (int offset = segstart; offset <= j && offset <= mLen; ++offset) {
-                        if (target[offset] >= segstart && target[offset] < j) {
+                    for (int offset = segStart; offset <= j && offset <= mLen; ++offset) {
+                        if (target[offset] >= segStart && target[offset] < j) {
                             measurement[offset] =
-                                    baseh + measureRun(segstart, offset, j, runIsRtl, crtfmi);
+                                    baseh + measureRun(segStart, offset, j, runIsRtl, crtfmi);
                         }
                     }
 
-                    if (codept == '\t') {
+                    if (j != runLimit) {  // charAt(j) == TAB_CHAR
                         if (target[j] == j) {
                             measurement[j] = h;
                         }
@@ -491,7 +443,7 @@
                         }
                     }
 
-                    segstart = j + 1;
+                    segStart = j + 1;
                 }
             }
         }