Home | History | Annotate | Download | only in text
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.text;
     18 
     19 import android.graphics.Bitmap;
     20 import android.graphics.Canvas;
     21 import android.graphics.Paint;
     22 import android.graphics.Paint.FontMetricsInt;
     23 import android.graphics.RectF;
     24 import android.text.Layout.Directions;
     25 import android.text.Layout.TabStops;
     26 import android.text.style.CharacterStyle;
     27 import android.text.style.MetricAffectingSpan;
     28 import android.text.style.ReplacementSpan;
     29 import android.util.Log;
     30 
     31 import com.android.internal.util.ArrayUtils;
     32 
     33 /**
     34  * Represents a line of styled text, for measuring in visual order and
     35  * for rendering.
     36  *
     37  * <p>Get a new instance using obtain(), and when finished with it, return it
     38  * to the pool using recycle().
     39  *
     40  * <p>Call set to prepare the instance for use, then either draw, measure,
     41  * metrics, or caretToLeftRightOf.
     42  *
     43  * @hide
     44  */
     45 class TextLine {
     46     private static final boolean DEBUG = false;
     47 
     48     private TextPaint mPaint;
     49     private CharSequence mText;
     50     private int mStart;
     51     private int mLen;
     52     private int mDir;
     53     private Directions mDirections;
     54     private boolean mHasTabs;
     55     private TabStops mTabs;
     56     private char[] mChars;
     57     private boolean mCharsValid;
     58     private Spanned mSpanned;
     59     private final TextPaint mWorkPaint = new TextPaint();
     60     private final SpanSet<MetricAffectingSpan> mMetricAffectingSpanSpanSet =
     61             new SpanSet<MetricAffectingSpan>(MetricAffectingSpan.class);
     62     private final SpanSet<CharacterStyle> mCharacterStyleSpanSet =
     63             new SpanSet<CharacterStyle>(CharacterStyle.class);
     64     private final SpanSet<ReplacementSpan> mReplacementSpanSpanSet =
     65             new SpanSet<ReplacementSpan>(ReplacementSpan.class);
     66 
     67     private static final TextLine[] sCached = new TextLine[3];
     68 
     69     /**
     70      * Returns a new TextLine from the shared pool.
     71      *
     72      * @return an uninitialized TextLine
     73      */
     74     static TextLine obtain() {
     75         TextLine tl;
     76         synchronized (sCached) {
     77             for (int i = sCached.length; --i >= 0;) {
     78                 if (sCached[i] != null) {
     79                     tl = sCached[i];
     80                     sCached[i] = null;
     81                     return tl;
     82                 }
     83             }
     84         }
     85         tl = new TextLine();
     86         if (DEBUG) {
     87             Log.v("TLINE", "new: " + tl);
     88         }
     89         return tl;
     90     }
     91 
     92     /**
     93      * Puts a TextLine back into the shared pool. Do not use this TextLine once
     94      * it has been returned.
     95      * @param tl the textLine
     96      * @return null, as a convenience from clearing references to the provided
     97      * TextLine
     98      */
     99     static TextLine recycle(TextLine tl) {
    100         tl.mText = null;
    101         tl.mPaint = null;
    102         tl.mDirections = null;
    103         tl.mSpanned = null;
    104         tl.mTabs = null;
    105         tl.mChars = null;
    106 
    107         tl.mMetricAffectingSpanSpanSet.recycle();
    108         tl.mCharacterStyleSpanSet.recycle();
    109         tl.mReplacementSpanSpanSet.recycle();
    110 
    111         synchronized(sCached) {
    112             for (int i = 0; i < sCached.length; ++i) {
    113                 if (sCached[i] == null) {
    114                     sCached[i] = tl;
    115                     break;
    116                 }
    117             }
    118         }
    119         return null;
    120     }
    121 
    122     /**
    123      * Initializes a TextLine and prepares it for use.
    124      *
    125      * @param paint the base paint for the line
    126      * @param text the text, can be Styled
    127      * @param start the start of the line relative to the text
    128      * @param limit the limit of the line relative to the text
    129      * @param dir the paragraph direction of this line
    130      * @param directions the directions information of this line
    131      * @param hasTabs true if the line might contain tabs or emoji
    132      * @param tabStops the tabStops. Can be null.
    133      */
    134     void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
    135             Directions directions, boolean hasTabs, TabStops tabStops) {
    136         mPaint = paint;
    137         mText = text;
    138         mStart = start;
    139         mLen = limit - start;
    140         mDir = dir;
    141         mDirections = directions;
    142         if (mDirections == null) {
    143             throw new IllegalArgumentException("Directions cannot be null");
    144         }
    145         mHasTabs = hasTabs;
    146         mSpanned = null;
    147 
    148         boolean hasReplacement = false;
    149         if (text instanceof Spanned) {
    150             mSpanned = (Spanned) text;
    151             mReplacementSpanSpanSet.init(mSpanned, start, limit);
    152             hasReplacement = mReplacementSpanSpanSet.numberOfSpans > 0;
    153         }
    154 
    155         mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
    156 
    157         if (mCharsValid) {
    158             if (mChars == null || mChars.length < mLen) {
    159                 mChars = ArrayUtils.newUnpaddedCharArray(mLen);
    160             }
    161             TextUtils.getChars(text, start, limit, mChars, 0);
    162             if (hasReplacement) {
    163                 // Handle these all at once so we don't have to do it as we go.
    164                 // Replace the first character of each replacement run with the
    165                 // object-replacement character and the remainder with zero width
    166                 // non-break space aka BOM.  Cursor movement code skips these
    167                 // zero-width characters.
    168                 char[] chars = mChars;
    169                 for (int i = start, inext; i < limit; i = inext) {
    170                     inext = mReplacementSpanSpanSet.getNextTransition(i, limit);
    171                     if (mReplacementSpanSpanSet.hasSpansIntersecting(i, inext)) {
    172                         // transition into a span
    173                         chars[i - start] = '\ufffc';
    174                         for (int j = i - start + 1, e = inext - start; j < e; ++j) {
    175                             chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip
    176                         }
    177                     }
    178                 }
    179             }
    180         }
    181         mTabs = tabStops;
    182     }
    183 
    184     /**
    185      * Renders the TextLine.
    186      *
    187      * @param c the canvas to render on
    188      * @param x the leading margin position
    189      * @param top the top of the line
    190      * @param y the baseline
    191      * @param bottom the bottom of the line
    192      */
    193     void draw(Canvas c, float x, int top, int y, int bottom) {
    194         if (!mHasTabs) {
    195             if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
    196                 drawRun(c, 0, mLen, false, x, top, y, bottom, false);
    197                 return;
    198             }
    199             if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
    200                 drawRun(c, 0, mLen, true, x, top, y, bottom, false);
    201                 return;
    202             }
    203         }
    204 
    205         float h = 0;
    206         int[] runs = mDirections.mDirections;
    207         RectF emojiRect = null;
    208 
    209         int lastRunIndex = runs.length - 2;
    210         for (int i = 0; i < runs.length; i += 2) {
    211             int runStart = runs[i];
    212             int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
    213             if (runLimit > mLen) {
    214                 runLimit = mLen;
    215             }
    216             boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
    217 
    218             int segstart = runStart;
    219             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
    220                 int codept = 0;
    221                 Bitmap bm = null;
    222 
    223                 if (mHasTabs && j < runLimit) {
    224                     codept = mChars[j];
    225                     if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) {
    226                         codept = Character.codePointAt(mChars, j);
    227                         if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) {
    228                             bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
    229                         } else if (codept > 0xffff) {
    230                             ++j;
    231                             continue;
    232                         }
    233                     }
    234                 }
    235 
    236                 if (j == runLimit || codept == '\t' || bm != null) {
    237                     h += drawRun(c, segstart, j, runIsRtl, x+h, top, y, bottom,
    238                             i != lastRunIndex || j != mLen);
    239 
    240                     if (codept == '\t') {
    241                         h = mDir * nextTab(h * mDir);
    242                     } else if (bm != null) {
    243                         float bmAscent = ascent(j);
    244                         float bitmapHeight = bm.getHeight();
    245                         float scale = -bmAscent / bitmapHeight;
    246                         float width = bm.getWidth() * scale;
    247 
    248                         if (emojiRect == null) {
    249                             emojiRect = new RectF();
    250                         }
    251                         emojiRect.set(x + h, y + bmAscent,
    252                                 x + h + width, y);
    253                         c.drawBitmap(bm, null, emojiRect, mPaint);
    254                         h += width;
    255                         j++;
    256                     }
    257                     segstart = j + 1;
    258                 }
    259             }
    260         }
    261     }
    262 
    263     /**
    264      * Returns metrics information for the entire line.
    265      *
    266      * @param fmi receives font metrics information, can be null
    267      * @return the signed width of the line
    268      */
    269     float metrics(FontMetricsInt fmi) {
    270         return measure(mLen, false, fmi);
    271     }
    272 
    273     /**
    274      * Returns information about a position on the line.
    275      *
    276      * @param offset the line-relative character offset, between 0 and the
    277      * line length, inclusive
    278      * @param trailing true to measure the trailing edge of the character
    279      * before offset, false to measure the leading edge of the character
    280      * at offset.
    281      * @param fmi receives metrics information about the requested
    282      * character, can be null.
    283      * @return the signed offset from the leading margin to the requested
    284      * character edge.
    285      */
    286     float measure(int offset, boolean trailing, FontMetricsInt fmi) {
    287         int target = trailing ? offset - 1 : offset;
    288         if (target < 0) {
    289             return 0;
    290         }
    291 
    292         float h = 0;
    293 
    294         if (!mHasTabs) {
    295             if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
    296                 return measureRun(0, offset, mLen, false, fmi);
    297             }
    298             if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
    299                 return measureRun(0, offset, mLen, true, fmi);
    300             }
    301         }
    302 
    303         char[] chars = mChars;
    304         int[] runs = mDirections.mDirections;
    305         for (int i = 0; i < runs.length; i += 2) {
    306             int runStart = runs[i];
    307             int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
    308             if (runLimit > mLen) {
    309                 runLimit = mLen;
    310             }
    311             boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
    312 
    313             int segstart = runStart;
    314             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
    315                 int codept = 0;
    316                 Bitmap bm = null;
    317 
    318                 if (mHasTabs && j < runLimit) {
    319                     codept = chars[j];
    320                     if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) {
    321                         codept = Character.codePointAt(chars, j);
    322                         if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) {
    323                             bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
    324                         } else if (codept > 0xffff) {
    325                             ++j;
    326                             continue;
    327                         }
    328                     }
    329                 }
    330 
    331                 if (j == runLimit || codept == '\t' || bm != null) {
    332                     boolean inSegment = target >= segstart && target < j;
    333 
    334                     boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
    335                     if (inSegment && advance) {
    336                         return h += measureRun(segstart, offset, j, runIsRtl, fmi);
    337                     }
    338 
    339                     float w = measureRun(segstart, j, j, runIsRtl, fmi);
    340                     h += advance ? w : -w;
    341 
    342                     if (inSegment) {
    343                         return h += measureRun(segstart, offset, j, runIsRtl, null);
    344                     }
    345 
    346                     if (codept == '\t') {
    347                         if (offset == j) {
    348                             return h;
    349                         }
    350                         h = mDir * nextTab(h * mDir);
    351                         if (target == j) {
    352                             return h;
    353                         }
    354                     }
    355 
    356                     if (bm != null) {
    357                         float bmAscent = ascent(j);
    358                         float wid = bm.getWidth() * -bmAscent / bm.getHeight();
    359                         h += mDir * wid;
    360                         j++;
    361                     }
    362 
    363                     segstart = j + 1;
    364                 }
    365             }
    366         }
    367 
    368         return h;
    369     }
    370 
    371     /**
    372      * Draws a unidirectional (but possibly multi-styled) run of text.
    373      *
    374      *
    375      * @param c the canvas to draw on
    376      * @param start the line-relative start
    377      * @param limit the line-relative limit
    378      * @param runIsRtl true if the run is right-to-left
    379      * @param x the position of the run that is closest to the leading margin
    380      * @param top the top of the line
    381      * @param y the baseline
    382      * @param bottom the bottom of the line
    383      * @param needWidth true if the width value is required.
    384      * @return the signed width of the run, based on the paragraph direction.
    385      * Only valid if needWidth is true.
    386      */
    387     private float drawRun(Canvas c, int start,
    388             int limit, boolean runIsRtl, float x, int top, int y, int bottom,
    389             boolean needWidth) {
    390 
    391         if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
    392             float w = -measureRun(start, limit, limit, runIsRtl, null);
    393             handleRun(start, limit, limit, runIsRtl, c, x + w, top,
    394                     y, bottom, null, false);
    395             return w;
    396         }
    397 
    398         return handleRun(start, limit, limit, runIsRtl, c, x, top,
    399                 y, bottom, null, needWidth);
    400     }
    401 
    402     /**
    403      * Measures a unidirectional (but possibly multi-styled) run of text.
    404      *
    405      *
    406      * @param start the line-relative start of the run
    407      * @param offset the offset to measure to, between start and limit inclusive
    408      * @param limit the line-relative limit of the run
    409      * @param runIsRtl true if the run is right-to-left
    410      * @param fmi receives metrics information about the requested
    411      * run, can be null.
    412      * @return the signed width from the start of the run to the leading edge
    413      * of the character at offset, based on the run (not paragraph) direction
    414      */
    415     private float measureRun(int start, int offset, int limit, boolean runIsRtl,
    416             FontMetricsInt fmi) {
    417         return handleRun(start, offset, limit, runIsRtl, null, 0, 0, 0, 0, fmi, true);
    418     }
    419 
    420     /**
    421      * Walk the cursor through this line, skipping conjuncts and
    422      * zero-width characters.
    423      *
    424      * <p>This function cannot properly walk the cursor off the ends of the line
    425      * since it does not know about any shaping on the previous/following line
    426      * that might affect the cursor position. Callers must either avoid these
    427      * situations or handle the result specially.
    428      *
    429      * @param cursor the starting position of the cursor, between 0 and the
    430      * length of the line, inclusive
    431      * @param toLeft true if the caret is moving to the left.
    432      * @return the new offset.  If it is less than 0 or greater than the length
    433      * of the line, the previous/following line should be examined to get the
    434      * actual offset.
    435      */
    436     int getOffsetToLeftRightOf(int cursor, boolean toLeft) {
    437         // 1) The caret marks the leading edge of a character. The character
    438         // logically before it might be on a different level, and the active caret
    439         // position is on the character at the lower level. If that character
    440         // was the previous character, the caret is on its trailing edge.
    441         // 2) Take this character/edge and move it in the indicated direction.
    442         // This gives you a new character and a new edge.
    443         // 3) This position is between two visually adjacent characters.  One of
    444         // these might be at a lower level.  The active position is on the
    445         // character at the lower level.
    446         // 4) If the active position is on the trailing edge of the character,
    447         // the new caret position is the following logical character, else it
    448         // is the character.
    449 
    450         int lineStart = 0;
    451         int lineEnd = mLen;
    452         boolean paraIsRtl = mDir == -1;
    453         int[] runs = mDirections.mDirections;
    454 
    455         int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1;
    456         boolean trailing = false;
    457 
    458         if (cursor == lineStart) {
    459             runIndex = -2;
    460         } else if (cursor == lineEnd) {
    461             runIndex = runs.length;
    462         } else {
    463           // First, get information about the run containing the character with
    464           // the active caret.
    465           for (runIndex = 0; runIndex < runs.length; runIndex += 2) {
    466             runStart = lineStart + runs[runIndex];
    467             if (cursor >= runStart) {
    468               runLimit = runStart + (runs[runIndex+1] & Layout.RUN_LENGTH_MASK);
    469               if (runLimit > lineEnd) {
    470                   runLimit = lineEnd;
    471               }
    472               if (cursor < runLimit) {
    473                 runLevel = (runs[runIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
    474                     Layout.RUN_LEVEL_MASK;
    475                 if (cursor == runStart) {
    476                   // The caret is on a run boundary, see if we should
    477                   // use the position on the trailing edge of the previous
    478                   // logical character instead.
    479                   int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit;
    480                   int pos = cursor - 1;
    481                   for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) {
    482                     prevRunStart = lineStart + runs[prevRunIndex];
    483                     if (pos >= prevRunStart) {
    484                       prevRunLimit = prevRunStart +
    485                           (runs[prevRunIndex+1] & Layout.RUN_LENGTH_MASK);
    486                       if (prevRunLimit > lineEnd) {
    487                           prevRunLimit = lineEnd;
    488                       }
    489                       if (pos < prevRunLimit) {
    490                         prevRunLevel = (runs[prevRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT)
    491                             & Layout.RUN_LEVEL_MASK;
    492                         if (prevRunLevel < runLevel) {
    493                           // Start from logically previous character.
    494                           runIndex = prevRunIndex;
    495                           runLevel = prevRunLevel;
    496                           runStart = prevRunStart;
    497                           runLimit = prevRunLimit;
    498                           trailing = true;
    499                           break;
    500                         }
    501                       }
    502                     }
    503                   }
    504                 }
    505                 break;
    506               }
    507             }
    508           }
    509 
    510           // caret might be == lineEnd.  This is generally a space or paragraph
    511           // separator and has an associated run, but might be the end of
    512           // text, in which case it doesn't.  If that happens, we ran off the
    513           // end of the run list, and runIndex == runs.length.  In this case,
    514           // we are at a run boundary so we skip the below test.
    515           if (runIndex != runs.length) {
    516               boolean runIsRtl = (runLevel & 0x1) != 0;
    517               boolean advance = toLeft == runIsRtl;
    518               if (cursor != (advance ? runLimit : runStart) || advance != trailing) {
    519                   // Moving within or into the run, so we can move logically.
    520                   newCaret = getOffsetBeforeAfter(runIndex, runStart, runLimit,
    521                           runIsRtl, cursor, advance);
    522                   // If the new position is internal to the run, we're at the strong
    523                   // position already so we're finished.
    524                   if (newCaret != (advance ? runLimit : runStart)) {
    525                       return newCaret;
    526                   }
    527               }
    528           }
    529         }
    530 
    531         // If newCaret is -1, we're starting at a run boundary and crossing
    532         // into another run. Otherwise we've arrived at a run boundary, and
    533         // need to figure out which character to attach to.  Note we might
    534         // need to run this twice, if we cross a run boundary and end up at
    535         // another run boundary.
    536         while (true) {
    537           boolean advance = toLeft == paraIsRtl;
    538           int otherRunIndex = runIndex + (advance ? 2 : -2);
    539           if (otherRunIndex >= 0 && otherRunIndex < runs.length) {
    540             int otherRunStart = lineStart + runs[otherRunIndex];
    541             int otherRunLimit = otherRunStart +
    542             (runs[otherRunIndex+1] & Layout.RUN_LENGTH_MASK);
    543             if (otherRunLimit > lineEnd) {
    544                 otherRunLimit = lineEnd;
    545             }
    546             int otherRunLevel = (runs[otherRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
    547                 Layout.RUN_LEVEL_MASK;
    548             boolean otherRunIsRtl = (otherRunLevel & 1) != 0;
    549 
    550             advance = toLeft == otherRunIsRtl;
    551             if (newCaret == -1) {
    552                 newCaret = getOffsetBeforeAfter(otherRunIndex, otherRunStart,
    553                         otherRunLimit, otherRunIsRtl,
    554                         advance ? otherRunStart : otherRunLimit, advance);
    555                 if (newCaret == (advance ? otherRunLimit : otherRunStart)) {
    556                     // Crossed and ended up at a new boundary,
    557                     // repeat a second and final time.
    558                     runIndex = otherRunIndex;
    559                     runLevel = otherRunLevel;
    560                     continue;
    561                 }
    562                 break;
    563             }
    564 
    565             // The new caret is at a boundary.
    566             if (otherRunLevel < runLevel) {
    567               // The strong character is in the other run.
    568               newCaret = advance ? otherRunStart : otherRunLimit;
    569             }
    570             break;
    571           }
    572 
    573           if (newCaret == -1) {
    574               // We're walking off the end of the line.  The paragraph
    575               // level is always equal to or lower than any internal level, so
    576               // the boundaries get the strong caret.
    577               newCaret = advance ? mLen + 1 : -1;
    578               break;
    579           }
    580 
    581           // Else we've arrived at the end of the line.  That's a strong position.
    582           // We might have arrived here by crossing over a run with no internal
    583           // breaks and dropping out of the above loop before advancing one final
    584           // time, so reset the caret.
    585           // Note, we use '<=' below to handle a situation where the only run
    586           // on the line is a counter-directional run.  If we're not advancing,
    587           // we can end up at the 'lineEnd' position but the caret we want is at
    588           // the lineStart.
    589           if (newCaret <= lineEnd) {
    590               newCaret = advance ? lineEnd : lineStart;
    591           }
    592           break;
    593         }
    594 
    595         return newCaret;
    596     }
    597 
    598     /**
    599      * Returns the next valid offset within this directional run, skipping
    600      * conjuncts and zero-width characters.  This should not be called to walk
    601      * off the end of the line, since the returned values might not be valid
    602      * on neighboring lines.  If the returned offset is less than zero or
    603      * greater than the line length, the offset should be recomputed on the
    604      * preceding or following line, respectively.
    605      *
    606      * @param runIndex the run index
    607      * @param runStart the start of the run
    608      * @param runLimit the limit of the run
    609      * @param runIsRtl true if the run is right-to-left
    610      * @param offset the offset
    611      * @param after true if the new offset should logically follow the provided
    612      * offset
    613      * @return the new offset
    614      */
    615     private int getOffsetBeforeAfter(int runIndex, int runStart, int runLimit,
    616             boolean runIsRtl, int offset, boolean after) {
    617 
    618         if (runIndex < 0 || offset == (after ? mLen : 0)) {
    619             // Walking off end of line.  Since we don't know
    620             // what cursor positions are available on other lines, we can't
    621             // return accurate values.  These are a guess.
    622             if (after) {
    623                 return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart;
    624             }
    625             return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart;
    626         }
    627 
    628         TextPaint wp = mWorkPaint;
    629         wp.set(mPaint);
    630 
    631         int spanStart = runStart;
    632         int spanLimit;
    633         if (mSpanned == null) {
    634             spanLimit = runLimit;
    635         } else {
    636             int target = after ? offset + 1 : offset;
    637             int limit = mStart + runLimit;
    638             while (true) {
    639                 spanLimit = mSpanned.nextSpanTransition(mStart + spanStart, limit,
    640                         MetricAffectingSpan.class) - mStart;
    641                 if (spanLimit >= target) {
    642                     break;
    643                 }
    644                 spanStart = spanLimit;
    645             }
    646 
    647             MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart,
    648                     mStart + spanLimit, MetricAffectingSpan.class);
    649             spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
    650 
    651             if (spans.length > 0) {
    652                 ReplacementSpan replacement = null;
    653                 for (int j = 0; j < spans.length; j++) {
    654                     MetricAffectingSpan span = spans[j];
    655                     if (span instanceof ReplacementSpan) {
    656                         replacement = (ReplacementSpan)span;
    657                     } else {
    658                         span.updateMeasureState(wp);
    659                     }
    660                 }
    661 
    662                 if (replacement != null) {
    663                     // If we have a replacement span, we're moving either to
    664                     // the start or end of this span.
    665                     return after ? spanLimit : spanStart;
    666                 }
    667             }
    668         }
    669 
    670         int dir = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
    671         int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
    672         if (mCharsValid) {
    673             return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart,
    674                     dir, offset, cursorOpt);
    675         } else {
    676             return wp.getTextRunCursor(mText, mStart + spanStart,
    677                     mStart + spanLimit, dir, mStart + offset, cursorOpt) - mStart;
    678         }
    679     }
    680 
    681     /**
    682      * @param wp
    683      */
    684     private static void expandMetricsFromPaint(FontMetricsInt fmi, TextPaint wp) {
    685         final int previousTop     = fmi.top;
    686         final int previousAscent  = fmi.ascent;
    687         final int previousDescent = fmi.descent;
    688         final int previousBottom  = fmi.bottom;
    689         final int previousLeading = fmi.leading;
    690 
    691         wp.getFontMetricsInt(fmi);
    692 
    693         updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom,
    694                 previousLeading);
    695     }
    696 
    697     static void updateMetrics(FontMetricsInt fmi, int previousTop, int previousAscent,
    698             int previousDescent, int previousBottom, int previousLeading) {
    699         fmi.top     = Math.min(fmi.top,     previousTop);
    700         fmi.ascent  = Math.min(fmi.ascent,  previousAscent);
    701         fmi.descent = Math.max(fmi.descent, previousDescent);
    702         fmi.bottom  = Math.max(fmi.bottom,  previousBottom);
    703         fmi.leading = Math.max(fmi.leading, previousLeading);
    704     }
    705 
    706     /**
    707      * Utility function for measuring and rendering text.  The text must
    708      * not include a tab or emoji.
    709      *
    710      * @param wp the working paint
    711      * @param start the start of the text
    712      * @param end the end of the text
    713      * @param runIsRtl true if the run is right-to-left
    714      * @param c the canvas, can be null if rendering is not needed
    715      * @param x the edge of the run closest to the leading margin
    716      * @param top the top of the line
    717      * @param y the baseline
    718      * @param bottom the bottom of the line
    719      * @param fmi receives metrics information, can be null
    720      * @param needWidth true if the width of the run is needed
    721      * @return the signed width of the run based on the run direction; only
    722      * valid if needWidth is true
    723      */
    724     private float handleText(TextPaint wp, int start, int end,
    725             int contextStart, int contextEnd, boolean runIsRtl,
    726             Canvas c, float x, int top, int y, int bottom,
    727             FontMetricsInt fmi, boolean needWidth) {
    728 
    729         // Get metrics first (even for empty strings or "0" width runs)
    730         if (fmi != null) {
    731             expandMetricsFromPaint(fmi, wp);
    732         }
    733 
    734         int runLen = end - start;
    735         // No need to do anything if the run width is "0"
    736         if (runLen == 0) {
    737             return 0f;
    738         }
    739 
    740         float ret = 0;
    741 
    742         int contextLen = contextEnd - contextStart;
    743         if (needWidth || (c != null && (wp.bgColor != 0 || wp.underlineColor != 0 || runIsRtl))) {
    744             if (mCharsValid) {
    745                 ret = wp.getTextRunAdvances(mChars, start, runLen,
    746                         contextStart, contextLen, runIsRtl, null, 0);
    747             } else {
    748                 int delta = mStart;
    749                 ret = wp.getTextRunAdvances(mText, delta + start,
    750                         delta + end, delta + contextStart, delta + contextEnd,
    751                         runIsRtl, null, 0);
    752             }
    753         }
    754 
    755         if (c != null) {
    756             if (runIsRtl) {
    757                 x -= ret;
    758             }
    759 
    760             if (wp.bgColor != 0) {
    761                 int previousColor = wp.getColor();
    762                 Paint.Style previousStyle = wp.getStyle();
    763 
    764                 wp.setColor(wp.bgColor);
    765                 wp.setStyle(Paint.Style.FILL);
    766                 c.drawRect(x, top, x + ret, bottom, wp);
    767 
    768                 wp.setStyle(previousStyle);
    769                 wp.setColor(previousColor);
    770             }
    771 
    772             if (wp.underlineColor != 0) {
    773                 // kStdUnderline_Offset = 1/9, defined in SkTextFormatParams.h
    774                 float underlineTop = y + wp.baselineShift + (1.0f / 9.0f) * wp.getTextSize();
    775 
    776                 int previousColor = wp.getColor();
    777                 Paint.Style previousStyle = wp.getStyle();
    778                 boolean previousAntiAlias = wp.isAntiAlias();
    779 
    780                 wp.setStyle(Paint.Style.FILL);
    781                 wp.setAntiAlias(true);
    782 
    783                 wp.setColor(wp.underlineColor);
    784                 c.drawRect(x, underlineTop, x + ret, underlineTop + wp.underlineThickness, wp);
    785 
    786                 wp.setStyle(previousStyle);
    787                 wp.setColor(previousColor);
    788                 wp.setAntiAlias(previousAntiAlias);
    789             }
    790 
    791             drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
    792                     x, y + wp.baselineShift);
    793         }
    794 
    795         return runIsRtl ? -ret : ret;
    796     }
    797 
    798     /**
    799      * Utility function for measuring and rendering a replacement.
    800      *
    801      *
    802      * @param replacement the replacement
    803      * @param wp the work paint
    804      * @param start the start of the run
    805      * @param limit the limit of the run
    806      * @param runIsRtl true if the run is right-to-left
    807      * @param c the canvas, can be null if not rendering
    808      * @param x the edge of the replacement closest to the leading margin
    809      * @param top the top of the line
    810      * @param y the baseline
    811      * @param bottom the bottom of the line
    812      * @param fmi receives metrics information, can be null
    813      * @param needWidth true if the width of the replacement is needed
    814      * @return the signed width of the run based on the run direction; only
    815      * valid if needWidth is true
    816      */
    817     private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
    818             int start, int limit, boolean runIsRtl, Canvas c,
    819             float x, int top, int y, int bottom, FontMetricsInt fmi,
    820             boolean needWidth) {
    821 
    822         float ret = 0;
    823 
    824         int textStart = mStart + start;
    825         int textLimit = mStart + limit;
    826 
    827         if (needWidth || (c != null && runIsRtl)) {
    828             int previousTop = 0;
    829             int previousAscent = 0;
    830             int previousDescent = 0;
    831             int previousBottom = 0;
    832             int previousLeading = 0;
    833 
    834             boolean needUpdateMetrics = (fmi != null);
    835 
    836             if (needUpdateMetrics) {
    837                 previousTop     = fmi.top;
    838                 previousAscent  = fmi.ascent;
    839                 previousDescent = fmi.descent;
    840                 previousBottom  = fmi.bottom;
    841                 previousLeading = fmi.leading;
    842             }
    843 
    844             ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
    845 
    846             if (needUpdateMetrics) {
    847                 updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom,
    848                         previousLeading);
    849             }
    850         }
    851 
    852         if (c != null) {
    853             if (runIsRtl) {
    854                 x -= ret;
    855             }
    856             replacement.draw(c, mText, textStart, textLimit,
    857                     x, top, y, bottom, wp);
    858         }
    859 
    860         return runIsRtl ? -ret : ret;
    861     }
    862 
    863     /**
    864      * Utility function for handling a unidirectional run.  The run must not
    865      * contain tabs or emoji but can contain styles.
    866      *
    867      *
    868      * @param start the line-relative start of the run
    869      * @param measureLimit the offset to measure to, between start and limit inclusive
    870      * @param limit the limit of the run
    871      * @param runIsRtl true if the run is right-to-left
    872      * @param c the canvas, can be null
    873      * @param x the end of the run closest to the leading margin
    874      * @param top the top of the line
    875      * @param y the baseline
    876      * @param bottom the bottom of the line
    877      * @param fmi receives metrics information, can be null
    878      * @param needWidth true if the width is required
    879      * @return the signed width of the run based on the run direction; only
    880      * valid if needWidth is true
    881      */
    882     private float handleRun(int start, int measureLimit,
    883             int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
    884             int bottom, FontMetricsInt fmi, boolean needWidth) {
    885 
    886         // Case of an empty line, make sure we update fmi according to mPaint
    887         if (start == measureLimit) {
    888             TextPaint wp = mWorkPaint;
    889             wp.set(mPaint);
    890             if (fmi != null) {
    891                 expandMetricsFromPaint(fmi, wp);
    892             }
    893             return 0f;
    894         }
    895 
    896         if (mSpanned == null) {
    897             TextPaint wp = mWorkPaint;
    898             wp.set(mPaint);
    899             final int mlimit = measureLimit;
    900             return handleText(wp, start, mlimit, start, limit, runIsRtl, c, x, top,
    901                     y, bottom, fmi, needWidth || mlimit < measureLimit);
    902         }
    903 
    904         mMetricAffectingSpanSpanSet.init(mSpanned, mStart + start, mStart + limit);
    905         mCharacterStyleSpanSet.init(mSpanned, mStart + start, mStart + limit);
    906 
    907         // Shaping needs to take into account context up to metric boundaries,
    908         // but rendering needs to take into account character style boundaries.
    909         // So we iterate through metric runs to get metric bounds,
    910         // then within each metric run iterate through character style runs
    911         // for the run bounds.
    912         final float originalX = x;
    913         for (int i = start, inext; i < measureLimit; i = inext) {
    914             TextPaint wp = mWorkPaint;
    915             wp.set(mPaint);
    916 
    917             inext = mMetricAffectingSpanSpanSet.getNextTransition(mStart + i, mStart + limit) -
    918                     mStart;
    919             int mlimit = Math.min(inext, measureLimit);
    920 
    921             ReplacementSpan replacement = null;
    922 
    923             for (int j = 0; j < mMetricAffectingSpanSpanSet.numberOfSpans; j++) {
    924                 // Both intervals [spanStarts..spanEnds] and [mStart + i..mStart + mlimit] are NOT
    925                 // empty by construction. This special case in getSpans() explains the >= & <= tests
    926                 if ((mMetricAffectingSpanSpanSet.spanStarts[j] >= mStart + mlimit) ||
    927                         (mMetricAffectingSpanSpanSet.spanEnds[j] <= mStart + i)) continue;
    928                 MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j];
    929                 if (span instanceof ReplacementSpan) {
    930                     replacement = (ReplacementSpan)span;
    931                 } else {
    932                     // We might have a replacement that uses the draw
    933                     // state, otherwise measure state would suffice.
    934                     span.updateDrawState(wp);
    935                 }
    936             }
    937 
    938             if (replacement != null) {
    939                 x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y,
    940                         bottom, fmi, needWidth || mlimit < measureLimit);
    941                 continue;
    942             }
    943 
    944             for (int j = i, jnext; j < mlimit; j = jnext) {
    945                 jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + mlimit) -
    946                         mStart;
    947 
    948                 wp.set(mPaint);
    949                 for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
    950                     // Intentionally using >= and <= as explained above
    951                     if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + jnext) ||
    952                             (mCharacterStyleSpanSet.spanEnds[k] <= mStart + j)) continue;
    953 
    954                     CharacterStyle span = mCharacterStyleSpanSet.spans[k];
    955                     span.updateDrawState(wp);
    956                 }
    957 
    958                 x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
    959                         top, y, bottom, fmi, needWidth || jnext < measureLimit);
    960             }
    961         }
    962 
    963         return x - originalX;
    964     }
    965 
    966     /**
    967      * Render a text run with the set-up paint.
    968      *
    969      * @param c the canvas
    970      * @param wp the paint used to render the text
    971      * @param start the start of the run
    972      * @param end the end of the run
    973      * @param contextStart the start of context for the run
    974      * @param contextEnd the end of the context for the run
    975      * @param runIsRtl true if the run is right-to-left
    976      * @param x the x position of the left edge of the run
    977      * @param y the baseline of the run
    978      */
    979     private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
    980             int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
    981 
    982         if (mCharsValid) {
    983             int count = end - start;
    984             int contextCount = contextEnd - contextStart;
    985             c.drawTextRun(mChars, start, count, contextStart, contextCount,
    986                     x, y, runIsRtl, wp);
    987         } else {
    988             int delta = mStart;
    989             c.drawTextRun(mText, delta + start, delta + end,
    990                     delta + contextStart, delta + contextEnd, x, y, runIsRtl, wp);
    991         }
    992     }
    993 
    994     /**
    995      * Returns the ascent of the text at start.  This is used for scaling
    996      * emoji.
    997      *
    998      * @param pos the line-relative position
    999      * @return the ascent of the text at start
   1000      */
   1001     float ascent(int pos) {
   1002         if (mSpanned == null) {
   1003             return mPaint.ascent();
   1004         }
   1005 
   1006         pos += mStart;
   1007         MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1, MetricAffectingSpan.class);
   1008         if (spans.length == 0) {
   1009             return mPaint.ascent();
   1010         }
   1011 
   1012         TextPaint wp = mWorkPaint;
   1013         wp.set(mPaint);
   1014         for (MetricAffectingSpan span : spans) {
   1015             span.updateMeasureState(wp);
   1016         }
   1017         return wp.ascent();
   1018     }
   1019 
   1020     /**
   1021      * Returns the next tab position.
   1022      *
   1023      * @param h the (unsigned) offset from the leading margin
   1024      * @return the (unsigned) tab position after this offset
   1025      */
   1026     float nextTab(float h) {
   1027         if (mTabs != null) {
   1028             return mTabs.nextTab(h);
   1029         }
   1030         return TabStops.nextDefaultStop(h, TAB_INCREMENT);
   1031     }
   1032 
   1033     private static final int TAB_INCREMENT = 20;
   1034 }
   1035