TextLine.java revision 1e3ac18e7ad03e02819f3e1a89d6a80a2bb7645f
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            ReplacementSpan[] spans = mSpanned.getSpans(start, limit, ReplacementSpan.class);
131            spans = TextUtils.removeEmptySpans(spans, mSpanned, ReplacementSpan.class);
132            hasReplacement = spans.length > 0;
133        }
134
135        mCharsValid = hasReplacement || hasTabs || 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, ReplacementSpan.class);
151                    ReplacementSpan[] spans = mSpanned.getSpans(i, inext, ReplacementSpan.class);
152                    spans = TextUtils.removeEmptySpans(spans, mSpanned, ReplacementSpan.class);
153                    if (spans.length > 0) {
154                        // transition into a span
155                        chars[i - start] = '\ufffc';
156                        for (int j = i - start + 1, e = inext - start; j < e; ++j) {
157                            chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip
158                        }
159                    }
160                }
161            }
162        }
163        mTabs = tabStops;
164    }
165
166    /**
167     * Renders the TextLine.
168     *
169     * @param c the canvas to render on
170     * @param x the leading margin position
171     * @param top the top of the line
172     * @param y the baseline
173     * @param bottom the bottom of the line
174     */
175    void draw(Canvas c, float x, int top, int y, int bottom) {
176        if (!mHasTabs) {
177            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
178                drawRun(c, 0, 0, mLen, false, x, top, y, bottom, false);
179                return;
180            }
181            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
182                drawRun(c, 0, 0, mLen, true, x, top, y, bottom, false);
183                return;
184            }
185        }
186
187        float h = 0;
188        int[] runs = mDirections.mDirections;
189        RectF emojiRect = null;
190
191        int lastRunIndex = runs.length - 2;
192        for (int i = 0; i < runs.length; i += 2) {
193            int runStart = runs[i];
194            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
195            if (runLimit > mLen) {
196                runLimit = mLen;
197            }
198            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
199
200            int segstart = runStart;
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            spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
633
634            if (spans.length > 0) {
635                ReplacementSpan replacement = null;
636                for (int j = 0; j < spans.length; j++) {
637                    MetricAffectingSpan span = spans[j];
638                    if (span instanceof ReplacementSpan) {
639                        replacement = (ReplacementSpan)span;
640                    } else {
641                        span.updateMeasureState(wp);
642                    }
643                }
644
645                if (replacement != null) {
646                    // If we have a replacement span, we're moving either to
647                    // the start or end of this span.
648                    return after ? spanLimit : spanStart;
649                }
650            }
651        }
652
653        int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
654        int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
655        if (mCharsValid) {
656            return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart,
657                    flags, offset, cursorOpt);
658        } else {
659            return wp.getTextRunCursor(mText, mStart + spanStart,
660                    mStart + spanLimit, flags, mStart + offset, cursorOpt) - mStart;
661        }
662    }
663
664    /**
665     * @param wp
666     */
667    private static void expandMetricsFromPaint(FontMetricsInt fmi, TextPaint wp) {
668        final int previousTop     = fmi.top;
669        final int previousAscent  = fmi.ascent;
670        final int previousDescent = fmi.descent;
671        final int previousBottom  = fmi.bottom;
672        final int previousLeading = fmi.leading;
673
674        wp.getFontMetricsInt(fmi);
675
676        fmi.top     = Math.min(fmi.top,     previousTop);
677        fmi.ascent  = Math.min(fmi.ascent,  previousAscent);
678        fmi.descent = Math.max(fmi.descent, previousDescent);
679        fmi.bottom  = Math.max(fmi.bottom,  previousBottom);
680        fmi.leading = Math.max(fmi.leading, previousLeading);
681    }
682
683    /**
684     * Utility function for measuring and rendering text.  The text must
685     * not include a tab or emoji.
686     *
687     * @param wp the working paint
688     * @param start the start of the text
689     * @param end the end of the text
690     * @param runIsRtl true if the run is right-to-left
691     * @param c the canvas, can be null if rendering is not needed
692     * @param x the edge of the run closest to the leading margin
693     * @param top the top of the line
694     * @param y the baseline
695     * @param bottom the bottom of the line
696     * @param fmi receives metrics information, can be null
697     * @param needWidth true if the width of the run is needed
698     * @return the signed width of the run based on the run direction; only
699     * valid if needWidth is true
700     */
701    private float handleText(TextPaint wp, int start, int end,
702            int contextStart, int contextEnd, boolean runIsRtl,
703            Canvas c, float x, int top, int y, int bottom,
704            FontMetricsInt fmi, boolean needWidth) {
705
706        float ret = 0;
707
708        int runLen = end - start;
709        int contextLen = contextEnd - contextStart;
710        if (needWidth || (c != null && (wp.bgColor != 0 || runIsRtl))) {
711            int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
712            if (mCharsValid) {
713                ret = wp.getTextRunAdvances(mChars, start, runLen,
714                        contextStart, contextLen, flags, null, 0);
715            } else {
716                int delta = mStart;
717                ret = wp.getTextRunAdvances(mText, delta + start,
718                        delta + end, delta + contextStart, delta + contextEnd,
719                        flags, null, 0);
720            }
721        }
722
723        if (fmi != null) {
724            expandMetricsFromPaint(fmi, wp);
725        }
726
727        if (c != null) {
728            if (runIsRtl) {
729                x -= ret;
730            }
731
732            if (wp.bgColor != 0) {
733                int color = wp.getColor();
734                Paint.Style s = wp.getStyle();
735                wp.setColor(wp.bgColor);
736                wp.setStyle(Paint.Style.FILL);
737
738                c.drawRect(x, top, x + ret, bottom, wp);
739
740                wp.setStyle(s);
741                wp.setColor(color);
742            }
743
744            drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
745                    x, y + wp.baselineShift);
746        }
747
748        return runIsRtl ? -ret : ret;
749    }
750
751    /**
752     * Utility function for measuring and rendering a replacement.
753     *
754     * @param replacement the replacement
755     * @param wp the work paint
756     * @param runIndex the run index
757     * @param start the start of the run
758     * @param limit the limit of the run
759     * @param runIsRtl true if the run is right-to-left
760     * @param c the canvas, can be null if not rendering
761     * @param x the edge of the replacement closest to the leading margin
762     * @param top the top of the line
763     * @param y the baseline
764     * @param bottom the bottom of the line
765     * @param fmi receives metrics information, can be null
766     * @param needWidth true if the width of the replacement is needed
767     * @return the signed width of the run based on the run direction; only
768     * valid if needWidth is true
769     */
770    private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
771            int runIndex, int start, int limit, boolean runIsRtl, Canvas c,
772            float x, int top, int y, int bottom, FontMetricsInt fmi,
773            boolean needWidth) {
774
775        float ret = 0;
776
777        int textStart = mStart + start;
778        int textLimit = mStart + limit;
779
780        if (needWidth || (c != null && runIsRtl)) {
781            ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
782        }
783
784        if (c != null) {
785            if (runIsRtl) {
786                x -= ret;
787            }
788            replacement.draw(c, mText, textStart, textLimit,
789                    x, top, y, bottom, wp);
790        }
791
792        return runIsRtl ? -ret : ret;
793    }
794
795    /**
796     * Utility function for handling a unidirectional run.  The run must not
797     * contain tabs or emoji but can contain styles.
798     *
799     * @param runIndex the run index
800     * @param start the line-relative start of the run
801     * @param measureLimit the offset to measure to, between start and limit inclusive
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
805     * @param x the end of the run 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 is required
811     * @return the signed width of the run based on the run direction; only
812     * valid if needWidth is true
813     */
814    private float handleRun(int runIndex, int start, int measureLimit,
815            int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
816            int bottom, FontMetricsInt fmi, boolean needWidth) {
817
818        // Shaping needs to take into account context up to metric boundaries,
819        // but rendering needs to take into account character style boundaries.
820        // So we iterate through metric runs to get metric bounds,
821        // then within each metric run iterate through character style runs
822        // for the run bounds.
823        float ox = x;
824        for (int i = start, inext; i < measureLimit; i = inext) {
825            TextPaint wp = mWorkPaint;
826            wp.set(mPaint);
827
828            int mlimit;
829            if (mSpanned == null) {
830                inext = limit;
831                mlimit = measureLimit;
832            } else {
833                inext = mSpanned.nextSpanTransition(mStart + i, mStart + limit,
834                        MetricAffectingSpan.class) - mStart;
835
836                mlimit = inext < measureLimit ? inext : measureLimit;
837                MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + i,
838                        mStart + mlimit, MetricAffectingSpan.class);
839                spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
840
841                if (spans.length > 0) {
842                    ReplacementSpan replacement = null;
843                    for (int j = 0; j < spans.length; j++) {
844                        MetricAffectingSpan span = spans[j];
845                        if (span instanceof ReplacementSpan) {
846                            replacement = (ReplacementSpan)span;
847                        } else {
848                            // We might have a replacement that uses the draw
849                            // state, otherwise measure state would suffice.
850                            span.updateDrawState(wp);
851                        }
852                    }
853
854                    if (replacement != null) {
855                        x += handleReplacement(replacement, wp, runIndex, i,
856                                mlimit, runIsRtl, c, x, top, y, bottom, fmi,
857                                needWidth || mlimit < measureLimit);
858                        continue;
859                    }
860                }
861            }
862
863            if (mSpanned == null || c == null) {
864                x += handleText(wp, i, mlimit, i, inext, runIsRtl, c, x, top,
865                        y, bottom, fmi, needWidth || mlimit < measureLimit);
866            } else {
867                for (int j = i, jnext; j < mlimit; j = jnext) {
868                    jnext = mSpanned.nextSpanTransition(mStart + j,
869                            mStart + mlimit, CharacterStyle.class) - mStart;
870
871                    CharacterStyle[] spans = mSpanned.getSpans(mStart + j,
872                            mStart + jnext, CharacterStyle.class);
873                    spans = TextUtils.removeEmptySpans(spans, mSpanned, CharacterStyle.class);
874
875                    wp.set(mPaint);
876                    for (int k = 0; k < spans.length; k++) {
877                        CharacterStyle span = spans[k];
878                        span.updateDrawState(wp);
879                    }
880
881                    x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
882                            top, y, bottom, fmi, needWidth || jnext < measureLimit);
883                }
884            }
885        }
886
887        return x - ox;
888    }
889
890    /**
891     * Render a text run with the set-up paint.
892     *
893     * @param c the canvas
894     * @param wp the paint used to render the text
895     * @param start the start of the run
896     * @param end the end of the run
897     * @param contextStart the start of context for the run
898     * @param contextEnd the end of the context for the run
899     * @param runIsRtl true if the run is right-to-left
900     * @param x the x position of the left edge of the run
901     * @param y the baseline of the run
902     */
903    private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
904            int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
905
906        int flags = runIsRtl ? Canvas.DIRECTION_RTL : Canvas.DIRECTION_LTR;
907        if (mCharsValid) {
908            int count = end - start;
909            int contextCount = contextEnd - contextStart;
910            c.drawTextRun(mChars, start, count, contextStart, contextCount,
911                    x, y, flags, wp);
912        } else {
913            int delta = mStart;
914            c.drawTextRun(mText, delta + start, delta + end,
915                    delta + contextStart, delta + contextEnd, x, y, flags, wp);
916        }
917    }
918
919    /**
920     * Returns the ascent of the text at start.  This is used for scaling
921     * emoji.
922     *
923     * @param pos the line-relative position
924     * @return the ascent of the text at start
925     */
926    float ascent(int pos) {
927        if (mSpanned == null) {
928            return mPaint.ascent();
929        }
930
931        pos += mStart;
932        MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1,
933                MetricAffectingSpan.class);
934        if (spans.length == 0) {
935            return mPaint.ascent();
936        }
937
938        TextPaint wp = mWorkPaint;
939        wp.set(mPaint);
940        for (MetricAffectingSpan span : spans) {
941            span.updateMeasureState(wp);
942        }
943        return wp.ascent();
944    }
945
946    /**
947     * Returns the next tab position.
948     *
949     * @param h the (unsigned) offset from the leading margin
950     * @return the (unsigned) tab position after this offset
951     */
952    float nextTab(float h) {
953        if (mTabs != null) {
954            return mTabs.nextTab(h);
955        }
956        return TabStops.nextDefaultStop(h, TAB_INCREMENT);
957    }
958
959    private static final int TAB_INCREMENT = 20;
960}
961