1 /*
2  * Copyright (C) 2006 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.annotation.IntRange;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.compat.annotation.UnsupportedAppUsage;
23 import android.graphics.Canvas;
24 import android.graphics.Paint;
25 import android.graphics.Path;
26 import android.text.style.ParagraphStyle;
27 
28 /**
29  * A BoringLayout is a very simple Layout implementation for text that
30  * fits on a single line and is all left-to-right characters.
31  * You will probably never want to make one of these yourself;
32  * if you do, be sure to call {@link #isBoring} first to make sure
33  * the text meets the criteria.
34  * <p>This class is used by widgets to control text layout. You should not need
35  * to use this class directly unless you are implementing your own widget
36  * or custom display object, in which case
37  * you are encouraged to use a Layout instead of calling
38  * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
39  *  Canvas.drawText()} directly.</p>
40  */
41 public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback {
42 
43     /**
44      * Utility function to construct a BoringLayout instance.
45      *
46      * @param source the text to render
47      * @param paint the default paint for the layout
48      * @param outerWidth the wrapping width for the text
49      * @param align whether to left, right, or center the text
50      * @param spacingMult this value is no longer used by BoringLayout
51      * @param spacingAdd this value is no longer used by BoringLayout
52      * @param metrics {@code #Metrics} instance that contains information about FontMetrics and
53      *                line width
54      * @param includePad set whether to include extra space beyond font ascent and descent which is
55      *                   needed to avoid clipping in some scripts
56      */
make(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad)57     public static BoringLayout make(CharSequence source, TextPaint paint, int outerWidth,
58             Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics,
59             boolean includePad) {
60         return new BoringLayout(source, paint, outerWidth, align, spacingMult, spacingAdd, metrics,
61                 includePad);
62     }
63 
64     /**
65      * Utility function to construct a BoringLayout instance.
66      *
67      * @param source the text to render
68      * @param paint the default paint for the layout
69      * @param outerWidth the wrapping width for the text
70      * @param align whether to left, right, or center the text
71      * @param spacingmult this value is no longer used by BoringLayout
72      * @param spacingadd this value is no longer used by BoringLayout
73      * @param metrics {@code #Metrics} instance that contains information about FontMetrics and
74      *                line width
75      * @param includePad set whether to include extra space beyond font ascent and descent which is
76      *                   needed to avoid clipping in some scripts
77      * @param ellipsize whether to ellipsize the text if width of the text is longer than the
78      *                  requested width
79      * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is
80      *                        {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is
81      *                        not used, {@code outerWidth} is used instead
82      */
make(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)83     public static BoringLayout make(CharSequence source, TextPaint paint, int outerWidth,
84             Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics,
85             boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
86         return new BoringLayout(source, paint, outerWidth, align, spacingmult, spacingadd, metrics,
87                 includePad, ellipsize, ellipsizedWidth);
88     }
89 
90     /**
91      * Utility function to construct a BoringLayout instance.
92      *
93      * The spacing multiplier and additional amount spacing are not used by BoringLayout.
94      * {@link Layout#getSpacingMultiplier()} will return 1.0 and {@link Layout#getSpacingAdd()} will
95      * return 0.0.
96      *
97      * @param source the text to render
98      * @param paint the default paint for the layout
99      * @param outerWidth the wrapping width for the text
100      * @param align whether to left, right, or center the text
101      * @param metrics {@code #Metrics} instance that contains information about FontMetrics and
102      *                line width
103      * @param includePad set whether to include extra space beyond font ascent and descent which is
104      *                   needed to avoid clipping in some scripts
105      * @param ellipsize whether to ellipsize the text if width of the text is longer than the
106      *                  requested width. null if ellipsis is not applied.
107      * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is
108      *                        {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is
109      *                        not used, {@code outerWidth} is used instead
110      * @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts.
111      *                              False for keeping the first font's line height. If some glyphs
112      *                              requires larger vertical spaces, by passing true to this
113      *                              argument, the layout increase the line height to fit all glyphs.
114      */
make( @onNull CharSequence source, @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth, @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics, boolean includePad, @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, boolean useFallbackLineSpacing)115     public static @NonNull BoringLayout make(
116             @NonNull CharSequence source, @NonNull TextPaint paint,
117             @IntRange(from = 0) int outerWidth,
118             @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics,
119             boolean includePad, @Nullable TextUtils.TruncateAt ellipsize,
120             @IntRange(from = 0) int ellipsizedWidth, boolean useFallbackLineSpacing) {
121         return new BoringLayout(source, paint, outerWidth, align, 1f, 0f, metrics, includePad,
122                 ellipsize, ellipsizedWidth, useFallbackLineSpacing);
123     }
124 
125     /**
126      * Returns a BoringLayout for the specified text, potentially reusing
127      * this one if it is already suitable.  The caller must make sure that
128      * no one is still using this Layout.
129      *
130      * @param source the text to render
131      * @param paint the default paint for the layout
132      * @param outerwidth the wrapping width for the text
133      * @param align whether to left, right, or center the text
134      * @param spacingMult this value is no longer used by BoringLayout
135      * @param spacingAdd this value is no longer used by BoringLayout
136      * @param metrics {@code #Metrics} instance that contains information about FontMetrics and
137      *                line width
138      * @param includePad set whether to include extra space beyond font ascent and descent which is
139      *                   needed to avoid clipping in some scripts
140      */
replaceOrMake(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad)141     public BoringLayout replaceOrMake(CharSequence source, TextPaint paint, int outerwidth,
142             Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics,
143             boolean includePad) {
144         replaceWith(source, paint, outerwidth, align, spacingMult, spacingAdd);
145 
146         mEllipsizedWidth = outerwidth;
147         mEllipsizedStart = 0;
148         mEllipsizedCount = 0;
149         mUseFallbackLineSpacing = false;
150 
151         init(source, paint, align, metrics, includePad, true, false /* useFallbackLineSpacing */);
152         return this;
153     }
154 
155     /**
156      * Returns a BoringLayout for the specified text, potentially reusing
157      * this one if it is already suitable.  The caller must make sure that
158      * no one is still using this Layout.
159      *
160      * The spacing multiplier and additional amount spacing are not used by BoringLayout.
161      * {@link Layout#getSpacingMultiplier()} will return 1.0 and {@link Layout#getSpacingAdd()} will
162      * return 0.0.
163      *
164      * @param source the text to render
165      * @param paint the default paint for the layout
166      * @param outerWidth the wrapping width for the text
167      * @param align whether to left, right, or center the text
168      * @param metrics {@code #Metrics} instance that contains information about FontMetrics and
169      *                line width
170      * @param includePad set whether to include extra space beyond font ascent and descent which is
171      *                   needed to avoid clipping in some scripts
172      * @param ellipsize whether to ellipsize the text if width of the text is longer than the
173      *                  requested width. null if ellipsis not applied.
174      * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is
175      *                        {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is
176      *                        not used, {@code outerWidth} is used instead
177      * @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts.
178      *                              False for keeping the first font's line height. If some glyphs
179      *                              requires larger vertical spaces, by passing true to this
180      *                              argument, the layout increase the line height to fit all glyphs.
181      */
replaceOrMake(@onNull CharSequence source, @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth, @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics, boolean includePad, @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, boolean useFallbackLineSpacing)182     public @NonNull BoringLayout replaceOrMake(@NonNull CharSequence source,
183             @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth,
184             @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics, boolean includePad,
185             @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth,
186             boolean useFallbackLineSpacing) {
187         boolean trust;
188 
189         if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
190             replaceWith(source, paint, outerWidth, align, 1f, 0f);
191 
192             mEllipsizedWidth = outerWidth;
193             mEllipsizedStart = 0;
194             mEllipsizedCount = 0;
195             trust = true;
196         } else {
197             replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth, ellipsize, true, this),
198                     paint, outerWidth, align, 1f, 0f);
199 
200             mEllipsizedWidth = ellipsizedWidth;
201             trust = false;
202         }
203 
204         mUseFallbackLineSpacing = useFallbackLineSpacing;
205 
206         init(getText(), paint, align, metrics, includePad, trust,
207                 useFallbackLineSpacing);
208         return this;
209     }
210 
211     /**
212      * Returns a BoringLayout for the specified text, potentially reusing
213      * this one if it is already suitable.  The caller must make sure that
214      * no one is still using this Layout.
215      *
216      * @param source the text to render
217      * @param paint the default paint for the layout
218      * @param outerWidth the wrapping width for the text
219      * @param align whether to left, right, or center the text
220      * @param spacingMult this value is no longer used by BoringLayout
221      * @param spacingAdd this value is no longer used by BoringLayout
222      * @param metrics {@code #Metrics} instance that contains information about FontMetrics and
223      *                line width
224      * @param includePad set whether to include extra space beyond font ascent and descent which is
225      *                   needed to avoid clipping in some scripts
226      * @param ellipsize whether to ellipsize the text if width of the text is longer than the
227      *                  requested width
228      * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is
229      *                        {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is
230      *                        not used, {@code outerWidth} is used instead
231      */
replaceOrMake(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)232     public BoringLayout replaceOrMake(CharSequence source, TextPaint paint, int outerWidth,
233             Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics,
234             boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
235         return replaceOrMake(source, paint, outerWidth, align, metrics,
236                 includePad, ellipsize, ellipsizedWidth, false /* useFallbackLineSpacing */);
237     }
238 
239     /**
240      * @param source the text to render
241      * @param paint the default paint for the layout
242      * @param outerwidth the wrapping width for the text
243      * @param align whether to left, right, or center the text
244      * @param spacingMult this value is no longer used by BoringLayout
245      * @param spacingAdd this value is no longer used by BoringLayout
246      * @param metrics {@code #Metrics} instance that contains information about FontMetrics and
247      *                line width
248      * @param includePad set whether to include extra space beyond font ascent and descent which is
249      *                   needed to avoid clipping in some scripts
250      */
BoringLayout(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad)251     public BoringLayout(CharSequence source, TextPaint paint, int outerwidth, Alignment align,
252             float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad) {
253         super(source, paint, outerwidth, align, spacingMult, spacingAdd);
254 
255         mEllipsizedWidth = outerwidth;
256         mEllipsizedStart = 0;
257         mEllipsizedCount = 0;
258         mUseFallbackLineSpacing = false;
259 
260         init(source, paint, align, metrics, includePad, true, false /* useFallbackLineSpacing */);
261     }
262 
263     /**
264      *
265      * @param source the text to render
266      * @param paint the default paint for the layout
267      * @param outerWidth the wrapping width for the text
268      * @param align whether to left, right, or center the text
269      * @param spacingMult this value is no longer used by BoringLayout
270      * @param spacingAdd this value is no longer used by BoringLayout
271      * @param metrics {@code #Metrics} instance that contains information about FontMetrics and
272      *                line width
273      * @param includePad set whether to include extra space beyond font ascent and descent which is
274      *                   needed to avoid clipping in some scripts
275      * @param ellipsize whether to ellipsize the text if width of the text is longer than the
276      *                  requested {@code outerWidth}
277      * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is
278      *                        {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is
279      *                        not used, {@code outerWidth} is used instead
280      */
BoringLayout(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)281     public BoringLayout(CharSequence source, TextPaint paint, int outerWidth, Alignment align,
282             float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad,
283             TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
284         this(source, paint, outerWidth, align, spacingMult, spacingAdd, metrics, includePad,
285                 ellipsize, ellipsizedWidth, false /* fallbackLineSpacing */);
286     }
287 
288     /**
289      *
290      * @param source the text to render
291      * @param paint the default paint for the layout
292      * @param outerWidth the wrapping width for the text
293      * @param align whether to left, right, or center the text
294      * @param spacingMult this value is no longer used by BoringLayout
295      * @param spacingAdd this value is no longer used by BoringLayout
296      * @param metrics {@code #Metrics} instance that contains information about FontMetrics and
297      *                line width
298      * @param includePad set whether to include extra space beyond font ascent and descent which is
299      *                   needed to avoid clipping in some scripts
300      * @param ellipsize whether to ellipsize the text if width of the text is longer than the
301      *                  requested {@code outerWidth}. null if ellipsis is not applied.
302      * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is
303      *                        {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is
304      *                        not used, {@code outerWidth} is used instead
305      * @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts.
306      *                              False for keeping the first font's line height. If some glyphs
307      *                              requires larger vertical spaces, by passing true to this
308      *                              argument, the layout increase the line height to fit all glyphs.
309      */
BoringLayout( @onNull CharSequence source, @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth, @NonNull Alignment align, float spacingMult, float spacingAdd, @NonNull BoringLayout.Metrics metrics, boolean includePad, @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, boolean useFallbackLineSpacing)310     public BoringLayout(
311             @NonNull CharSequence source, @NonNull TextPaint paint,
312             @IntRange(from = 0) int outerWidth, @NonNull Alignment align, float spacingMult,
313             float spacingAdd, @NonNull BoringLayout.Metrics metrics, boolean includePad,
314             @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth,
315             boolean useFallbackLineSpacing) {
316         /*
317          * It is silly to have to call super() and then replaceWith(),
318          * but we can't use "this" for the callback until the call to
319          * super() finishes.
320          */
321         super(source, paint, outerWidth, align, spacingMult, spacingAdd);
322 
323         boolean trust;
324 
325         if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
326             mEllipsizedWidth = outerWidth;
327             mEllipsizedStart = 0;
328             mEllipsizedCount = 0;
329             trust = true;
330         } else {
331             replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth, ellipsize, true, this),
332                         paint, outerWidth, align, spacingMult, spacingAdd);
333 
334             mEllipsizedWidth = ellipsizedWidth;
335             trust = false;
336         }
337 
338         mUseFallbackLineSpacing = useFallbackLineSpacing;
339         init(getText(), paint, align, metrics, includePad, trust, useFallbackLineSpacing);
340     }
341 
init(CharSequence source, TextPaint paint, Alignment align, BoringLayout.Metrics metrics, boolean includePad, boolean trustWidth, boolean useFallbackLineSpacing)342     /* package */ void init(CharSequence source, TextPaint paint, Alignment align,
343             BoringLayout.Metrics metrics, boolean includePad, boolean trustWidth,
344             boolean useFallbackLineSpacing) {
345         int spacing;
346 
347         if (source instanceof String && align == Layout.Alignment.ALIGN_NORMAL) {
348             mDirect = source.toString();
349         } else {
350             mDirect = null;
351         }
352 
353         mPaint = paint;
354 
355         if (includePad) {
356             spacing = metrics.bottom - metrics.top;
357             mDesc = metrics.bottom;
358         } else {
359             spacing = metrics.descent - metrics.ascent;
360             mDesc = metrics.descent;
361         }
362 
363         mBottom = spacing;
364 
365         if (trustWidth) {
366             mMax = metrics.width;
367         } else {
368             /*
369              * If we have ellipsized, we have to actually calculate the
370              * width because the width that was passed in was for the
371              * full text, not the ellipsized form.
372              */
373             TextLine line = TextLine.obtain();
374             line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT,
375                     Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
376                     mEllipsizedStart, mEllipsizedStart + mEllipsizedCount, useFallbackLineSpacing);
377             mMax = (int) Math.ceil(line.metrics(null));
378             TextLine.recycle(line);
379         }
380 
381         if (includePad) {
382             mTopPadding = metrics.top - metrics.ascent;
383             mBottomPadding = metrics.bottom - metrics.descent;
384         }
385     }
386 
387     /**
388      * Determine and compute metrics if given text can be handled by BoringLayout.
389      *
390      * @param text a text
391      * @param paint a paint
392      * @return layout metric for the given text. null if given text is unable to be handled by
393      *         BoringLayout.
394      */
isBoring(CharSequence text, TextPaint paint)395     public static Metrics isBoring(CharSequence text, TextPaint paint) {
396         return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, null);
397     }
398 
399     /**
400      * Determine and compute metrics if given text can be handled by BoringLayout.
401      *
402      * @param text a text
403      * @param paint a paint
404      * @param metrics a metrics object to be recycled. If null is passed, this function creat new
405      *                object.
406      * @return layout metric for the given text. If metrics is not null, this method fills values
407      *         to given metrics object instead of allocating new metrics object. null if given text
408      *         is unable to be handled by BoringLayout.
409      */
isBoring(CharSequence text, TextPaint paint, Metrics metrics)410     public static Metrics isBoring(CharSequence text, TextPaint paint, Metrics metrics) {
411         return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, metrics);
412     }
413 
414     /**
415      * Returns true if the text contains any RTL characters, bidi format characters, or surrogate
416      * code units.
417      */
hasAnyInterestingChars(CharSequence text, int textLength)418     private static boolean hasAnyInterestingChars(CharSequence text, int textLength) {
419         final int MAX_BUF_LEN = 500;
420         final char[] buffer = TextUtils.obtain(MAX_BUF_LEN);
421         try {
422             for (int start = 0; start < textLength; start += MAX_BUF_LEN) {
423                 final int end = Math.min(start + MAX_BUF_LEN, textLength);
424 
425                 // No need to worry about getting half codepoints, since we consider surrogate code
426                 // units "interesting" as soon we see one.
427                 TextUtils.getChars(text, start, end, buffer, 0);
428 
429                 final int len = end - start;
430                 for (int i = 0; i < len; i++) {
431                     final char c = buffer[i];
432                     if (c == '\n' || c == '\t' || TextUtils.couldAffectRtl(c)) {
433                         return true;
434                     }
435                 }
436             }
437             return false;
438         } finally {
439             TextUtils.recycle(buffer);
440         }
441     }
442 
443     /**
444      * Returns null if not boring; the width, ascent, and descent in the
445      * provided Metrics object (or a new one if the provided one was null)
446      * if boring.
447      * @hide
448      */
449     @UnsupportedAppUsage
isBoring(CharSequence text, TextPaint paint, TextDirectionHeuristic textDir, Metrics metrics)450     public static Metrics isBoring(CharSequence text, TextPaint paint,
451             TextDirectionHeuristic textDir, Metrics metrics) {
452         return isBoring(text, paint, textDir, false /* useFallbackLineSpacing */, metrics);
453     }
454 
455     /**
456      * Returns null if not boring; the width, ascent, and descent in the
457      * provided Metrics object (or a new one if the provided one was null)
458      * if boring.
459      *
460      * @param text a text to be calculated text layout.
461      * @param paint a paint object used for styling.
462      * @param textDir a text direction.
463      * @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts.
464      *                              False for keeping the first font's line height. If some glyphs
465      *                              requires larger vertical spaces, by passing true to this
466      *                              argument, the layout increase the line height to fit all glyphs.
467      * @param metrics the out metrics.
468      * @return metrics on success. null if text cannot be rendered by BoringLayout.
469      */
isBoring(@onNull CharSequence text, @NonNull TextPaint paint, @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing, @Nullable Metrics metrics)470     public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
471             @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
472             @Nullable Metrics metrics) {
473         final int textLength = text.length();
474         if (hasAnyInterestingChars(text, textLength)) {
475            return null;  // There are some interesting characters. Not boring.
476         }
477         if (textDir != null && textDir.isRtl(text, 0, textLength)) {
478            return null;  // The heuristic considers the whole text RTL. Not boring.
479         }
480         if (text instanceof Spanned) {
481             Spanned sp = (Spanned) text;
482             Object[] styles = sp.getSpans(0, textLength, ParagraphStyle.class);
483             if (styles.length > 0) {
484                 return null;  // There are some ParagraphStyle spans. Not boring.
485             }
486         }
487 
488         Metrics fm = metrics;
489         if (fm == null) {
490             fm = new Metrics();
491         } else {
492             fm.reset();
493         }
494 
495         TextLine line = TextLine.obtain();
496         line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
497                 Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
498                 0 /* ellipsisStart, 0 since text has not been ellipsized at this point */,
499                 0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */,
500                 useFallbackLineSpacing);
501         fm.width = (int) Math.ceil(line.metrics(fm));
502         TextLine.recycle(line);
503 
504         return fm;
505     }
506 
507     @Override
getHeight()508     public int getHeight() {
509         return mBottom;
510     }
511 
512     @Override
getLineCount()513     public int getLineCount() {
514         return 1;
515     }
516 
517     @Override
getLineTop(int line)518     public int getLineTop(int line) {
519         if (line == 0)
520             return 0;
521         else
522             return mBottom;
523     }
524 
525     @Override
getLineDescent(int line)526     public int getLineDescent(int line) {
527         return mDesc;
528     }
529 
530     @Override
getLineStart(int line)531     public int getLineStart(int line) {
532         if (line == 0)
533             return 0;
534         else
535             return getText().length();
536     }
537 
538     @Override
getParagraphDirection(int line)539     public int getParagraphDirection(int line) {
540         return DIR_LEFT_TO_RIGHT;
541     }
542 
543     @Override
getLineContainsTab(int line)544     public boolean getLineContainsTab(int line) {
545         return false;
546     }
547 
548     @Override
getLineMax(int line)549     public float getLineMax(int line) {
550         return mMax;
551     }
552 
553     @Override
getLineWidth(int line)554     public float getLineWidth(int line) {
555         return (line == 0 ? mMax : 0);
556     }
557 
558     @Override
getLineDirections(int line)559     public final Directions getLineDirections(int line) {
560         return Layout.DIRS_ALL_LEFT_TO_RIGHT;
561     }
562 
563     @Override
getTopPadding()564     public int getTopPadding() {
565         return mTopPadding;
566     }
567 
568     @Override
getBottomPadding()569     public int getBottomPadding() {
570         return mBottomPadding;
571     }
572 
573     @Override
getEllipsisCount(int line)574     public int getEllipsisCount(int line) {
575         return mEllipsizedCount;
576     }
577 
578     @Override
getEllipsisStart(int line)579     public int getEllipsisStart(int line) {
580         return mEllipsizedStart;
581     }
582 
583     @Override
getEllipsizedWidth()584     public int getEllipsizedWidth() {
585         return mEllipsizedWidth;
586     }
587 
588     @Override
isFallbackLineSpacingEnabled()589     public boolean isFallbackLineSpacingEnabled() {
590         return mUseFallbackLineSpacing;
591     }
592 
593     // Override draw so it will be faster.
594     @Override
draw(Canvas c, Path highlight, Paint highlightpaint, int cursorOffset)595     public void draw(Canvas c, Path highlight, Paint highlightpaint,
596                      int cursorOffset) {
597         if (mDirect != null && highlight == null) {
598             c.drawText(mDirect, 0, mBottom - mDesc, mPaint);
599         } else {
600             super.draw(c, highlight, highlightpaint, cursorOffset);
601         }
602     }
603 
604     /**
605      * Callback for the ellipsizer to report what region it ellipsized.
606      */
ellipsized(int start, int end)607     public void ellipsized(int start, int end) {
608         mEllipsizedStart = start;
609         mEllipsizedCount = end - start;
610     }
611 
612     private String mDirect;
613     private Paint mPaint;
614     private boolean mUseFallbackLineSpacing;
615 
616     /* package */ int mBottom, mDesc;   // for Direct
617     private int mTopPadding, mBottomPadding;
618     private float mMax;
619     private int mEllipsizedWidth, mEllipsizedStart, mEllipsizedCount;
620 
621     public static class Metrics extends Paint.FontMetricsInt {
622         public int width;
623 
toString()624         @Override public String toString() {
625             return super.toString() + " width=" + width;
626         }
627 
reset()628         private void reset() {
629             top = 0;
630             bottom = 0;
631             ascent = 0;
632             descent = 0;
633             width = 0;
634             leading = 0;
635         }
636     }
637 }
638