blob: 328fe99a7f936c83cbb41f248bc04b4bdfc5d727 [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 -080023
24/**
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>
36 */
37public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback {
38 public static BoringLayout make(CharSequence source,
39 TextPaint paint, int outerwidth,
40 Alignment align,
41 float spacingmult, float spacingadd,
42 BoringLayout.Metrics metrics, boolean includepad) {
43 return new BoringLayout(source, paint, outerwidth, align,
44 spacingmult, spacingadd, metrics,
45 includepad);
46 }
47
48 public static BoringLayout make(CharSequence source,
49 TextPaint paint, int outerwidth,
50 Alignment align,
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);
57 }
58
59 /**
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.
63 */
64 public BoringLayout replaceOrMake(CharSequence source, TextPaint paint,
65 int outerwidth, Alignment align,
66 float spacingmult, float spacingadd,
67 BoringLayout.Metrics metrics,
68 boolean includepad) {
69 replaceWith(source, paint, outerwidth, align, spacingmult,
70 spacingadd);
71
72 mEllipsizedWidth = outerwidth;
73 mEllipsizedStart = 0;
74 mEllipsizedCount = 0;
75
76 init(source, paint, outerwidth, align, spacingmult, spacingadd,
77 metrics, includepad, true);
78 return this;
79 }
80
81 /**
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.
85 */
86 public BoringLayout replaceOrMake(CharSequence source, TextPaint paint,
87 int outerwidth, Alignment align,
88 float spacingmult, float spacingadd,
89 BoringLayout.Metrics metrics,
90 boolean includepad,
91 TextUtils.TruncateAt ellipsize,
92 int ellipsizedWidth) {
93 boolean trust;
94
95 if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
96 replaceWith(source, paint, outerwidth, align, spacingmult,
97 spacingadd);
98
99 mEllipsizedWidth = outerwidth;
100 mEllipsizedStart = 0;
101 mEllipsizedCount = 0;
102 trust = true;
103 } else {
104 replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth,
105 ellipsize, true, this),
106 paint, outerwidth, align, spacingmult,
107 spacingadd);
108
109 mEllipsizedWidth = ellipsizedWidth;
110 trust = false;
111 }
112
113 init(getText(), paint, outerwidth, align, spacingmult, spacingadd,
114 metrics, includepad, trust);
115 return this;
116 }
117
118 public BoringLayout(CharSequence source,
119 TextPaint paint, int outerwidth,
120 Alignment align,
121 float spacingmult, float spacingadd,
122 BoringLayout.Metrics metrics, boolean includepad) {
123 super(source, paint, outerwidth, align, spacingmult, spacingadd);
124
125 mEllipsizedWidth = outerwidth;
126 mEllipsizedStart = 0;
127 mEllipsizedCount = 0;
128
129 init(source, paint, outerwidth, align, spacingmult, spacingadd,
130 metrics, includepad, true);
131 }
132
133 public BoringLayout(CharSequence source,
134 TextPaint paint, int outerwidth,
135 Alignment align,
136 float spacingmult, float spacingadd,
137 BoringLayout.Metrics metrics, boolean includepad,
138 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
139 /*
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
142 * super() finishes.
143 */
144 super(source, paint, outerwidth, align, spacingmult, spacingadd);
145
146 boolean trust;
147
148 if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
149 mEllipsizedWidth = outerwidth;
150 mEllipsizedStart = 0;
151 mEllipsizedCount = 0;
152 trust = true;
153 } else {
154 replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth,
155 ellipsize, true, this),
156 paint, outerwidth, align, spacingmult,
157 spacingadd);
158
159
160 mEllipsizedWidth = ellipsizedWidth;
161 trust = false;
162 }
163
164 init(getText(), paint, outerwidth, align, spacingmult, spacingadd,
165 metrics, includepad, trust);
166 }
167
168 /* package */ void init(CharSequence source,
169 TextPaint paint, int outerwidth,
170 Alignment align,
171 float spacingmult, float spacingadd,
172 BoringLayout.Metrics metrics, boolean includepad,
173 boolean trustWidth) {
174 int spacing;
175
176 if (source instanceof String && align == Layout.Alignment.ALIGN_NORMAL) {
177 mDirect = source.toString();
178 } else {
179 mDirect = null;
180 }
181
182 mPaint = paint;
183
184 if (includepad) {
185 spacing = metrics.bottom - metrics.top;
186 } else {
187 spacing = metrics.descent - metrics.ascent;
188 }
189
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800190 mBottom = spacing;
191
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800192 if (trustWidth) {
193 mMax = metrics.width;
194 } else {
195 /*
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.
199 */
Doug Felte8e45f22010-03-29 14:58:40 -0700200 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);
Neil Fuller33253a42014-10-01 11:55:10 +0100203 mMax = (int) Math.ceil(line.metrics(null));
Doug Felte8e45f22010-03-29 14:58:40 -0700204 TextLine.recycle(line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800205 }
206
207 if (includepad) {
208 mTopPadding = metrics.top - metrics.ascent;
209 mBottomPadding = metrics.bottom - metrics.descent;
210 }
Siyamed Sinir715589f2016-02-12 18:03:55 -0800211
212 mDesc = spacing + mBottomPadding + (includepad ? metrics.top : metrics.ascent);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800213 }
214
215 /**
216 * Returns null if not boring; the width, ascent, and descent if boring.
217 */
218 public static Metrics isBoring(CharSequence text,
219 TextPaint paint) {
Doug Feltcb3791202011-07-07 11:57:48 -0700220 return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, null);
221 }
222
223 /**
224 * Returns null if not boring; the width, ascent, and descent if boring.
225 * @hide
226 */
227 public static Metrics isBoring(CharSequence text,
228 TextPaint paint,
229 TextDirectionHeuristic textDir) {
230 return isBoring(text, paint, textDir, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231 }
232
233 /**
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)
236 * if boring.
237 */
Gilles Debunnef483e512011-04-28 15:08:54 -0700238 public static Metrics isBoring(CharSequence text, TextPaint paint, Metrics metrics) {
Doug Feltcb3791202011-07-07 11:57:48 -0700239 return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, metrics);
240 }
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 * @hide
247 */
248 public static Metrics isBoring(CharSequence text, TextPaint paint,
249 TextDirectionHeuristic textDir, Metrics metrics) {
Roozbeh Pournader3cf82082016-03-16 14:33:51 -0700250 final int MAX_BUF_LEN = 500;
251 final char[] buffer = TextUtils.obtain(MAX_BUF_LEN);
252 final int textLength = text.length();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800253 boolean boring = true;
254
255 outer:
Roozbeh Pournader3cf82082016-03-16 14:33:51 -0700256 for (int start = 0; start < textLength; start += MAX_BUF_LEN) {
257 final int end = Math.min(start + MAX_BUF_LEN, textLength);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800258
Roozbeh Pournader3cf82082016-03-16 14:33:51 -0700259 // No need to worry about getting half codepoints, since we reject surrogate code units
260 // as non-boring as soon we see one.
261 TextUtils.getChars(text, start, end, buffer, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800262
Roozbeh Pournader3cf82082016-03-16 14:33:51 -0700263 final int len = end - start;
264 for (int i = 0; i < len; i++) {
265 final char c = buffer[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800266
Raph Leviena0419de2015-07-09 12:36:31 -0700267 if (c == '\n' || c == '\t' ||
268 (c >= 0x0590 && c <= 0x08FF) || // RTL scripts
269 c == 0x200F || // Bidi format character
270 (c >= 0x202A && c <= 0x202E) || // Bidi format characters
271 (c >= 0x2066 && c <= 0x2069) || // Bidi format characters
272 (c >= 0xD800 && c <= 0xDFFF) || // surrogate pairs
273 (c >= 0xFB1D && c <= 0xFDFF) || // Hebrew and Arabic presentation forms
274 (c >= 0xFE70 && c <= 0xFEFE) // Arabic presentation forms
275 ) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800276 boring = false;
277 break outer;
278 }
279 }
Doug Feltcb3791202011-07-07 11:57:48 -0700280
Roozbeh Pournader3cf82082016-03-16 14:33:51 -0700281 // TODO: This looks a little suspicious, and in some cases can result in O(n^2)
282 // run time. Consider moving outside the loop.
283 if (textDir != null && textDir.isRtl(buffer, 0, len)) {
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
Roozbeh Pournader3cf82082016-03-16 14:33:51 -0700289 TextUtils.recycle(buffer);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800290
Eric Fischer86fcef82009-08-17 17:16:44 -0700291 if (boring && text instanceof Spanned) {
292 Spanned sp = (Spanned) text;
Roozbeh Pournader3cf82082016-03-16 14:33:51 -0700293 Object[] styles = sp.getSpans(0, textLength, 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();
Siyamed Sinir70f660f2016-03-29 11:56:53 -0700303 } else {
304 fm.reset();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800305 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800306
Doug Felte8e45f22010-03-29 14:58:40 -0700307 TextLine line = TextLine.obtain();
Roozbeh Pournader3cf82082016-03-16 14:33:51 -0700308 line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
Doug Felte8e45f22010-03-29 14:58:40 -0700309 Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
Neil Fuller33253a42014-10-01 11:55:10 +0100310 fm.width = (int) Math.ceil(line.metrics(fm));
Doug Felte8e45f22010-03-29 14:58:40 -0700311 TextLine.recycle(line);
312
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800313 return fm;
314 } else {
315 return null;
316 }
317 }
318
Gilles Debunnef483e512011-04-28 15:08:54 -0700319 @Override
320 public int getHeight() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800321 return mBottom;
322 }
323
Gilles Debunnef483e512011-04-28 15:08:54 -0700324 @Override
325 public int getLineCount() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800326 return 1;
327 }
328
Gilles Debunnef483e512011-04-28 15:08:54 -0700329 @Override
330 public int getLineTop(int line) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800331 if (line == 0)
332 return 0;
333 else
334 return mBottom;
335 }
336
Gilles Debunnef483e512011-04-28 15:08:54 -0700337 @Override
338 public int getLineDescent(int line) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800339 return mDesc;
340 }
341
Gilles Debunnef483e512011-04-28 15:08:54 -0700342 @Override
343 public int getLineStart(int line) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800344 if (line == 0)
345 return 0;
346 else
347 return getText().length();
348 }
349
Gilles Debunnef483e512011-04-28 15:08:54 -0700350 @Override
351 public int getParagraphDirection(int line) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352 return DIR_LEFT_TO_RIGHT;
353 }
354
Gilles Debunnef483e512011-04-28 15:08:54 -0700355 @Override
356 public boolean getLineContainsTab(int line) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800357 return false;
358 }
359
Gilles Debunnef483e512011-04-28 15:08:54 -0700360 @Override
361 public float getLineMax(int line) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800362 return mMax;
363 }
364
Gilles Debunnef483e512011-04-28 15:08:54 -0700365 @Override
John Reck44e8d602015-10-05 15:44:04 -0700366 public float getLineWidth(int line) {
367 return (line == 0 ? mMax : 0);
368 }
369
370 @Override
Gilles Debunnef483e512011-04-28 15:08:54 -0700371 public final Directions getLineDirections(int line) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800372 return Layout.DIRS_ALL_LEFT_TO_RIGHT;
373 }
374
Gilles Debunnef483e512011-04-28 15:08:54 -0700375 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800376 public int getTopPadding() {
377 return mTopPadding;
378 }
379
Gilles Debunnef483e512011-04-28 15:08:54 -0700380 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800381 public int getBottomPadding() {
382 return mBottomPadding;
383 }
384
385 @Override
386 public int getEllipsisCount(int line) {
387 return mEllipsizedCount;
388 }
389
390 @Override
391 public int getEllipsisStart(int line) {
392 return mEllipsizedStart;
393 }
394
395 @Override
396 public int getEllipsizedWidth() {
397 return mEllipsizedWidth;
398 }
399
400 // Override draw so it will be faster.
401 @Override
402 public void draw(Canvas c, Path highlight, Paint highlightpaint,
403 int cursorOffset) {
404 if (mDirect != null && highlight == null) {
405 c.drawText(mDirect, 0, mBottom - mDesc, mPaint);
406 } else {
407 super.draw(c, highlight, highlightpaint, cursorOffset);
408 }
409 }
410
411 /**
412 * Callback for the ellipsizer to report what region it ellipsized.
413 */
414 public void ellipsized(int start, int end) {
415 mEllipsizedStart = start;
416 mEllipsizedCount = end - start;
417 }
418
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419 private String mDirect;
420 private Paint mPaint;
421
422 /* package */ int mBottom, mDesc; // for Direct
423 private int mTopPadding, mBottomPadding;
424 private float mMax;
425 private int mEllipsizedWidth, mEllipsizedStart, mEllipsizedCount;
426
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800427 public static class Metrics extends Paint.FontMetricsInt {
428 public int width;
Doug Felte8e45f22010-03-29 14:58:40 -0700429
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430 @Override public String toString() {
431 return super.toString() + " width=" + width;
432 }
Siyamed Sinir70f660f2016-03-29 11:56:53 -0700433
434 private void reset() {
435 top = 0;
436 bottom = 0;
437 ascent = 0;
438 descent = 0;
439 width = 0;
440 leading = 0;
441 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800442 }
443}