• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package android.text;
18 
19 import android.graphics.Canvas;
20 import android.graphics.Paint;
21 import android.graphics.Path;
22 import android.text.style.ParagraphStyle;
23 import 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  */
38 public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback {
make(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad)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 
make(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)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      */
replaceOrMake(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad)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      */
replaceOrMake(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)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 
BoringLayout(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad)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 
BoringLayout(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)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 
init(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad, boolean trustWidth)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         mBottom = spacing;
192 
193         if (includepad) {
194             mDesc = spacing + metrics.top;
195         } else {
196             mDesc = spacing + metrics.ascent;
197         }
198 
199         if (trustWidth) {
200             mMax = metrics.width;
201         } else {
202             /*
203              * If we have ellipsized, we have to actually calculate the
204              * width because the width that was passed in was for the
205              * full text, not the ellipsized form.
206              */
207             TextLine line = TextLine.obtain();
208             line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT,
209                     Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
210             mMax = (int) FloatMath.ceil(line.metrics(null));
211             TextLine.recycle(line);
212         }
213 
214         if (includepad) {
215             mTopPadding = metrics.top - metrics.ascent;
216             mBottomPadding = metrics.bottom - metrics.descent;
217         }
218     }
219 
220     /**
221      * Returns null if not boring; the width, ascent, and descent if boring.
222      */
isBoring(CharSequence text, TextPaint paint)223     public static Metrics isBoring(CharSequence text,
224                                    TextPaint paint) {
225         return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, null);
226     }
227 
228     /**
229      * Returns null if not boring; the width, ascent, and descent if boring.
230      * @hide
231      */
isBoring(CharSequence text, TextPaint paint, TextDirectionHeuristic textDir)232     public static Metrics isBoring(CharSequence text,
233                                    TextPaint paint,
234                                    TextDirectionHeuristic textDir) {
235         return isBoring(text, paint, textDir, null);
236     }
237 
238     /**
239      * Returns null if not boring; the width, ascent, and descent in the
240      * provided Metrics object (or a new one if the provided one was null)
241      * if boring.
242      */
isBoring(CharSequence text, TextPaint paint, Metrics metrics)243     public static Metrics isBoring(CharSequence text, TextPaint paint, Metrics metrics) {
244         return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, metrics);
245     }
246 
247     /**
248      * Returns null if not boring; the width, ascent, and descent in the
249      * provided Metrics object (or a new one if the provided one was null)
250      * if boring.
251      * @hide
252      */
isBoring(CharSequence text, TextPaint paint, TextDirectionHeuristic textDir, Metrics metrics)253     public static Metrics isBoring(CharSequence text, TextPaint paint,
254             TextDirectionHeuristic textDir, Metrics metrics) {
255         char[] temp = TextUtils.obtain(500);
256         int length = text.length();
257         boolean boring = true;
258 
259         outer:
260         for (int i = 0; i < length; i += 500) {
261             int j = i + 500;
262 
263             if (j > length)
264                 j = length;
265 
266             TextUtils.getChars(text, i, j, temp, 0);
267 
268             int n = j - i;
269 
270             for (int a = 0; a < n; a++) {
271                 char c = temp[a];
272 
273                 if (c == '\n' || c == '\t' || c >= FIRST_RIGHT_TO_LEFT) {
274                     boring = false;
275                     break outer;
276                 }
277             }
278 
279             if (textDir != null && textDir.isRtl(temp, 0, n)) {
280                boring = false;
281                break outer;
282             }
283         }
284 
285         TextUtils.recycle(temp);
286 
287         if (boring && text instanceof Spanned) {
288             Spanned sp = (Spanned) text;
289             Object[] styles = sp.getSpans(0, length, ParagraphStyle.class);
290             if (styles.length > 0) {
291                 boring = false;
292             }
293         }
294 
295         if (boring) {
296             Metrics fm = metrics;
297             if (fm == null) {
298                 fm = new Metrics();
299             }
300 
301             TextLine line = TextLine.obtain();
302             line.set(paint, text, 0, length, Layout.DIR_LEFT_TO_RIGHT,
303                     Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
304             fm.width = (int) FloatMath.ceil(line.metrics(fm));
305             TextLine.recycle(line);
306 
307             return fm;
308         } else {
309             return null;
310         }
311     }
312 
313     @Override
getHeight()314     public int getHeight() {
315         return mBottom;
316     }
317 
318     @Override
getLineCount()319     public int getLineCount() {
320         return 1;
321     }
322 
323     @Override
getLineTop(int line)324     public int getLineTop(int line) {
325         if (line == 0)
326             return 0;
327         else
328             return mBottom;
329     }
330 
331     @Override
getLineDescent(int line)332     public int getLineDescent(int line) {
333         return mDesc;
334     }
335 
336     @Override
getLineStart(int line)337     public int getLineStart(int line) {
338         if (line == 0)
339             return 0;
340         else
341             return getText().length();
342     }
343 
344     @Override
getParagraphDirection(int line)345     public int getParagraphDirection(int line) {
346         return DIR_LEFT_TO_RIGHT;
347     }
348 
349     @Override
getLineContainsTab(int line)350     public boolean getLineContainsTab(int line) {
351         return false;
352     }
353 
354     @Override
getLineMax(int line)355     public float getLineMax(int line) {
356         return mMax;
357     }
358 
359     @Override
getLineDirections(int line)360     public final Directions getLineDirections(int line) {
361         return Layout.DIRS_ALL_LEFT_TO_RIGHT;
362     }
363 
364     @Override
getTopPadding()365     public int getTopPadding() {
366         return mTopPadding;
367     }
368 
369     @Override
getBottomPadding()370     public int getBottomPadding() {
371         return mBottomPadding;
372     }
373 
374     @Override
getEllipsisCount(int line)375     public int getEllipsisCount(int line) {
376         return mEllipsizedCount;
377     }
378 
379     @Override
getEllipsisStart(int line)380     public int getEllipsisStart(int line) {
381         return mEllipsizedStart;
382     }
383 
384     @Override
getEllipsizedWidth()385     public int getEllipsizedWidth() {
386         return mEllipsizedWidth;
387     }
388 
389     // Override draw so it will be faster.
390     @Override
draw(Canvas c, Path highlight, Paint highlightpaint, int cursorOffset)391     public void draw(Canvas c, Path highlight, Paint highlightpaint,
392                      int cursorOffset) {
393         if (mDirect != null && highlight == null) {
394             c.drawText(mDirect, 0, mBottom - mDesc, mPaint);
395         } else {
396             super.draw(c, highlight, highlightpaint, cursorOffset);
397         }
398     }
399 
400     /**
401      * Callback for the ellipsizer to report what region it ellipsized.
402      */
ellipsized(int start, int end)403     public void ellipsized(int start, int end) {
404         mEllipsizedStart = start;
405         mEllipsizedCount = end - start;
406     }
407 
408     private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
409 
410     private String mDirect;
411     private Paint mPaint;
412 
413     /* package */ int mBottom, mDesc;   // for Direct
414     private int mTopPadding, mBottomPadding;
415     private float mMax;
416     private int mEllipsizedWidth, mEllipsizedStart, mEllipsizedCount;
417 
418     private static final TextPaint sTemp =
419                                 new TextPaint();
420 
421     public static class Metrics extends Paint.FontMetricsInt {
422         public int width;
423 
toString()424         @Override public String toString() {
425             return super.toString() + " width=" + width;
426         }
427     }
428 }
429