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