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             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      */
    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      */
    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 
    293     @Override public int getHeight() {
    294         return mBottom;
    295     }
    296 
    297     @Override public int getLineCount() {
    298         return 1;
    299     }
    300 
    301     @Override public int getLineTop(int line) {
    302         if (line == 0)
    303             return 0;
    304         else
    305             return mBottom;
    306     }
    307 
    308     @Override public int getLineDescent(int line) {
    309         return mDesc;
    310     }
    311 
    312     @Override public int getLineStart(int line) {
    313         if (line == 0)
    314             return 0;
    315         else
    316             return getText().length();
    317     }
    318 
    319     @Override public int getParagraphDirection(int line) {
    320         return DIR_LEFT_TO_RIGHT;
    321     }
    322 
    323     @Override public boolean getLineContainsTab(int line) {
    324         return false;
    325     }
    326 
    327     @Override public float getLineMax(int line) {
    328         return mMax;
    329     }
    330 
    331     @Override public final Directions getLineDirections(int line) {
    332         return Layout.DIRS_ALL_LEFT_TO_RIGHT;
    333     }
    334 
    335     public int getTopPadding() {
    336         return mTopPadding;
    337     }
    338 
    339     public int getBottomPadding() {
    340         return mBottomPadding;
    341     }
    342 
    343     @Override
    344     public int getEllipsisCount(int line) {
    345         return mEllipsizedCount;
    346     }
    347 
    348     @Override
    349     public int getEllipsisStart(int line) {
    350         return mEllipsizedStart;
    351     }
    352 
    353     @Override
    354     public int getEllipsizedWidth() {
    355         return mEllipsizedWidth;
    356     }
    357 
    358     // Override draw so it will be faster.
    359     @Override
    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      */
    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 
    393         @Override public String toString() {
    394             return super.toString() + " width=" + width;
    395         }
    396     }
    397 }
    398