/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.text;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.text.style.ParagraphStyle;

A BoringLayout is a very simple Layout implementation for text that fits on a single line and is all left-to-right characters. You will probably never want to make one of these yourself; if you do, be sure to call isBoring first to make sure the text meets the criteria.

This class is used by widgets to control text layout. You should not need to use this class directly unless you are implementing your own widget or custom display object, in which case you are encouraged to use a Layout instead of calling Canvas.drawText() directly.

/** * A BoringLayout is a very simple Layout implementation for text that * fits on a single line and is all left-to-right characters. * You will probably never want to make one of these yourself; * if you do, be sure to call {@link #isBoring} first to make sure * the text meets the criteria. * <p>This class is used by widgets to control text layout. You should not need * to use this class directly unless you are implementing your own widget * or custom display object, in which case * you are encouraged to use a Layout instead of calling * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint) * Canvas.drawText()} directly.</p> */
public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback {
Utility function to construct a BoringLayout instance.
Params:
  • source – the text to render
  • paint – the default paint for the layout
  • outerWidth – the wrapping width for the text
  • align – whether to left, right, or center the text
  • spacingMult – this value is no longer used by BoringLayout
  • spacingAdd – this value is no longer used by BoringLayout
  • metrics – #Metrics instance that contains information about FontMetrics and line width
  • includePad – set whether to include extra space beyond font ascent and descent which is needed to avoid clipping in some scripts
/** * Utility function to construct a BoringLayout instance. * * @param source the text to render * @param paint the default paint for the layout * @param outerWidth the wrapping width for the text * @param align whether to left, right, or center the text * @param spacingMult this value is no longer used by BoringLayout * @param spacingAdd this value is no longer used by BoringLayout * @param metrics {@code #Metrics} instance that contains information about FontMetrics and * line width * @param includePad set whether to include extra space beyond font ascent and descent which is * needed to avoid clipping in some scripts */
public static BoringLayout make(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad) { return new BoringLayout(source, paint, outerWidth, align, spacingMult, spacingAdd, metrics, includePad); }
Utility function to construct a BoringLayout instance.
Params:
  • source – the text to render
  • paint – the default paint for the layout
  • outerWidth – the wrapping width for the text
  • align – whether to left, right, or center the text
  • spacingmult – this value is no longer used by BoringLayout
  • spacingadd – this value is no longer used by BoringLayout
  • metrics – #Metrics instance that contains information about FontMetrics and line width
  • includePad – set whether to include extra space beyond font ascent and descent which is needed to avoid clipping in some scripts
  • ellipsize – whether to ellipsize the text if width of the text is longer than the requested width
  • ellipsizedWidth – the width to which this Layout is ellipsizing. If ellipsize is null, or is TruncateAt.MARQUEE this value is not used, outerWidth is used instead
/** * Utility function to construct a BoringLayout instance. * * @param source the text to render * @param paint the default paint for the layout * @param outerWidth the wrapping width for the text * @param align whether to left, right, or center the text * @param spacingmult this value is no longer used by BoringLayout * @param spacingadd this value is no longer used by BoringLayout * @param metrics {@code #Metrics} instance that contains information about FontMetrics and * line width * @param includePad set whether to include extra space beyond font ascent and descent which is * needed to avoid clipping in some scripts * @param ellipsize whether to ellipsize the text if width of the text is longer than the * requested width * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is * not used, {@code outerWidth} is used instead */
public static BoringLayout make(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { return new BoringLayout(source, paint, outerWidth, align, spacingmult, spacingadd, metrics, includePad, ellipsize, ellipsizedWidth); }
Returns a BoringLayout for the specified text, potentially reusing this one if it is already suitable. The caller must make sure that no one is still using this Layout.
Params:
  • source – the text to render
  • paint – the default paint for the layout
  • outerwidth – the wrapping width for the text
  • align – whether to left, right, or center the text
  • spacingMult – this value is no longer used by BoringLayout
  • spacingAdd – this value is no longer used by BoringLayout
  • metrics – #Metrics instance that contains information about FontMetrics and line width
  • includePad – set whether to include extra space beyond font ascent and descent which is needed to avoid clipping in some scripts
/** * Returns a BoringLayout for the specified text, potentially reusing * this one if it is already suitable. The caller must make sure that * no one is still using this Layout. * * @param source the text to render * @param paint the default paint for the layout * @param outerwidth the wrapping width for the text * @param align whether to left, right, or center the text * @param spacingMult this value is no longer used by BoringLayout * @param spacingAdd this value is no longer used by BoringLayout * @param metrics {@code #Metrics} instance that contains information about FontMetrics and * line width * @param includePad set whether to include extra space beyond font ascent and descent which is * needed to avoid clipping in some scripts */
public BoringLayout replaceOrMake(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad) { replaceWith(source, paint, outerwidth, align, spacingMult, spacingAdd); mEllipsizedWidth = outerwidth; mEllipsizedStart = 0; mEllipsizedCount = 0; init(source, paint, align, metrics, includePad, true); return this; }
Returns a BoringLayout for the specified text, potentially reusing this one if it is already suitable. The caller must make sure that no one is still using this Layout.
Params:
  • source – the text to render
  • paint – the default paint for the layout
  • outerWidth – the wrapping width for the text
  • align – whether to left, right, or center the text
  • spacingMult – this value is no longer used by BoringLayout
  • spacingAdd – this value is no longer used by BoringLayout
  • metrics – #Metrics instance that contains information about FontMetrics and line width
  • includePad – set whether to include extra space beyond font ascent and descent which is needed to avoid clipping in some scripts
  • ellipsize – whether to ellipsize the text if width of the text is longer than the requested width
  • ellipsizedWidth – the width to which this Layout is ellipsizing. If ellipsize is null, or is TruncateAt.MARQUEE this value is not used, outerwidth is used instead
/** * Returns a BoringLayout for the specified text, potentially reusing * this one if it is already suitable. The caller must make sure that * no one is still using this Layout. * * @param source the text to render * @param paint the default paint for the layout * @param outerWidth the wrapping width for the text * @param align whether to left, right, or center the text * @param spacingMult this value is no longer used by BoringLayout * @param spacingAdd this value is no longer used by BoringLayout * @param metrics {@code #Metrics} instance that contains information about FontMetrics and * line width * @param includePad set whether to include extra space beyond font ascent and descent which is * needed to avoid clipping in some scripts * @param ellipsize whether to ellipsize the text if width of the text is longer than the * requested width * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is * not used, {@code outerwidth} is used instead */
public BoringLayout replaceOrMake(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { boolean trust; if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) { replaceWith(source, paint, outerWidth, align, spacingMult, spacingAdd); mEllipsizedWidth = outerWidth; mEllipsizedStart = 0; mEllipsizedCount = 0; trust = true; } else { replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth, ellipsize, true, this), paint, outerWidth, align, spacingMult, spacingAdd); mEllipsizedWidth = ellipsizedWidth; trust = false; } init(getText(), paint, align, metrics, includePad, trust); return this; }
Params:
  • source – the text to render
  • paint – the default paint for the layout
  • outerwidth – the wrapping width for the text
  • align – whether to left, right, or center the text
  • spacingMult – this value is no longer used by BoringLayout
  • spacingAdd – this value is no longer used by BoringLayout
  • metrics – #Metrics instance that contains information about FontMetrics and line width
  • includePad – set whether to include extra space beyond font ascent and descent which is needed to avoid clipping in some scripts
/** * @param source the text to render * @param paint the default paint for the layout * @param outerwidth the wrapping width for the text * @param align whether to left, right, or center the text * @param spacingMult this value is no longer used by BoringLayout * @param spacingAdd this value is no longer used by BoringLayout * @param metrics {@code #Metrics} instance that contains information about FontMetrics and * line width * @param includePad set whether to include extra space beyond font ascent and descent which is * needed to avoid clipping in some scripts */
public BoringLayout(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad) { super(source, paint, outerwidth, align, spacingMult, spacingAdd); mEllipsizedWidth = outerwidth; mEllipsizedStart = 0; mEllipsizedCount = 0; init(source, paint, align, metrics, includePad, true); }
Params:
  • source – the text to render
  • paint – the default paint for the layout
  • outerWidth – the wrapping width for the text
  • align – whether to left, right, or center the text
  • spacingMult – this value is no longer used by BoringLayout
  • spacingAdd – this value is no longer used by BoringLayout
  • metrics – #Metrics instance that contains information about FontMetrics and line width
  • includePad – set whether to include extra space beyond font ascent and descent which is needed to avoid clipping in some scripts
  • ellipsize – whether to ellipsize the text if width of the text is longer than the requested outerwidth
  • ellipsizedWidth – the width to which this Layout is ellipsizing. If ellipsize is null, or is TruncateAt.MARQUEE this value is not used, outerwidth is used instead
/** * * @param source the text to render * @param paint the default paint for the layout * @param outerWidth the wrapping width for the text * @param align whether to left, right, or center the text * @param spacingMult this value is no longer used by BoringLayout * @param spacingAdd this value is no longer used by BoringLayout * @param metrics {@code #Metrics} instance that contains information about FontMetrics and * line width * @param includePad set whether to include extra space beyond font ascent and descent which is * needed to avoid clipping in some scripts * @param ellipsize whether to ellipsize the text if width of the text is longer than the * requested {@code outerwidth} * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is * not used, {@code outerwidth} is used instead */
public BoringLayout(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { /* * It is silly to have to call super() and then replaceWith(), * but we can't use "this" for the callback until the call to * super() finishes. */ super(source, paint, outerWidth, align, spacingMult, spacingAdd); boolean trust; if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) { mEllipsizedWidth = outerWidth; mEllipsizedStart = 0; mEllipsizedCount = 0; trust = true; } else { replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth, ellipsize, true, this), paint, outerWidth, align, spacingMult, spacingAdd); mEllipsizedWidth = ellipsizedWidth; trust = false; } init(getText(), paint, align, metrics, includePad, trust); } /* package */ void init(CharSequence source, TextPaint paint, Alignment align, BoringLayout.Metrics metrics, boolean includePad, boolean trustWidth) { int spacing; if (source instanceof String && align == Layout.Alignment.ALIGN_NORMAL) { mDirect = source.toString(); } else { mDirect = null; } mPaint = paint; if (includePad) { spacing = metrics.bottom - metrics.top; mDesc = metrics.bottom; } else { spacing = metrics.descent - metrics.ascent; mDesc = metrics.descent; } mBottom = spacing; if (trustWidth) { mMax = metrics.width; } else { /* * If we have ellipsized, we have to actually calculate the * width because the width that was passed in was for the * full text, not the ellipsized form. */ TextLine line = TextLine.obtain(); line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null); mMax = (int) Math.ceil(line.metrics(null)); TextLine.recycle(line); } if (includePad) { mTopPadding = metrics.top - metrics.ascent; mBottomPadding = metrics.bottom - metrics.descent; } }
Returns null if not boring; the width, ascent, and descent if boring.
/** * Returns null if not boring; the width, ascent, and descent if boring. */
public static Metrics isBoring(CharSequence text, TextPaint paint) { return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, null); }
Returns null if not boring; the width, ascent, and descent in the provided Metrics object (or a new one if the provided one was null) if boring.
/** * Returns null if not boring; the width, ascent, and descent in the * provided Metrics object (or a new one if the provided one was null) * if boring. */
public static Metrics isBoring(CharSequence text, TextPaint paint, Metrics metrics) { return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, metrics); }
Returns true if the text contains any RTL characters, bidi format characters, or surrogate code units.
/** * Returns true if the text contains any RTL characters, bidi format characters, or surrogate * code units. */
private static boolean hasAnyInterestingChars(CharSequence text, int textLength) { final int MAX_BUF_LEN = 500; final char[] buffer = TextUtils.obtain(MAX_BUF_LEN); try { for (int start = 0; start < textLength; start += MAX_BUF_LEN) { final int end = Math.min(start + MAX_BUF_LEN, textLength); // No need to worry about getting half codepoints, since we consider surrogate code // units "interesting" as soon we see one. TextUtils.getChars(text, start, end, buffer, 0); final int len = end - start; for (int i = 0; i < len; i++) { final char c = buffer[i]; if (c == '\n' || c == '\t' || TextUtils.couldAffectRtl(c)) { return true; } } } return false; } finally { TextUtils.recycle(buffer); } }
Returns null if not boring; the width, ascent, and descent in the provided Metrics object (or a new one if the provided one was null) if boring.
@hide
/** * Returns null if not boring; the width, ascent, and descent in the * provided Metrics object (or a new one if the provided one was null) * if boring. * @hide */
public static Metrics isBoring(CharSequence text, TextPaint paint, TextDirectionHeuristic textDir, Metrics metrics) { final int textLength = text.length(); if (hasAnyInterestingChars(text, textLength)) { return null; // There are some interesting characters. Not boring. } if (textDir != null && textDir.isRtl(text, 0, textLength)) { return null; // The heuristic considers the whole text RTL. Not boring. } if (text instanceof Spanned) { Spanned sp = (Spanned) text; Object[] styles = sp.getSpans(0, textLength, ParagraphStyle.class); if (styles.length > 0) { return null; // There are some PargraphStyle spans. Not boring. } } Metrics fm = metrics; if (fm == null) { fm = new Metrics(); } else { fm.reset(); } TextLine line = TextLine.obtain(); line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null); fm.width = (int) Math.ceil(line.metrics(fm)); TextLine.recycle(line); return fm; } @Override public int getHeight() { return mBottom; } @Override public int getLineCount() { return 1; } @Override public int getLineTop(int line) { if (line == 0) return 0; else return mBottom; } @Override public int getLineDescent(int line) { return mDesc; } @Override public int getLineStart(int line) { if (line == 0) return 0; else return getText().length(); } @Override public int getParagraphDirection(int line) { return DIR_LEFT_TO_RIGHT; } @Override public boolean getLineContainsTab(int line) { return false; } @Override public float getLineMax(int line) { return mMax; } @Override public float getLineWidth(int line) { return (line == 0 ? mMax : 0); } @Override public final Directions getLineDirections(int line) { return Layout.DIRS_ALL_LEFT_TO_RIGHT; } @Override public int getTopPadding() { return mTopPadding; } @Override public int getBottomPadding() { return mBottomPadding; } @Override public int getEllipsisCount(int line) { return mEllipsizedCount; } @Override public int getEllipsisStart(int line) { return mEllipsizedStart; } @Override public int getEllipsizedWidth() { return mEllipsizedWidth; } // Override draw so it will be faster. @Override public void draw(Canvas c, Path highlight, Paint highlightpaint, int cursorOffset) { if (mDirect != null && highlight == null) { c.drawText(mDirect, 0, mBottom - mDesc, mPaint); } else { super.draw(c, highlight, highlightpaint, cursorOffset); } }
Callback for the ellipsizer to report what region it ellipsized.
/** * Callback for the ellipsizer to report what region it ellipsized. */
public void ellipsized(int start, int end) { mEllipsizedStart = start; mEllipsizedCount = end - start; } private String mDirect; private Paint mPaint; /* package */ int mBottom, mDesc; // for Direct private int mTopPadding, mBottomPadding; private float mMax; private int mEllipsizedWidth, mEllipsizedStart, mEllipsizedCount; public static class Metrics extends Paint.FontMetricsInt { public int width; @Override public String toString() { return super.toString() + " width=" + width; } private void reset() { top = 0; bottom = 0; ascent = 0; descent = 0; width = 0; leading = 0; } } }