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