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
17package android.text;
18
19import android.graphics.Bitmap;
20import android.graphics.Canvas;
21import android.graphics.Paint;
22import android.graphics.Paint.FontMetricsInt;
23import android.graphics.RectF;
24import android.text.Layout.Directions;
25import android.text.Layout.TabStops;
26import android.text.style.CharacterStyle;
27import android.text.style.MetricAffectingSpan;
28import android.text.style.ReplacementSpan;
29import android.util.Log;
30
31import 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 */
45class 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            for (int j = i, jnext; j < mlimit; j = jnext) {
943                jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + mlimit) -
944                        mStart;
945
946                wp.set(mPaint);
947                for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
948                    // Intentionally using >= and <= as explained above
949                    if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + jnext) ||
950                            (mCharacterStyleSpanSet.spanEnds[k] <= mStart + j)) continue;
951
952                    CharacterStyle span = mCharacterStyleSpanSet.spans[k];
953                    span.updateDrawState(wp);
954                }
955
956                x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
957                        top, y, bottom, fmi, needWidth || jnext < measureLimit);
958            }
959        }
960
961        return x - originalX;
962    }
963
964    /**
965     * Render a text run with the set-up paint.
966     *
967     * @param c the canvas
968     * @param wp the paint used to render the text
969     * @param start the start of the run
970     * @param end the end of the run
971     * @param contextStart the start of context for the run
972     * @param contextEnd the end of the context for the run
973     * @param runIsRtl true if the run is right-to-left
974     * @param x the x position of the left edge of the run
975     * @param y the baseline of the run
976     */
977    private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
978            int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
979
980        int flags = runIsRtl ? Canvas.DIRECTION_RTL : Canvas.DIRECTION_LTR;
981        if (mCharsValid) {
982            int count = end - start;
983            int contextCount = contextEnd - contextStart;
984            c.drawTextRun(mChars, start, count, contextStart, contextCount,
985                    x, y, flags, wp);
986        } else {
987            int delta = mStart;
988            c.drawTextRun(mText, delta + start, delta + end,
989                    delta + contextStart, delta + contextEnd, x, y, flags, wp);
990        }
991    }
992
993    /**
994     * Returns the ascent of the text at start.  This is used for scaling
995     * emoji.
996     *
997     * @param pos the line-relative position
998     * @return the ascent of the text at start
999     */
1000    float ascent(int pos) {
1001        if (mSpanned == null) {
1002            return mPaint.ascent();
1003        }
1004
1005        pos += mStart;
1006        MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1, MetricAffectingSpan.class);
1007        if (spans.length == 0) {
1008            return mPaint.ascent();
1009        }
1010
1011        TextPaint wp = mWorkPaint;
1012        wp.set(mPaint);
1013        for (MetricAffectingSpan span : spans) {
1014            span.updateMeasureState(wp);
1015        }
1016        return wp.ascent();
1017    }
1018
1019    /**
1020     * Returns the next tab position.
1021     *
1022     * @param h the (unsigned) offset from the leading margin
1023     * @return the (unsigned) tab position after this offset
1024     */
1025    float nextTab(float h) {
1026        if (mTabs != null) {
1027            return mTabs.nextTab(h);
1028        }
1029        return TabStops.nextDefaultStop(h, TAB_INCREMENT);
1030    }
1031
1032    private static final int TAB_INCREMENT = 20;
1033}
1034