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