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