TextLine.java revision 15c097a1c23105cdc0dd66dd5605ff35467d7118
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        float ret = 0;
710
711        int runLen = end - start;
712        int contextLen = contextEnd - contextStart;
713        if (needWidth || (c != null && (wp.bgColor != 0 || runIsRtl))) {
714            int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
715            if (mCharsValid) {
716                ret = wp.getTextRunAdvances(mChars, start, runLen,
717                        contextStart, contextLen, flags, null, 0);
718            } else {
719                int delta = mStart;
720                ret = wp.getTextRunAdvances(mText, delta + start,
721                        delta + end, delta + contextStart, delta + contextEnd,
722                        flags, null, 0);
723            }
724        }
725
726        if (fmi != null) {
727            expandMetricsFromPaint(fmi, wp);
728        }
729
730        if (c != null) {
731            if (runIsRtl) {
732                x -= ret;
733            }
734
735            if (wp.bgColor != 0) {
736                int color = wp.getColor();
737                Paint.Style s = wp.getStyle();
738                wp.setColor(wp.bgColor);
739                wp.setStyle(Paint.Style.FILL);
740
741                c.drawRect(x, top, x + ret, bottom, wp);
742
743                wp.setStyle(s);
744                wp.setColor(color);
745            }
746
747            drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
748                    x, y + wp.baselineShift);
749        }
750
751        return runIsRtl ? -ret : ret;
752    }
753
754    /**
755     * Utility function for measuring and rendering a replacement.
756     *
757     *
758     * @param replacement the replacement
759     * @param wp the work paint
760     * @param start the start of the run
761     * @param limit the limit of the run
762     * @param runIsRtl true if the run is right-to-left
763     * @param c the canvas, can be null if not rendering
764     * @param x the edge of the replacement closest to the leading margin
765     * @param top the top of the line
766     * @param y the baseline
767     * @param bottom the bottom of the line
768     * @param fmi receives metrics information, can be null
769     * @param needWidth true if the width of the replacement is needed
770     * @return the signed width of the run based on the run direction; only
771     * valid if needWidth is true
772     */
773    private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
774            int start, int limit, boolean runIsRtl, Canvas c,
775            float x, int top, int y, int bottom, FontMetricsInt fmi,
776            boolean needWidth) {
777
778        float ret = 0;
779
780        int textStart = mStart + start;
781        int textLimit = mStart + limit;
782
783        if (needWidth || (c != null && runIsRtl)) {
784            ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
785        }
786
787        if (c != null) {
788            if (runIsRtl) {
789                x -= ret;
790            }
791            replacement.draw(c, mText, textStart, textLimit,
792                    x, top, y, bottom, wp);
793        }
794
795        return runIsRtl ? -ret : ret;
796    }
797
798    /**
799     * Utility function for handling a unidirectional run.  The run must not
800     * contain tabs or emoji but can contain styles.
801     *
802     *
803     * @param start the line-relative start of the run
804     * @param measureLimit the offset to measure to, between start and limit inclusive
805     * @param limit the limit of the run
806     * @param runIsRtl true if the run is right-to-left
807     * @param c the canvas, can be null
808     * @param x the end of the run closest to the leading margin
809     * @param top the top of the line
810     * @param y the baseline
811     * @param bottom the bottom of the line
812     * @param fmi receives metrics information, can be null
813     * @param needWidth true if the width is required
814     * @return the signed width of the run based on the run direction; only
815     * valid if needWidth is true
816     */
817    private float handleRun(int start, int measureLimit,
818            int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
819            int bottom, FontMetricsInt fmi, boolean needWidth) {
820
821        // Case of an empty line, make sure we update fmi according to mPaint
822        if (start == measureLimit) {
823            TextPaint wp = mWorkPaint;
824            wp.set(mPaint);
825            if (fmi != null) {
826                expandMetricsFromPaint(fmi, wp);
827            }
828            return 0f;
829        }
830
831        // Shaping needs to take into account context up to metric boundaries,
832        // but rendering needs to take into account character style boundaries.
833        // So we iterate through metric runs to get metric bounds,
834        // then within each metric run iterate through character style runs
835        // for the run bounds.
836        float ox = x;
837        for (int i = start, inext; i < measureLimit; i = inext) {
838            TextPaint wp = mWorkPaint;
839            wp.set(mPaint);
840
841            int mlimit;
842            if (mSpanned == null) {
843                inext = limit;
844                mlimit = measureLimit;
845            } else {
846                inext = mSpanned.nextSpanTransition(mStart + i, mStart + limit,
847                        MetricAffectingSpan.class) - mStart;
848
849                mlimit = inext < measureLimit ? inext : measureLimit;
850                MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + i,
851                        mStart + mlimit, MetricAffectingSpan.class);
852                spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
853
854                if (spans.length > 0) {
855                    ReplacementSpan replacement = null;
856                    for (int j = 0; j < spans.length; j++) {
857                        MetricAffectingSpan span = spans[j];
858                        if (span instanceof ReplacementSpan) {
859                            replacement = (ReplacementSpan)span;
860                        } else {
861                            // We might have a replacement that uses the draw
862                            // state, otherwise measure state would suffice.
863                            span.updateDrawState(wp);
864                        }
865                    }
866
867                    if (replacement != null) {
868                        x += handleReplacement(replacement, wp, i,
869                                mlimit, runIsRtl, c, x, top, y, bottom, fmi,
870                                needWidth || mlimit < measureLimit);
871                        continue;
872                    }
873                }
874            }
875
876            if (mSpanned == null || c == null) {
877                x += handleText(wp, i, mlimit, i, inext, runIsRtl, c, x, top,
878                        y, bottom, fmi, needWidth || mlimit < measureLimit);
879            } else {
880                for (int j = i, jnext; j < mlimit; j = jnext) {
881                    jnext = mSpanned.nextSpanTransition(mStart + j,
882                            mStart + mlimit, CharacterStyle.class) - mStart;
883
884                    CharacterStyle[] spans = mSpanned.getSpans(mStart + j,
885                            mStart + jnext, CharacterStyle.class);
886                    spans = TextUtils.removeEmptySpans(spans, mSpanned, CharacterStyle.class);
887
888                    wp.set(mPaint);
889                    for (int k = 0; k < spans.length; k++) {
890                        CharacterStyle span = spans[k];
891                        span.updateDrawState(wp);
892                    }
893
894                    x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
895                            top, y, bottom, fmi, needWidth || jnext < measureLimit);
896                }
897            }
898        }
899
900        return x - ox;
901    }
902
903    /**
904     * Render a text run with the set-up paint.
905     *
906     * @param c the canvas
907     * @param wp the paint used to render the text
908     * @param start the start of the run
909     * @param end the end of the run
910     * @param contextStart the start of context for the run
911     * @param contextEnd the end of the context for the run
912     * @param runIsRtl true if the run is right-to-left
913     * @param x the x position of the left edge of the run
914     * @param y the baseline of the run
915     */
916    private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
917            int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
918
919        int flags = runIsRtl ? Canvas.DIRECTION_RTL : Canvas.DIRECTION_LTR;
920        if (mCharsValid) {
921            int count = end - start;
922            int contextCount = contextEnd - contextStart;
923            c.drawTextRun(mChars, start, count, contextStart, contextCount,
924                    x, y, flags, wp);
925        } else {
926            int delta = mStart;
927            c.drawTextRun(mText, delta + start, delta + end,
928                    delta + contextStart, delta + contextEnd, x, y, flags, wp);
929        }
930    }
931
932    /**
933     * Returns the ascent of the text at start.  This is used for scaling
934     * emoji.
935     *
936     * @param pos the line-relative position
937     * @return the ascent of the text at start
938     */
939    float ascent(int pos) {
940        if (mSpanned == null) {
941            return mPaint.ascent();
942        }
943
944        pos += mStart;
945        MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1,
946                MetricAffectingSpan.class);
947        if (spans.length == 0) {
948            return mPaint.ascent();
949        }
950
951        TextPaint wp = mWorkPaint;
952        wp.set(mPaint);
953        for (MetricAffectingSpan span : spans) {
954            span.updateMeasureState(wp);
955        }
956        return wp.ascent();
957    }
958
959    /**
960     * Returns the next tab position.
961     *
962     * @param h the (unsigned) offset from the leading margin
963     * @return the (unsigned) tab position after this offset
964     */
965    float nextTab(float h) {
966        if (mTabs != null) {
967            return mTabs.nextTab(h);
968        }
969        return TabStops.nextDefaultStop(h, TAB_INCREMENT);
970    }
971
972    private static final int TAB_INCREMENT = 20;
973}
974