• 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 
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  */
37 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)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 
make(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)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      */
replaceOrMake(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad)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      */
replaceOrMake(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)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 
BoringLayout(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad)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 
BoringLayout(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)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 
init(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includepad, boolean trustWidth)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             mDesc = metrics.bottom;
187         } else {
188             spacing = metrics.descent - metrics.ascent;
189             mDesc = metrics.descent;
190         }
191 
192         mBottom = spacing;
193 
194         if (trustWidth) {
195             mMax = metrics.width;
196         } else {
197             /*
198              * If we have ellipsized, we have to actually calculate the
199              * width because the width that was passed in was for the
200              * full text, not the ellipsized form.
201              */
202             TextLine line = TextLine.obtain();
203             line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT,
204                     Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
205             mMax = (int) Math.ceil(line.metrics(null));
206             TextLine.recycle(line);
207         }
208 
209         if (includepad) {
210             mTopPadding = metrics.top - metrics.ascent;
211             mBottomPadding = metrics.bottom - metrics.descent;
212         }
213     }
214 
215     /**
216      * Returns null if not boring; the width, ascent, and descent if boring.
217      */
isBoring(CharSequence text, TextPaint paint)218     public static Metrics isBoring(CharSequence text,
219                                    TextPaint paint) {
220         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      */
isBoring(CharSequence text, TextPaint paint, TextDirectionHeuristic textDir)227     public static Metrics isBoring(CharSequence text,
228                                    TextPaint paint,
229                                    TextDirectionHeuristic textDir) {
230         return isBoring(text, paint, textDir, null);
231     }
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      */
isBoring(CharSequence text, TextPaint paint, Metrics metrics)238     public static Metrics isBoring(CharSequence text, TextPaint paint, Metrics metrics) {
239         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      */
isBoring(CharSequence text, TextPaint paint, TextDirectionHeuristic textDir, Metrics metrics)248     public static Metrics isBoring(CharSequence text, TextPaint paint,
249             TextDirectionHeuristic textDir, Metrics metrics) {
250         final int MAX_BUF_LEN = 500;
251         final char[] buffer = TextUtils.obtain(MAX_BUF_LEN);
252         final int textLength = text.length();
253         boolean boring = true;
254 
255         outer:
256         for (int start = 0; start < textLength; start += MAX_BUF_LEN) {
257             final int end = Math.min(start + MAX_BUF_LEN, textLength);
258 
259             // 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);
262 
263             final int len = end - start;
264             for (int i = 0; i < len; i++) {
265                 final char c = buffer[i];
266 
267                 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                    ) {
276                     boring = false;
277                     break outer;
278                 }
279             }
280 
281             // 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)) {
284                boring = false;
285                break outer;
286             }
287         }
288 
289         TextUtils.recycle(buffer);
290 
291         if (boring && text instanceof Spanned) {
292             Spanned sp = (Spanned) text;
293             Object[] styles = sp.getSpans(0, textLength, ParagraphStyle.class);
294             if (styles.length > 0) {
295                 boring = false;
296             }
297         }
298 
299         if (boring) {
300             Metrics fm = metrics;
301             if (fm == null) {
302                 fm = new Metrics();
303             } else {
304                 fm.reset();
305             }
306 
307             TextLine line = TextLine.obtain();
308             line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
309                     Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
310             fm.width = (int) Math.ceil(line.metrics(fm));
311             TextLine.recycle(line);
312 
313             return fm;
314         } else {
315             return null;
316         }
317     }
318 
319     @Override
getHeight()320     public int getHeight() {
321         return mBottom;
322     }
323 
324     @Override
getLineCount()325     public int getLineCount() {
326         return 1;
327     }
328 
329     @Override
getLineTop(int line)330     public int getLineTop(int line) {
331         if (line == 0)
332             return 0;
333         else
334             return mBottom;
335     }
336 
337     @Override
getLineDescent(int line)338     public int getLineDescent(int line) {
339         return mDesc;
340     }
341 
342     @Override
getLineStart(int line)343     public int getLineStart(int line) {
344         if (line == 0)
345             return 0;
346         else
347             return getText().length();
348     }
349 
350     @Override
getParagraphDirection(int line)351     public int getParagraphDirection(int line) {
352         return DIR_LEFT_TO_RIGHT;
353     }
354 
355     @Override
getLineContainsTab(int line)356     public boolean getLineContainsTab(int line) {
357         return false;
358     }
359 
360     @Override
getLineMax(int line)361     public float getLineMax(int line) {
362         return mMax;
363     }
364 
365     @Override
getLineWidth(int line)366     public float getLineWidth(int line) {
367         return (line == 0 ? mMax : 0);
368     }
369 
370     @Override
getLineDirections(int line)371     public final Directions getLineDirections(int line) {
372         return Layout.DIRS_ALL_LEFT_TO_RIGHT;
373     }
374 
375     @Override
getTopPadding()376     public int getTopPadding() {
377         return mTopPadding;
378     }
379 
380     @Override
getBottomPadding()381     public int getBottomPadding() {
382         return mBottomPadding;
383     }
384 
385     @Override
getEllipsisCount(int line)386     public int getEllipsisCount(int line) {
387         return mEllipsizedCount;
388     }
389 
390     @Override
getEllipsisStart(int line)391     public int getEllipsisStart(int line) {
392         return mEllipsizedStart;
393     }
394 
395     @Override
getEllipsizedWidth()396     public int getEllipsizedWidth() {
397         return mEllipsizedWidth;
398     }
399 
400     // Override draw so it will be faster.
401     @Override
draw(Canvas c, Path highlight, Paint highlightpaint, int cursorOffset)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      */
ellipsized(int start, int end)414     public void ellipsized(int start, int end) {
415         mEllipsizedStart = start;
416         mEllipsizedCount = end - start;
417     }
418 
419     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 
427     public static class Metrics extends Paint.FontMetricsInt {
428         public int width;
429 
toString()430         @Override public String toString() {
431             return super.toString() + " width=" + width;
432         }
433 
reset()434         private void reset() {
435             top = 0;
436             bottom = 0;
437             ascent = 0;
438             descent = 0;
439             width = 0;
440             leading = 0;
441         }
442     }
443 }
444