OSDN Git Service

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