blob: 4c8188801eff6b49e8cb5386cb83f38a362b30f4 [file] [log] [blame]
/*
* 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 static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
import android.annotation.FlaggedApi;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.text.LineBreakConfig;
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 {@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.
*
* @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.
*
* @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);
}
/**
* Utility function to construct a BoringLayout instance.
*
* The spacing multiplier and additional amount spacing are not used by BoringLayout.
* {@link Layout#getSpacingMultiplier()} will return 1.0 and {@link Layout#getSpacingAdd()} will
* return 0.0.
*
* @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 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. null if ellipsis is not applied.
* @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
* @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts.
* False for keeping the first font's line height. If some glyphs
* requires larger vertical spaces, by passing true to this
* argument, the layout increase the line height to fit all glyphs.
*/
public static @NonNull BoringLayout make(
@NonNull 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) {
return new BoringLayout(source, paint, outerWidth, align, 1f, 0f, metrics, includePad,
ellipsize, ellipsizedWidth, useFallbackLineSpacing);
}
/**
* 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;
mUseFallbackLineSpacing = false;
init(source, paint, align, metrics, includePad, true, false /* useFallbackLineSpacing */);
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.
*
* The spacing multiplier and additional amount spacing are not used by BoringLayout.
* {@link Layout#getSpacingMultiplier()} will return 1.0 and {@link Layout#getSpacingAdd()} will
* return 0.0.
*
* @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 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. null if ellipsis not applied.
* @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
* @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts.
* False for keeping the first font's line height. If some glyphs
* requires larger vertical spaces, by passing true to this
* argument, the layout increase the line height to fit all glyphs.
*/
public @NonNull BoringLayout replaceOrMake(@NonNull 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) {
return replaceOrMake(source, paint, outerWidth, align, 1.0f, 0.0f, metrics, includePad,
ellipsize, ellipsizedWidth, useFallbackLineSpacing, false /* useBoundsForWidth */,
null /* minimumFontMetrics */);
}
/** @hide */
public @NonNull BoringLayout replaceOrMake(@NonNull 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) {
boolean trust;
if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
replaceWith(source, paint, outerWidth, align, 1f, 0f);
mEllipsizedWidth = outerWidth;
mEllipsizedStart = 0;
mEllipsizedCount = 0;
trust = true;
} else {
replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth, ellipsize, true, this),
paint, outerWidth, align, spacingMultiplier, spacingAmount);
mEllipsizedWidth = ellipsizedWidth;
trust = false;
}
mUseFallbackLineSpacing = useFallbackLineSpacing;
init(getText(), paint, align, metrics, includePad, trust,
useFallbackLineSpacing);
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.
*
* @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) {
return replaceOrMake(source, paint, outerWidth, align, metrics,
includePad, ellipsize, ellipsizedWidth, false /* useFallbackLineSpacing */);
}
/**
* @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, TextDirectionHeuristics.LTR, spacingMult,
spacingAdd, includePad, false /* fallbackLineSpacing */,
outerwidth /* ellipsizedWidth */, null /* ellipsize */, 1 /* maxLines */,
BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null /* leftIndents */,
null /* rightIndents */, JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false,
null);
mEllipsizedWidth = outerwidth;
mEllipsizedStart = 0;
mEllipsizedCount = 0;
mUseFallbackLineSpacing = false;
init(source, paint, align, metrics, includePad, true, false /* useFallbackLineSpacing */);
}
/**
*
* @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) {
this(source, paint, outerWidth, align, spacingMult, spacingAdd, metrics, includePad,
ellipsize, ellipsizedWidth, false /* fallbackLineSpacing */);
}
/**
*
* @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}. null if ellipsis is not applied.
* @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
* @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts.
* False for keeping the first font's line height. If some glyphs
* requires larger vertical spaces, by passing true to this
* argument, the layout increase the line height to fit all glyphs.
*/
public BoringLayout(
@NonNull 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) {
/*
* 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.
*/
this(source, paint, outerWidth, align, TextDirectionHeuristics.LTR, spacingMult,
spacingAdd, includePad, useFallbackLineSpacing,
ellipsizedWidth, ellipsize, 1 /* maxLines */,
BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null /* leftIndents */,
null /* rightIndents */, JUSTIFICATION_MODE_NONE,
LineBreakConfig.NONE, metrics, false /* useBoundsForWidth */, null);
}
/** @hide */
public 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,
@Nullable Paint.FontMetrics minimumFontMetrics) {
this(text, paint, width, align, TextDirectionHeuristics.LTR,
spacingMult, spacingAdd, includePad, fallbackLineSpacing, ellipsizedWidth,
ellipsize, 1 /* maxLines */, Layout.BREAK_STRATEGY_SIMPLE,
Layout.HYPHENATION_FREQUENCY_NONE, null, null, Layout.JUSTIFICATION_MODE_NONE,
LineBreakConfig.NONE, metrics, useBoundsForWidth, minimumFontMetrics);
}
/* package */ 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,
@Nullable Paint.FontMetrics minimumFontMetrics) {
super(text, paint, width, align, textDir, spacingMult, spacingAdd, includePad,
fallbackLineSpacing, ellipsizedWidth, ellipsize, maxLines, breakStrategy,
hyphenationFrequency, leftIndents, rightIndents, justificationMode,
lineBreakConfig, useBoundsForWidth, minimumFontMetrics);
boolean trust;
if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
mEllipsizedWidth = width;
mEllipsizedStart = 0;
mEllipsizedCount = 0;
trust = true;
} else {
replaceWith(TextUtils.ellipsize(text, paint, ellipsizedWidth, ellipsize, true, this),
paint, width, align, spacingMult, spacingAdd);
mEllipsizedWidth = ellipsizedWidth;
trust = false;
}
mUseFallbackLineSpacing = fallbackLineSpacing;
init(getText(), paint, align, metrics, includePad, trust, fallbackLineSpacing);
}
/* package */ void init(CharSequence source, TextPaint paint, Alignment align,
BoringLayout.Metrics metrics, boolean includePad, boolean trustWidth,
boolean useFallbackLineSpacing) {
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,
mEllipsizedStart, mEllipsizedStart + mEllipsizedCount, useFallbackLineSpacing);
mMax = (int) Math.ceil(line.metrics(null, null, false));
TextLine.recycle(line);
}
if (includePad) {
mTopPadding = metrics.top - metrics.ascent;
mBottomPadding = metrics.bottom - metrics.descent;
}
mDrawingBounds.set(metrics.mDrawingBounds);
mDrawingBounds.offset(0, mBottom - mDesc);
}
/**
* Determine and compute metrics if given text can be handled by BoringLayout.
*
* @param text a text
* @param paint a paint
* @return layout metric for the given text. null if given text is unable to be handled by
* BoringLayout.
*/
public static Metrics isBoring(CharSequence text, TextPaint paint) {
return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, null);
}
/**
* Determine and compute metrics if given text can be handled by BoringLayout.
*
* @param text a text
* @param paint a paint
* @param metrics a metrics object to be recycled. If null is passed, this function creat new
* object.
* @return layout metric for the given text. If metrics is not null, this method fills values
* to given metrics object instead of allocating new metrics object. null if given text
* is unable to be handled by BoringLayout.
*/
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.
*/
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
*/
@UnsupportedAppUsage
public static Metrics isBoring(CharSequence text, TextPaint paint,
TextDirectionHeuristic textDir, Metrics metrics) {
return isBoring(text, paint, textDir, false /* useFallbackLineSpacing */, metrics);
}
/**
* 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.
*
* @param text a text to be calculated text layout.
* @param paint a paint object used for styling.
* @param textDir a text direction.
* @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts.
* False for keeping the first font's line height. If some glyphs
* requires larger vertical spaces, by passing true to this
* argument, the layout increase the line height to fit all glyphs.
* @param metrics the out metrics.
* @return metrics on success. null if text cannot be rendered by BoringLayout.
*/
public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
@NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
@Nullable Metrics metrics) {
return isBoring(text, paint, textDir, useFallbackLineSpacing, null, metrics);
}
/**
* @hide
*/
public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
@NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
@Nullable Paint.FontMetrics minimumFontMetrics, @Nullable 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 ParagraphStyle spans. Not boring.
}
}
Metrics fm = metrics;
if (fm == null) {
fm = new Metrics();
} else {
fm.reset();
}
if (ClientFlags.fixLineHeightForLocale()) {
if (minimumFontMetrics == null) {
paint.getFontMetricsIntForLocale(fm);
} else {
fm.set(minimumFontMetrics);
// Because the font metrics is provided by public APIs, adjust the top/bottom with
// ascent/descent: top must be smaller than ascent, bottom must be larger than
// descent.
fm.top = Math.min(fm.top, fm.ascent);
fm.bottom = Math.max(fm.bottom, fm.descent);
}
}
TextLine line = TextLine.obtain();
line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
0 /* ellipsisStart, 0 since text has not been ellipsized at this point */,
0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */,
useFallbackLineSpacing);
fm.width = (int) Math.ceil(line.metrics(fm, fm.mDrawingBounds, false));
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) {
if (getUseBoundsForWidth()) {
return super.getLineMax(line);
} else {
return mMax;
}
}
@Override
public float getLineWidth(int line) {
if (getUseBoundsForWidth()) {
return super.getLineWidth(line);
} else {
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
public boolean isFallbackLineSpacingEnabled() {
return mUseFallbackLineSpacing;
}
@Override
public @NonNull RectF computeDrawingBoundingBox() {
return mDrawingBounds;
}
// Override draw so it will be faster.
@Override
public void draw(Canvas c, Path highlight, Paint highlightpaint,
int cursorOffset) {
if (mDirect != null && highlight == null) {
if (getUseBoundsForWidth()) {
c.save();
RectF drawingRect = computeDrawingBoundingBox();
if (drawingRect.left < 0) {
c.translate(-drawingRect.left, 0);
}
}
c.drawText(mDirect, 0, mBottom - mDesc, mPaint);
if (getUseBoundsForWidth()) {
c.restore();
}
} else {
super.draw(c, highlight, highlightpaint, cursorOffset);
}
}
/**
* 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;
private boolean mUseFallbackLineSpacing;
/* package */ int mBottom, mDesc; // for Direct
private int mTopPadding, mBottomPadding;
private float mMax;
private int mEllipsizedWidth, mEllipsizedStart, mEllipsizedCount;
private final RectF mDrawingBounds = new RectF();
public static class Metrics extends Paint.FontMetricsInt {
public int width;
private final RectF mDrawingBounds = new RectF();
/**
* Returns drawing bounding box.
*
* @return a drawing bounding box.
*/
@FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
@NonNull public RectF getDrawingBoundingBox() {
return mDrawingBounds;
}
@Override public String toString() {
return super.toString() + " width=" + width + ", drawingBounds = " + mDrawingBounds;
}
private void reset() {
top = 0;
bottom = 0;
ascent = 0;
descent = 0;
width = 0;
leading = 0;
mDrawingBounds.setEmpty();
}
}
}