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