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