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