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