/* * 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; import android.util.FloatMath; /** * 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. *

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.

*/ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback { 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); } 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. */ 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, outerwidth, align, spacingmult, spacingadd, 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. */ 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, outerwidth, align, spacingmult, spacingadd, metrics, includepad, trust); return this; } 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, outerwidth, align, spacingmult, spacingadd, metrics, includepad, true); } 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, outerwidth, align, spacingmult, spacingadd, metrics, includepad, trust); } /* package */ void init(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, 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; } else { spacing = metrics.descent - metrics.ascent; } if (spacingmult != 1 || spacingadd != 0) { spacing = (int)(spacing * spacingmult + spacingadd + 0.5f); } mBottom = spacing; if (includepad) { mDesc = spacing + metrics.top; } else { mDesc = spacing + metrics.ascent; } 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) FloatMath.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. */ 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 if boring. * @hide */ public static Metrics isBoring(CharSequence text, TextPaint paint, TextDirectionHeuristic textDir) { return isBoring(text, paint, textDir, 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. */ public static Metrics isBoring(CharSequence text, TextPaint paint, Metrics metrics) { return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, 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. * @hide */ public static Metrics isBoring(CharSequence text, TextPaint paint, TextDirectionHeuristic textDir, Metrics metrics) { char[] temp = TextUtils.obtain(500); int length = text.length(); boolean boring = true; outer: for (int i = 0; i < length; i += 500) { int j = i + 500; if (j > length) j = length; TextUtils.getChars(text, i, j, temp, 0); int n = j - i; for (int a = 0; a < n; a++) { char c = temp[a]; if (c == '\n' || c == '\t' || c >= FIRST_RIGHT_TO_LEFT) { boring = false; break outer; } } if (textDir != null && textDir.isRtl(temp, 0, n)) { boring = false; break outer; } } TextUtils.recycle(temp); if (boring && text instanceof Spanned) { Spanned sp = (Spanned) text; Object[] styles = sp.getSpans(0, length, ParagraphStyle.class); if (styles.length > 0) { boring = false; } } if (boring) { Metrics fm = metrics; if (fm == null) { fm = new Metrics(); } TextLine line = TextLine.obtain(); line.set(paint, text, 0, length, Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null); fm.width = (int) FloatMath.ceil(line.metrics(fm)); TextLine.recycle(line); return fm; } else { return null; } } @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 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. */ public void ellipsized(int start, int end) { mEllipsizedStart = start; mEllipsizedCount = end - start; } private static final char FIRST_RIGHT_TO_LEFT = '\u0590'; private String mDirect; private Paint mPaint; /* package */ int mBottom, mDesc; // for Direct private int mTopPadding, mBottomPadding; private float mMax; private int mEllipsizedWidth, mEllipsizedStart, mEllipsizedCount; private static final TextPaint sTemp = new TextPaint(); public static class Metrics extends Paint.FontMetricsInt { public int width; @Override public String toString() { return super.toString() + " width=" + width; } } }