blob: 77cd71efc3c3dc162bc3ba32704dca8ccb5a2c65 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.text;
18
19import android.graphics.Canvas;
20import android.graphics.Paint;
21import android.graphics.Path;
Eric Fischer86fcef82009-08-17 17:16:44 -070022import android.text.style.ParagraphStyle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.util.FloatMath;
24
25/**
26 * A BoringLayout is a very simple Layout implementation for text that
27 * fits on a single line and is all left-to-right characters.
28 * You will probably never want to make one of these yourself;
29 * if you do, be sure to call {@link #isBoring} first to make sure
30 * the text meets the criteria.
31 * <p>This class is used by widgets to control text layout. You should not need
32 * to use this class directly unless you are implementing your own widget
33 * or custom display object, in which case
34 * you are encouraged to use a Layout instead of calling
35 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
36 * Canvas.drawText()} directly.</p>
37 */
38public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback {
39 public static BoringLayout make(CharSequence source,
40 TextPaint paint, int outerwidth,
41 Alignment align,
42 float spacingmult, float spacingadd,
43 BoringLayout.Metrics metrics, boolean includepad) {
44 return new BoringLayout(source, paint, outerwidth, align,
45 spacingmult, spacingadd, metrics,
46 includepad);
47 }
48
49 public static BoringLayout make(CharSequence source,
50 TextPaint paint, int outerwidth,
51 Alignment align,
52 float spacingmult, float spacingadd,
53 BoringLayout.Metrics metrics, boolean includepad,
54 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
55 return new BoringLayout(source, paint, outerwidth, align,
56 spacingmult, spacingadd, metrics,
57 includepad, ellipsize, ellipsizedWidth);
58 }
59
60 /**
61 * Returns a BoringLayout for the specified text, potentially reusing
62 * this one if it is already suitable. The caller must make sure that
63 * no one is still using this Layout.
64 */
65 public BoringLayout replaceOrMake(CharSequence source, TextPaint paint,
66 int outerwidth, Alignment align,
67 float spacingmult, float spacingadd,
68 BoringLayout.Metrics metrics,
69 boolean includepad) {
70 replaceWith(source, paint, outerwidth, align, spacingmult,
71 spacingadd);
72
73 mEllipsizedWidth = outerwidth;
74 mEllipsizedStart = 0;
75 mEllipsizedCount = 0;
76
77 init(source, paint, outerwidth, align, spacingmult, spacingadd,
78 metrics, includepad, true);
79 return this;
80 }
81
82 /**
83 * Returns a BoringLayout for the specified text, potentially reusing
84 * this one if it is already suitable. The caller must make sure that
85 * no one is still using this Layout.
86 */
87 public BoringLayout replaceOrMake(CharSequence source, TextPaint paint,
88 int outerwidth, Alignment align,
89 float spacingmult, float spacingadd,
90 BoringLayout.Metrics metrics,
91 boolean includepad,
92 TextUtils.TruncateAt ellipsize,
93 int ellipsizedWidth) {
94 boolean trust;
95
96 if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
97 replaceWith(source, paint, outerwidth, align, spacingmult,
98 spacingadd);
99
100 mEllipsizedWidth = outerwidth;
101 mEllipsizedStart = 0;
102 mEllipsizedCount = 0;
103 trust = true;
104 } else {
105 replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth,
106 ellipsize, true, this),
107 paint, outerwidth, align, spacingmult,
108 spacingadd);
109
110 mEllipsizedWidth = ellipsizedWidth;
111 trust = false;
112 }
113
114 init(getText(), paint, outerwidth, align, spacingmult, spacingadd,
115 metrics, includepad, trust);
116 return this;
117 }
118
119 public BoringLayout(CharSequence source,
120 TextPaint paint, int outerwidth,
121 Alignment align,
122 float spacingmult, float spacingadd,
123 BoringLayout.Metrics metrics, boolean includepad) {
124 super(source, paint, outerwidth, align, spacingmult, spacingadd);
125
126 mEllipsizedWidth = outerwidth;
127 mEllipsizedStart = 0;
128 mEllipsizedCount = 0;
129
130 init(source, paint, outerwidth, align, spacingmult, spacingadd,
131 metrics, includepad, true);
132 }
133
134 public BoringLayout(CharSequence source,
135 TextPaint paint, int outerwidth,
136 Alignment align,
137 float spacingmult, float spacingadd,
138 BoringLayout.Metrics metrics, boolean includepad,
139 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
140 /*
141 * It is silly to have to call super() and then replaceWith(),
142 * but we can't use "this" for the callback until the call to
143 * super() finishes.
144 */
145 super(source, paint, outerwidth, align, spacingmult, spacingadd);
146
147 boolean trust;
148
149 if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
150 mEllipsizedWidth = outerwidth;
151 mEllipsizedStart = 0;
152 mEllipsizedCount = 0;
153 trust = true;
154 } else {
155 replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth,
156 ellipsize, true, this),
157 paint, outerwidth, align, spacingmult,
158 spacingadd);
159
160
161 mEllipsizedWidth = ellipsizedWidth;
162 trust = false;
163 }
164
165 init(getText(), paint, outerwidth, align, spacingmult, spacingadd,
166 metrics, includepad, trust);
167 }
168
169 /* package */ void init(CharSequence source,
170 TextPaint paint, int outerwidth,
171 Alignment align,
172 float spacingmult, float spacingadd,
173 BoringLayout.Metrics metrics, boolean includepad,
174 boolean trustWidth) {
175 int spacing;
176
177 if (source instanceof String && align == Layout.Alignment.ALIGN_NORMAL) {
178 mDirect = source.toString();
179 } else {
180 mDirect = null;
181 }
182
183 mPaint = paint;
184
185 if (includepad) {
186 spacing = metrics.bottom - metrics.top;
187 } else {
188 spacing = metrics.descent - metrics.ascent;
189 }
190
191 if (spacingmult != 1 || spacingadd != 0) {
192 spacing = (int)(spacing * spacingmult + spacingadd + 0.5f);
193 }
194
195 mBottom = spacing;
196
197 if (includepad) {
198 mDesc = spacing + metrics.top;
199 } else {
200 mDesc = spacing + metrics.ascent;
201 }
202
203 if (trustWidth) {
204 mMax = metrics.width;
205 } else {
206 /*
207 * If we have ellipsized, we have to actually calculate the
208 * width because the width that was passed in was for the
209 * full text, not the ellipsized form.
210 */
Doug Felte8e45f22010-03-29 14:58:40 -0700211 TextLine line = TextLine.obtain();
212 line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT,
213 Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
214 mMax = (int) FloatMath.ceil(line.metrics(null));
215 TextLine.recycle(line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 }
217
218 if (includepad) {
219 mTopPadding = metrics.top - metrics.ascent;
220 mBottomPadding = metrics.bottom - metrics.descent;
221 }
222 }
223
224 /**
225 * Returns null if not boring; the width, ascent, and descent if boring.
226 */
227 public static Metrics isBoring(CharSequence text,
228 TextPaint paint) {
Doug Feltcb3791202011-07-07 11:57:48 -0700229 return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, null);
230 }
231
232 /**
233 * Returns null if not boring; the width, ascent, and descent if boring.
234 * @hide
235 */
236 public static Metrics isBoring(CharSequence text,
237 TextPaint paint,
238 TextDirectionHeuristic textDir) {
239 return isBoring(text, paint, textDir, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800240 }
241
242 /**
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)
245 * if boring.
246 */
Gilles Debunnef483e512011-04-28 15:08:54 -0700247 public static Metrics isBoring(CharSequence text, TextPaint paint, Metrics metrics) {
Doug Feltcb3791202011-07-07 11:57:48 -0700248 return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, metrics);
249 }
250
251 /**
252 * Returns null if not boring; the width, ascent, and descent in the
253 * provided Metrics object (or a new one if the provided one was null)
254 * if boring.
255 * @hide
256 */
257 public static Metrics isBoring(CharSequence text, TextPaint paint,
258 TextDirectionHeuristic textDir, Metrics metrics) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800259 char[] temp = TextUtils.obtain(500);
Gilles Debunnef483e512011-04-28 15:08:54 -0700260 int length = text.length();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800261 boolean boring = true;
262
263 outer:
Gilles Debunnef483e512011-04-28 15:08:54 -0700264 for (int i = 0; i < length; i += 500) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265 int j = i + 500;
266
Gilles Debunnef483e512011-04-28 15:08:54 -0700267 if (j > length)
268 j = length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800269
270 TextUtils.getChars(text, i, j, temp, 0);
271
272 int n = j - i;
273
274 for (int a = 0; a < n; a++) {
275 char c = temp[a];
276
277 if (c == '\n' || c == '\t' || c >= FIRST_RIGHT_TO_LEFT) {
278 boring = false;
279 break outer;
280 }
281 }
Doug Feltcb3791202011-07-07 11:57:48 -0700282
Fabrice Di Meglio4b60c302011-08-17 16:56:55 -0700283 if (textDir != null && textDir.isRtl(temp, 0, n)) {
Doug Feltcb3791202011-07-07 11:57:48 -0700284 boring = false;
285 break outer;
286 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800287 }
288
289 TextUtils.recycle(temp);
290
Eric Fischer86fcef82009-08-17 17:16:44 -0700291 if (boring && text instanceof Spanned) {
292 Spanned sp = (Spanned) text;
Gilles Debunnef483e512011-04-28 15:08:54 -0700293 Object[] styles = sp.getSpans(0, length, ParagraphStyle.class);
Eric Fischer86fcef82009-08-17 17:16:44 -0700294 if (styles.length > 0) {
295 boring = false;
296 }
297 }
298
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800299 if (boring) {
300 Metrics fm = metrics;
301 if (fm == null) {
302 fm = new Metrics();
303 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800304
Doug Felte8e45f22010-03-29 14:58:40 -0700305 TextLine line = TextLine.obtain();
Gilles Debunnef483e512011-04-28 15:08:54 -0700306 line.set(paint, text, 0, length, Layout.DIR_LEFT_TO_RIGHT,
Doug Felte8e45f22010-03-29 14:58:40 -0700307 Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
308 fm.width = (int) FloatMath.ceil(line.metrics(fm));
309 TextLine.recycle(line);
310
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800311 return fm;
312 } else {
313 return null;
314 }
315 }
316
Gilles Debunnef483e512011-04-28 15:08:54 -0700317 @Override
318 public int getHeight() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800319 return mBottom;
320 }
321
Gilles Debunnef483e512011-04-28 15:08:54 -0700322 @Override
323 public int getLineCount() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800324 return 1;
325 }
326
Gilles Debunnef483e512011-04-28 15:08:54 -0700327 @Override
328 public int getLineTop(int line) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800329 if (line == 0)
330 return 0;
331 else
332 return mBottom;
333 }
334
Gilles Debunnef483e512011-04-28 15:08:54 -0700335 @Override
336 public int getLineDescent(int line) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800337 return mDesc;
338 }
339
Gilles Debunnef483e512011-04-28 15:08:54 -0700340 @Override
341 public int getLineStart(int line) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 if (line == 0)
343 return 0;
344 else
345 return getText().length();
346 }
347
Gilles Debunnef483e512011-04-28 15:08:54 -0700348 @Override
349 public int getParagraphDirection(int line) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800350 return DIR_LEFT_TO_RIGHT;
351 }
352
Gilles Debunnef483e512011-04-28 15:08:54 -0700353 @Override
354 public boolean getLineContainsTab(int line) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800355 return false;
356 }
357
Gilles Debunnef483e512011-04-28 15:08:54 -0700358 @Override
359 public float getLineMax(int line) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800360 return mMax;
361 }
362
Gilles Debunnef483e512011-04-28 15:08:54 -0700363 @Override
364 public final Directions getLineDirections(int line) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800365 return Layout.DIRS_ALL_LEFT_TO_RIGHT;
366 }
367
Gilles Debunnef483e512011-04-28 15:08:54 -0700368 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800369 public int getTopPadding() {
370 return mTopPadding;
371 }
372
Gilles Debunnef483e512011-04-28 15:08:54 -0700373 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800374 public int getBottomPadding() {
375 return mBottomPadding;
376 }
377
378 @Override
379 public int getEllipsisCount(int line) {
380 return mEllipsizedCount;
381 }
382
383 @Override
384 public int getEllipsisStart(int line) {
385 return mEllipsizedStart;
386 }
387
388 @Override
389 public int getEllipsizedWidth() {
390 return mEllipsizedWidth;
391 }
392
393 // Override draw so it will be faster.
394 @Override
395 public void draw(Canvas c, Path highlight, Paint highlightpaint,
396 int cursorOffset) {
397 if (mDirect != null && highlight == null) {
398 c.drawText(mDirect, 0, mBottom - mDesc, mPaint);
399 } else {
400 super.draw(c, highlight, highlightpaint, cursorOffset);
401 }
402 }
403
404 /**
405 * Callback for the ellipsizer to report what region it ellipsized.
406 */
407 public void ellipsized(int start, int end) {
408 mEllipsizedStart = start;
409 mEllipsizedCount = end - start;
410 }
411
412 private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
413
414 private String mDirect;
415 private Paint mPaint;
416
417 /* package */ int mBottom, mDesc; // for Direct
418 private int mTopPadding, mBottomPadding;
419 private float mMax;
420 private int mEllipsizedWidth, mEllipsizedStart, mEllipsizedCount;
421
422 private static final TextPaint sTemp =
423 new TextPaint();
424
425 public static class Metrics extends Paint.FontMetricsInt {
426 public int width;
Doug Felte8e45f22010-03-29 14:58:40 -0700427
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800428 @Override public String toString() {
429 return super.toString() + " width=" + width;
430 }
431 }
432}