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 = ArrayUtils.newUnpaddedCharArray(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 dir = 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                     dir, offset, cursorOpt);
    672         } else {
    673             return wp.getTextRunCursor(mText, mStart + spanStart,
    674                     mStart + spanLimit, dir, 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             if (mCharsValid) {
    742                 ret = wp.getTextRunAdvances(mChars, start, runLen,
    743                         contextStart, contextLen, runIsRtl, null, 0);
    744             } else {
    745                 int delta = mStart;
    746                 ret = wp.getTextRunAdvances(mText, delta + start,
    747                         delta + end, delta + contextStart, delta + contextEnd,
    748                         runIsRtl, null, 0);
    749             }
    750         }
    751 
    752         if (c != null) {
    753             if (runIsRtl) {
    754                 x -= ret;
    755             }
    756 
    757             if (wp.bgColor != 0) {
    758                 int previousColor = wp.getColor();
    759                 Paint.Style previousStyle = wp.getStyle();
    760 
    761                 wp.setColor(wp.bgColor);
    762                 wp.setStyle(Paint.Style.FILL);
    763                 c.drawRect(x, top, x + ret, bottom, wp);
    764 
    765                 wp.setStyle(previousStyle);
    766                 wp.setColor(previousColor);
    767             }
    768 
    769             if (wp.underlineColor != 0) {
    770                 // kStdUnderline_Offset = 1/9, defined in SkTextFormatParams.h
    771                 float underlineTop = y + wp.baselineShift + (1.0f / 9.0f) * wp.getTextSize();
    772 
    773                 int previousColor = wp.getColor();
    774                 Paint.Style previousStyle = wp.getStyle();
    775                 boolean previousAntiAlias = wp.isAntiAlias();
    776 
    777                 wp.setStyle(Paint.Style.FILL);
    778                 wp.setAntiAlias(true);
    779 
    780                 wp.setColor(wp.underlineColor);
    781                 c.drawRect(x, underlineTop, x + ret, underlineTop + wp.underlineThickness, wp);
    782 
    783                 wp.setStyle(previousStyle);
    784                 wp.setColor(previousColor);
    785                 wp.setAntiAlias(previousAntiAlias);
    786             }
    787 
    788             drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
    789                     x, y + wp.baselineShift);
    790         }
    791 
    792         return runIsRtl ? -ret : ret;
    793     }
    794 
    795     /**
    796      * Utility function for measuring and rendering a replacement.
    797      *
    798      *
    799      * @param replacement the replacement
    800      * @param wp the work paint
    801      * @param start the start of the run
    802      * @param limit the limit of the run
    803      * @param runIsRtl true if the run is right-to-left
    804      * @param c the canvas, can be null if not rendering
    805      * @param x the edge of the replacement closest to the leading margin
    806      * @param top the top of the line
    807      * @param y the baseline
    808      * @param bottom the bottom of the line
    809      * @param fmi receives metrics information, can be null
    810      * @param needWidth true if the width of the replacement is needed
    811      * @return the signed width of the run based on the run direction; only
    812      * valid if needWidth is true
    813      */
    814     private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
    815             int start, int limit, boolean runIsRtl, Canvas c,
    816             float x, int top, int y, int bottom, FontMetricsInt fmi,
    817             boolean needWidth) {
    818 
    819         float ret = 0;
    820 
    821         int textStart = mStart + start;
    822         int textLimit = mStart + limit;
    823 
    824         if (needWidth || (c != null && runIsRtl)) {
    825             int previousTop = 0;
    826             int previousAscent = 0;
    827             int previousDescent = 0;
    828             int previousBottom = 0;
    829             int previousLeading = 0;
    830 
    831             boolean needUpdateMetrics = (fmi != null);
    832 
    833             if (needUpdateMetrics) {
    834                 previousTop     = fmi.top;
    835                 previousAscent  = fmi.ascent;
    836                 previousDescent = fmi.descent;
    837                 previousBottom  = fmi.bottom;
    838                 previousLeading = fmi.leading;
    839             }
    840 
    841             ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
    842 
    843             if (needUpdateMetrics) {
    844                 updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom,
    845                         previousLeading);
    846             }
    847         }
    848 
    849         if (c != null) {
    850             if (runIsRtl) {
    851                 x -= ret;
    852             }
    853             replacement.draw(c, mText, textStart, textLimit,
    854                     x, top, y, bottom, wp);
    855         }
    856 
    857         return runIsRtl ? -ret : ret;
    858     }
    859 
    860     /**
    861      * Utility function for handling a unidirectional run.  The run must not
    862      * contain tabs or emoji but can contain styles.
    863      *
    864      *
    865      * @param start the line-relative start of the run
    866      * @param measureLimit the offset to measure to, between start and limit inclusive
    867      * @param limit the limit of the run
    868      * @param runIsRtl true if the run is right-to-left
    869      * @param c the canvas, can be null
    870      * @param x the end of the run closest to the leading margin
    871      * @param top the top of the line
    872      * @param y the baseline
    873      * @param bottom the bottom of the line
    874      * @param fmi receives metrics information, can be null
    875      * @param needWidth true if the width is required
    876      * @return the signed width of the run based on the run direction; only
    877      * valid if needWidth is true
    878      */
    879     private float handleRun(int start, int measureLimit,
    880             int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
    881             int bottom, FontMetricsInt fmi, boolean needWidth) {
    882 
    883         // Case of an empty line, make sure we update fmi according to mPaint
    884         if (start == measureLimit) {
    885             TextPaint wp = mWorkPaint;
    886             wp.set(mPaint);
    887             if (fmi != null) {
    888                 expandMetricsFromPaint(fmi, wp);
    889             }
    890             return 0f;
    891         }
    892 
    893         if (mSpanned == null) {
    894             TextPaint wp = mWorkPaint;
    895             wp.set(mPaint);
    896             final int mlimit = measureLimit;
    897             return handleText(wp, start, mlimit, start, limit, runIsRtl, c, x, top,
    898                     y, bottom, fmi, needWidth || mlimit < measureLimit);
    899         }
    900 
    901         mMetricAffectingSpanSpanSet.init(mSpanned, mStart + start, mStart + limit);
    902         mCharacterStyleSpanSet.init(mSpanned, mStart + start, mStart + limit);
    903 
    904         // Shaping needs to take into account context up to metric boundaries,
    905         // but rendering needs to take into account character style boundaries.
    906         // So we iterate through metric runs to get metric bounds,
    907         // then within each metric run iterate through character style runs
    908         // for the run bounds.
    909         final float originalX = x;
    910         for (int i = start, inext; i < measureLimit; i = inext) {
    911             TextPaint wp = mWorkPaint;
    912             wp.set(mPaint);
    913 
    914             inext = mMetricAffectingSpanSpanSet.getNextTransition(mStart + i, mStart + limit) -
    915                     mStart;
    916             int mlimit = Math.min(inext, measureLimit);
    917 
    918             ReplacementSpan replacement = null;
    919 
    920             for (int j = 0; j < mMetricAffectingSpanSpanSet.numberOfSpans; j++) {
    921                 // Both intervals [spanStarts..spanEnds] and [mStart + i..mStart + mlimit] are NOT
    922                 // empty by construction. This special case in getSpans() explains the >= & <= tests
    923                 if ((mMetricAffectingSpanSpanSet.spanStarts[j] >= mStart + mlimit) ||
    924                         (mMetricAffectingSpanSpanSet.spanEnds[j] <= mStart + i)) continue;
    925                 MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j];
    926                 if (span instanceof ReplacementSpan) {
    927                     replacement = (ReplacementSpan)span;
    928                 } else {
    929                     // We might have a replacement that uses the draw
    930                     // state, otherwise measure state would suffice.
    931                     span.updateDrawState(wp);
    932                 }
    933             }
    934 
    935             if (replacement != null) {
    936                 x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y,
    937                         bottom, fmi, needWidth || mlimit < measureLimit);
    938                 continue;
    939             }
    940 
    941             for (int j = i, jnext; j < mlimit; j = jnext) {
    942                 jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + mlimit) -
    943                         mStart;
    944 
    945                 wp.set(mPaint);
    946                 for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
    947                     // Intentionally using >= and <= as explained above
    948                     if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + jnext) ||
    949                             (mCharacterStyleSpanSet.spanEnds[k] <= mStart + j)) continue;
    950 
    951                     CharacterStyle span = mCharacterStyleSpanSet.spans[k];
    952                     span.updateDrawState(wp);
    953                 }
    954 
    955                 x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
    956                         top, y, bottom, fmi, needWidth || jnext < measureLimit);
    957             }
    958         }
    959 
    960         return x - originalX;
    961     }
    962 
    963     /**
    964      * Render a text run with the set-up paint.
    965      *
    966      * @param c the canvas
    967      * @param wp the paint used to render the text
    968      * @param start the start of the run
    969      * @param end the end of the run
    970      * @param contextStart the start of context for the run
    971      * @param contextEnd the end of the context for the run
    972      * @param runIsRtl true if the run is right-to-left
    973      * @param x the x position of the left edge of the run
    974      * @param y the baseline of the run
    975      */
    976     private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
    977             int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
    978 
    979         if (mCharsValid) {
    980             int count = end - start;
    981             int contextCount = contextEnd - contextStart;
    982             c.drawTextRun(mChars, start, count, contextStart, contextCount,
    983                     x, y, runIsRtl, wp);
    984         } else {
    985             int delta = mStart;
    986             c.drawTextRun(mText, delta + start, delta + end,
    987                     delta + contextStart, delta + contextEnd, x, y, runIsRtl, wp);
    988         }
    989     }
    990 
    991     /**
    992      * Returns the ascent of the text at start.  This is used for scaling
    993      * emoji.
    994      *
    995      * @param pos the line-relative position
    996      * @return the ascent of the text at start
    997      */
    998     float ascent(int pos) {
    999         if (mSpanned == null) {
   1000             return mPaint.ascent();
   1001         }
   1002 
   1003         pos += mStart;
   1004         MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1, MetricAffectingSpan.class);
   1005         if (spans.length == 0) {
   1006             return mPaint.ascent();
   1007         }
   1008 
   1009         TextPaint wp = mWorkPaint;
   1010         wp.set(mPaint);
   1011         for (MetricAffectingSpan span : spans) {
   1012             span.updateMeasureState(wp);
   1013         }
   1014         return wp.ascent();
   1015     }
   1016 
   1017     /**
   1018      * Returns the next tab position.
   1019      *
   1020      * @param h the (unsigned) offset from the leading margin
   1021      * @return the (unsigned) tab position after this offset
   1022      */
   1023     float nextTab(float h) {
   1024         if (mTabs != null) {
   1025             return mTabs.nextTab(h);
   1026         }
   1027         return TabStops.nextDefaultStop(h, TAB_INCREMENT);
   1028     }
   1029 
   1030     private static final int TAB_INCREMENT = 20;
   1031 }
   1032