• 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         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              */
211             synchronized (sTemp) {
212                 mMax = (int) (FloatMath.ceil(Styled.measureText(paint, sTemp,
213                                                 source, 0, source.length(),
214                                                 null)));
215             }
216         }
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      */
isBoring(CharSequence text, TextPaint paint)227     public static Metrics isBoring(CharSequence text,
228                                    TextPaint paint) {
229         return isBoring(text, paint, null);
230     }
231 
232     /**
233      * Returns null if not boring; the width, ascent, and descent in the
234      * provided Metrics object (or a new one if the provided one was null)
235      * if boring.
236      */
isBoring(CharSequence text, TextPaint paint, Metrics metrics)237     public static Metrics isBoring(CharSequence text, TextPaint paint,
238                                    Metrics metrics) {
239         char[] temp = TextUtils.obtain(500);
240         int len = text.length();
241         boolean boring = true;
242 
243         outer:
244         for (int i = 0; i < len; i += 500) {
245             int j = i + 500;
246 
247             if (j > len)
248                 j = len;
249 
250             TextUtils.getChars(text, i, j, temp, 0);
251 
252             int n = j - i;
253 
254             for (int a = 0; a < n; a++) {
255                 char c = temp[a];
256 
257                 if (c == '\n' || c == '\t' || c >= FIRST_RIGHT_TO_LEFT) {
258                     boring = false;
259                     break outer;
260                 }
261             }
262         }
263 
264         TextUtils.recycle(temp);
265 
266         if (boring && text instanceof Spanned) {
267             Spanned sp = (Spanned) text;
268             Object[] styles = sp.getSpans(0, text.length(), ParagraphStyle.class);
269             if (styles.length > 0) {
270                 boring = false;
271             }
272         }
273 
274         if (boring) {
275             Metrics fm = metrics;
276             if (fm == null) {
277                 fm = new Metrics();
278             }
279 
280             int wid;
281 
282             synchronized (sTemp) {
283                 wid = (int) (FloatMath.ceil(Styled.measureText(paint, sTemp,
284                                                 text, 0, text.length(), fm)));
285             }
286             fm.width = wid;
287             return fm;
288         } else {
289             return null;
290         }
291     }
292 
getHeight()293     @Override public int getHeight() {
294         return mBottom;
295     }
296 
getLineCount()297     @Override public int getLineCount() {
298         return 1;
299     }
300 
getLineTop(int line)301     @Override public int getLineTop(int line) {
302         if (line == 0)
303             return 0;
304         else
305             return mBottom;
306     }
307 
getLineDescent(int line)308     @Override public int getLineDescent(int line) {
309         return mDesc;
310     }
311 
getLineStart(int line)312     @Override public int getLineStart(int line) {
313         if (line == 0)
314             return 0;
315         else
316             return getText().length();
317     }
318 
getParagraphDirection(int line)319     @Override public int getParagraphDirection(int line) {
320         return DIR_LEFT_TO_RIGHT;
321     }
322 
getLineContainsTab(int line)323     @Override public boolean getLineContainsTab(int line) {
324         return false;
325     }
326 
getLineMax(int line)327     @Override public float getLineMax(int line) {
328         return mMax;
329     }
330 
getLineDirections(int line)331     @Override public final Directions getLineDirections(int line) {
332         return Layout.DIRS_ALL_LEFT_TO_RIGHT;
333     }
334 
getTopPadding()335     public int getTopPadding() {
336         return mTopPadding;
337     }
338 
getBottomPadding()339     public int getBottomPadding() {
340         return mBottomPadding;
341     }
342 
343     @Override
getEllipsisCount(int line)344     public int getEllipsisCount(int line) {
345         return mEllipsizedCount;
346     }
347 
348     @Override
getEllipsisStart(int line)349     public int getEllipsisStart(int line) {
350         return mEllipsizedStart;
351     }
352 
353     @Override
getEllipsizedWidth()354     public int getEllipsizedWidth() {
355         return mEllipsizedWidth;
356     }
357 
358     // Override draw so it will be faster.
359     @Override
draw(Canvas c, Path highlight, Paint highlightpaint, int cursorOffset)360     public void draw(Canvas c, Path highlight, Paint highlightpaint,
361                      int cursorOffset) {
362         if (mDirect != null && highlight == null) {
363             c.drawText(mDirect, 0, mBottom - mDesc, mPaint);
364         } else {
365             super.draw(c, highlight, highlightpaint, cursorOffset);
366         }
367     }
368 
369     /**
370      * Callback for the ellipsizer to report what region it ellipsized.
371      */
ellipsized(int start, int end)372     public void ellipsized(int start, int end) {
373         mEllipsizedStart = start;
374         mEllipsizedCount = end - start;
375     }
376 
377     private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
378 
379     private String mDirect;
380     private Paint mPaint;
381 
382     /* package */ int mBottom, mDesc;   // for Direct
383     private int mTopPadding, mBottomPadding;
384     private float mMax;
385     private int mEllipsizedWidth, mEllipsizedStart, mEllipsizedCount;
386 
387     private static final TextPaint sTemp =
388                                 new TextPaint();
389 
390     public static class Metrics extends Paint.FontMetricsInt {
391         public int width;
392 
toString()393         @Override public String toString() {
394             return super.toString() + " width=" + width;
395         }
396     }
397 }
398