2 * Copyright (C) 2006 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 import android.graphics.Canvas;
20 import android.graphics.Paint;
21 import android.graphics.Path;
22 import android.text.style.ParagraphStyle;
25 * A BoringLayout is a very simple Layout implementation for text that
26 * fits on a single line and is all left-to-right characters.
27 * You will probably never want to make one of these yourself;
28 * if you do, be sure to call {@link #isBoring} first to make sure
29 * the text meets the criteria.
30 * <p>This class is used by widgets to control text layout. You should not need
31 * to use this class directly unless you are implementing your own widget
32 * or custom display object, in which case
33 * you are encouraged to use a Layout instead of calling
34 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
35 * Canvas.drawText()} directly.</p>
37 public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback {
38 public static BoringLayout make(CharSequence source,
39 TextPaint paint, int outerwidth,
41 float spacingmult, float spacingadd,
42 BoringLayout.Metrics metrics, boolean includepad) {
43 return new BoringLayout(source, paint, outerwidth, align,
44 spacingmult, spacingadd, metrics,
48 public static BoringLayout make(CharSequence source,
49 TextPaint paint, int outerwidth,
51 float spacingmult, float spacingadd,
52 BoringLayout.Metrics metrics, boolean includepad,
53 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
54 return new BoringLayout(source, paint, outerwidth, align,
55 spacingmult, spacingadd, metrics,
56 includepad, ellipsize, ellipsizedWidth);
60 * Returns a BoringLayout for the specified text, potentially reusing
61 * this one if it is already suitable. The caller must make sure that
62 * no one is still using this Layout.
64 public BoringLayout replaceOrMake(CharSequence source, TextPaint paint,
65 int outerwidth, Alignment align,
66 float spacingmult, float spacingadd,
67 BoringLayout.Metrics metrics,
69 replaceWith(source, paint, outerwidth, align, spacingmult,
72 mEllipsizedWidth = outerwidth;
76 init(source, paint, outerwidth, align, spacingmult, spacingadd,
77 metrics, includepad, true);
82 * Returns a BoringLayout for the specified text, potentially reusing
83 * this one if it is already suitable. The caller must make sure that
84 * no one is still using this Layout.
86 public BoringLayout replaceOrMake(CharSequence source, TextPaint paint,
87 int outerwidth, Alignment align,
88 float spacingmult, float spacingadd,
89 BoringLayout.Metrics metrics,
91 TextUtils.TruncateAt ellipsize,
92 int ellipsizedWidth) {
95 if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
96 replaceWith(source, paint, outerwidth, align, spacingmult,
99 mEllipsizedWidth = outerwidth;
100 mEllipsizedStart = 0;
101 mEllipsizedCount = 0;
104 replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth,
105 ellipsize, true, this),
106 paint, outerwidth, align, spacingmult,
109 mEllipsizedWidth = ellipsizedWidth;
113 init(getText(), paint, outerwidth, align, spacingmult, spacingadd,
114 metrics, includepad, trust);
118 public BoringLayout(CharSequence source,
119 TextPaint paint, int outerwidth,
121 float spacingmult, float spacingadd,
122 BoringLayout.Metrics metrics, boolean includepad) {
123 super(source, paint, outerwidth, align, spacingmult, spacingadd);
125 mEllipsizedWidth = outerwidth;
126 mEllipsizedStart = 0;
127 mEllipsizedCount = 0;
129 init(source, paint, outerwidth, align, spacingmult, spacingadd,
130 metrics, includepad, true);
133 public BoringLayout(CharSequence source,
134 TextPaint paint, int outerwidth,
136 float spacingmult, float spacingadd,
137 BoringLayout.Metrics metrics, boolean includepad,
138 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
140 * It is silly to have to call super() and then replaceWith(),
141 * but we can't use "this" for the callback until the call to
144 super(source, paint, outerwidth, align, spacingmult, spacingadd);
148 if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
149 mEllipsizedWidth = outerwidth;
150 mEllipsizedStart = 0;
151 mEllipsizedCount = 0;
154 replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth,
155 ellipsize, true, this),
156 paint, outerwidth, align, spacingmult,
160 mEllipsizedWidth = ellipsizedWidth;
164 init(getText(), paint, outerwidth, align, spacingmult, spacingadd,
165 metrics, includepad, trust);
168 /* package */ void init(CharSequence source,
169 TextPaint paint, int outerwidth,
171 float spacingmult, float spacingadd,
172 BoringLayout.Metrics metrics, boolean includepad,
173 boolean trustWidth) {
176 if (source instanceof String && align == Layout.Alignment.ALIGN_NORMAL) {
177 mDirect = source.toString();
185 spacing = metrics.bottom - metrics.top;
187 spacing = metrics.descent - metrics.ascent;
193 mMax = metrics.width;
196 * If we have ellipsized, we have to actually calculate the
197 * width because the width that was passed in was for the
198 * full text, not the ellipsized form.
200 TextLine line = TextLine.obtain();
201 line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT,
202 Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
203 mMax = (int) Math.ceil(line.metrics(null));
204 TextLine.recycle(line);
208 mTopPadding = metrics.top - metrics.ascent;
209 mBottomPadding = metrics.bottom - metrics.descent;
212 mDesc = spacing + mBottomPadding + (includepad ? metrics.top : metrics.ascent);
216 * Returns null if not boring; the width, ascent, and descent if boring.
218 public static Metrics isBoring(CharSequence text,
220 return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, null);
224 * Returns null if not boring; the width, ascent, and descent if boring.
227 public static Metrics isBoring(CharSequence text,
229 TextDirectionHeuristic textDir) {
230 return isBoring(text, paint, textDir, null);
234 * Returns null if not boring; the width, ascent, and descent in the
235 * provided Metrics object (or a new one if the provided one was null)
238 public static Metrics isBoring(CharSequence text, TextPaint paint, Metrics metrics) {
239 return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, metrics);
243 * Returns null if not boring; the width, ascent, and descent in the
244 * provided Metrics object (or a new one if the provided one was null)
248 public static Metrics isBoring(CharSequence text, TextPaint paint,
249 TextDirectionHeuristic textDir, Metrics metrics) {
250 char[] temp = TextUtils.obtain(500);
251 int length = text.length();
252 boolean boring = true;
255 for (int i = 0; i < length; i += 500) {
261 TextUtils.getChars(text, i, j, temp, 0);
265 for (int a = 0; a < n; a++) {
268 if (c == '\n' || c == '\t' ||
269 (c >= 0x0590 && c <= 0x08FF) || // RTL scripts
270 c == 0x200F || // Bidi format character
271 (c >= 0x202A && c <= 0x202E) || // Bidi format characters
272 (c >= 0x2066 && c <= 0x2069) || // Bidi format characters
273 (c >= 0xD800 && c <= 0xDFFF) || // surrogate pairs
274 (c >= 0xFB1D && c <= 0xFDFF) || // Hebrew and Arabic presentation forms
275 (c >= 0xFE70 && c <= 0xFEFE) // Arabic presentation forms
282 if (textDir != null && textDir.isRtl(temp, 0, n)) {
288 TextUtils.recycle(temp);
290 if (boring && text instanceof Spanned) {
291 Spanned sp = (Spanned) text;
292 Object[] styles = sp.getSpans(0, length, ParagraphStyle.class);
293 if (styles.length > 0) {
299 Metrics fm = metrics;
304 TextLine line = TextLine.obtain();
305 line.set(paint, text, 0, length, Layout.DIR_LEFT_TO_RIGHT,
306 Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
307 fm.width = (int) Math.ceil(line.metrics(fm));
308 TextLine.recycle(line);
317 public int getHeight() {
322 public int getLineCount() {
327 public int getLineTop(int line) {
335 public int getLineDescent(int line) {
340 public int getLineStart(int line) {
344 return getText().length();
348 public int getParagraphDirection(int line) {
349 return DIR_LEFT_TO_RIGHT;
353 public boolean getLineContainsTab(int line) {
358 public float getLineMax(int line) {
363 public float getLineWidth(int line) {
364 return (line == 0 ? mMax : 0);
368 public final Directions getLineDirections(int line) {
369 return Layout.DIRS_ALL_LEFT_TO_RIGHT;
373 public int getTopPadding() {
378 public int getBottomPadding() {
379 return mBottomPadding;
383 public int getEllipsisCount(int line) {
384 return mEllipsizedCount;
388 public int getEllipsisStart(int line) {
389 return mEllipsizedStart;
393 public int getEllipsizedWidth() {
394 return mEllipsizedWidth;
397 // Override draw so it will be faster.
399 public void draw(Canvas c, Path highlight, Paint highlightpaint,
401 if (mDirect != null && highlight == null) {
402 c.drawText(mDirect, 0, mBottom - mDesc, mPaint);
404 super.draw(c, highlight, highlightpaint, cursorOffset);
409 * Callback for the ellipsizer to report what region it ellipsized.
411 public void ellipsized(int start, int end) {
412 mEllipsizedStart = start;
413 mEllipsizedCount = end - start;
416 private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
418 private String mDirect;
419 private Paint mPaint;
421 /* package */ int mBottom, mDesc; // for Direct
422 private int mTopPadding, mBottomPadding;
424 private int mEllipsizedWidth, mEllipsizedStart, mEllipsizedCount;
426 public static class Metrics extends Paint.FontMetricsInt {
429 @Override public String toString() {
430 return super.toString() + " width=" + width;