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.Bitmap;
     20 import android.graphics.Paint;
     21 import com.android.internal.util.ArrayUtils;
     22 import android.util.Log;
     23 import android.text.style.LeadingMarginSpan;
     24 import android.text.style.LineHeightSpan;
     25 import android.text.style.MetricAffectingSpan;
     26 import android.text.style.ReplacementSpan;
     27 
     28 /**
     29  * StaticLayout is a Layout for text that will not be edited after it
     30  * is laid out.  Use {@link DynamicLayout} for text that may change.
     31  * <p>This 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, or would be tempted to call
     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
     38 StaticLayout
     39 extends Layout
     40 {
     41     public StaticLayout(CharSequence source, TextPaint paint,
     42                         int width,
     43                         Alignment align, float spacingmult, float spacingadd,
     44                         boolean includepad) {
     45         this(source, 0, source.length(), paint, width, align,
     46              spacingmult, spacingadd, includepad);
     47     }
     48 
     49     public StaticLayout(CharSequence source, int bufstart, int bufend,
     50                         TextPaint paint, int outerwidth,
     51                         Alignment align,
     52                         float spacingmult, float spacingadd,
     53                         boolean includepad) {
     54         this(source, bufstart, bufend, paint, outerwidth, align,
     55              spacingmult, spacingadd, includepad, null, 0);
     56     }
     57 
     58     public StaticLayout(CharSequence source, int bufstart, int bufend,
     59                         TextPaint paint, int outerwidth,
     60                         Alignment align,
     61                         float spacingmult, float spacingadd,
     62                         boolean includepad,
     63                         TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
     64         super((ellipsize == null)
     65                 ? source
     66                 : (source instanceof Spanned)
     67                     ? new SpannedEllipsizer(source)
     68                     : new Ellipsizer(source),
     69               paint, outerwidth, align, spacingmult, spacingadd);
     70 
     71         /*
     72          * This is annoying, but we can't refer to the layout until
     73          * superclass construction is finished, and the superclass
     74          * constructor wants the reference to the display text.
     75          *
     76          * This will break if the superclass constructor ever actually
     77          * cares about the content instead of just holding the reference.
     78          */
     79         if (ellipsize != null) {
     80             Ellipsizer e = (Ellipsizer) getText();
     81 
     82             e.mLayout = this;
     83             e.mWidth = ellipsizedWidth;
     84             e.mMethod = ellipsize;
     85             mEllipsizedWidth = ellipsizedWidth;
     86 
     87             mColumns = COLUMNS_ELLIPSIZE;
     88         } else {
     89             mColumns = COLUMNS_NORMAL;
     90             mEllipsizedWidth = outerwidth;
     91         }
     92 
     93         mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
     94         mLineDirections = new Directions[
     95                              ArrayUtils.idealIntArraySize(2 * mColumns)];
     96 
     97         generate(source, bufstart, bufend, paint, outerwidth, align,
     98                  spacingmult, spacingadd, includepad, includepad,
     99                  ellipsize != null, ellipsizedWidth, ellipsize);
    100 
    101         mChdirs = null;
    102         mChs = null;
    103         mWidths = null;
    104         mFontMetricsInt = null;
    105     }
    106 
    107     /* package */ StaticLayout(boolean ellipsize) {
    108         super(null, null, 0, null, 0, 0);
    109 
    110         mColumns = COLUMNS_ELLIPSIZE;
    111         mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
    112         mLineDirections = new Directions[
    113                              ArrayUtils.idealIntArraySize(2 * mColumns)];
    114     }
    115 
    116     /* package */ void generate(CharSequence source, int bufstart, int bufend,
    117                         TextPaint paint, int outerwidth,
    118                         Alignment align,
    119                         float spacingmult, float spacingadd,
    120                         boolean includepad, boolean trackpad,
    121                         boolean breakOnlyAtSpaces,
    122                         float ellipsizedWidth, TextUtils.TruncateAt where) {
    123         mLineCount = 0;
    124 
    125         int v = 0;
    126         boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
    127 
    128         Paint.FontMetricsInt fm = mFontMetricsInt;
    129         int[] choosehtv = null;
    130 
    131         int end = TextUtils.indexOf(source, '\n', bufstart, bufend);
    132         int bufsiz = end >= 0 ? end - bufstart : bufend - bufstart;
    133         boolean first = true;
    134 
    135         if (mChdirs == null) {
    136             mChdirs = new byte[ArrayUtils.idealByteArraySize(bufsiz + 1)];
    137             mChs = new char[ArrayUtils.idealCharArraySize(bufsiz + 1)];
    138             mWidths = new float[ArrayUtils.idealIntArraySize((bufsiz + 1) * 2)];
    139         }
    140 
    141         byte[] chdirs = mChdirs;
    142         char[] chs = mChs;
    143         float[] widths = mWidths;
    144 
    145         AlteredCharSequence alter = null;
    146         Spanned spanned = null;
    147 
    148         if (source instanceof Spanned)
    149             spanned = (Spanned) source;
    150 
    151         int DEFAULT_DIR = DIR_LEFT_TO_RIGHT; // XXX
    152 
    153         for (int start = bufstart; start <= bufend; start = end) {
    154             if (first)
    155                 first = false;
    156             else
    157                 end = TextUtils.indexOf(source, '\n', start, bufend);
    158 
    159             if (end < 0)
    160                 end = bufend;
    161             else
    162                 end++;
    163 
    164             int firstWidthLineCount = 1;
    165             int firstwidth = outerwidth;
    166             int restwidth = outerwidth;
    167 
    168             LineHeightSpan[] chooseht = null;
    169 
    170             if (spanned != null) {
    171                 LeadingMarginSpan[] sp;
    172 
    173                 sp = spanned.getSpans(start, end, LeadingMarginSpan.class);
    174                 for (int i = 0; i < sp.length; i++) {
    175                     LeadingMarginSpan lms = sp[i];
    176                     firstwidth -= sp[i].getLeadingMargin(true);
    177                     restwidth -= sp[i].getLeadingMargin(false);
    178                     if (lms instanceof LeadingMarginSpan.LeadingMarginSpan2) {
    179                         firstWidthLineCount = ((LeadingMarginSpan.LeadingMarginSpan2)lms).getLeadingMarginLineCount();
    180                     }
    181                 }
    182 
    183                 chooseht = spanned.getSpans(start, end, LineHeightSpan.class);
    184 
    185                 if (chooseht.length != 0) {
    186                     if (choosehtv == null ||
    187                         choosehtv.length < chooseht.length) {
    188                         choosehtv = new int[ArrayUtils.idealIntArraySize(
    189                                             chooseht.length)];
    190                     }
    191 
    192                     for (int i = 0; i < chooseht.length; i++) {
    193                         int o = spanned.getSpanStart(chooseht[i]);
    194 
    195                         if (o < start) {
    196                             // starts in this layout, before the
    197                             // current paragraph
    198 
    199                             choosehtv[i] = getLineTop(getLineForOffset(o));
    200                         } else {
    201                             // starts in this paragraph
    202 
    203                             choosehtv[i] = v;
    204                         }
    205                     }
    206                 }
    207             }
    208 
    209             if (end - start > chdirs.length) {
    210                 chdirs = new byte[ArrayUtils.idealByteArraySize(end - start)];
    211                 mChdirs = chdirs;
    212             }
    213             if (end - start > chs.length) {
    214                 chs = new char[ArrayUtils.idealCharArraySize(end - start)];
    215                 mChs = chs;
    216             }
    217             if ((end - start) * 2 > widths.length) {
    218                 widths = new float[ArrayUtils.idealIntArraySize((end - start) * 2)];
    219                 mWidths = widths;
    220             }
    221 
    222             TextUtils.getChars(source, start, end, chs, 0);
    223             final int n = end - start;
    224 
    225             boolean easy = true;
    226             boolean altered = false;
    227             int dir = DEFAULT_DIR; // XXX
    228 
    229             for (int i = 0; i < n; i++) {
    230                 if (chs[i] >= FIRST_RIGHT_TO_LEFT) {
    231                     easy = false;
    232                     break;
    233                 }
    234             }
    235 
    236             // Ensure that none of the underlying characters are treated
    237             // as viable breakpoints, and that the entire run gets the
    238             // same bidi direction.
    239 
    240             if (source instanceof Spanned) {
    241                 Spanned sp = (Spanned) source;
    242                 ReplacementSpan[] spans = sp.getSpans(start, end, ReplacementSpan.class);
    243 
    244                 for (int y = 0; y < spans.length; y++) {
    245                     int a = sp.getSpanStart(spans[y]);
    246                     int b = sp.getSpanEnd(spans[y]);
    247 
    248                     for (int x = a; x < b; x++) {
    249                         chs[x - start] = '\uFFFC';
    250                     }
    251                 }
    252             }
    253 
    254             if (!easy) {
    255                 // XXX put override flags, etc. into chdirs
    256                 dir = bidi(dir, chs, chdirs, n, false);
    257 
    258                 // Do mirroring for right-to-left segments
    259 
    260                 for (int i = 0; i < n; i++) {
    261                     if (chdirs[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
    262                         int j;
    263 
    264                         for (j = i; j < n; j++) {
    265                             if (chdirs[j] !=
    266                                 Character.DIRECTIONALITY_RIGHT_TO_LEFT)
    267                                 break;
    268                         }
    269 
    270                         if (AndroidCharacter.mirror(chs, i, j - i))
    271                             altered = true;
    272 
    273                         i = j - 1;
    274                     }
    275                 }
    276             }
    277 
    278             CharSequence sub;
    279 
    280             if (altered) {
    281                 if (alter == null)
    282                     alter = AlteredCharSequence.make(source, chs, start, end);
    283                 else
    284                     alter.update(chs, start, end);
    285 
    286                 sub = alter;
    287             } else {
    288                 sub = source;
    289             }
    290 
    291             int width = firstwidth;
    292 
    293             float w = 0;
    294             int here = start;
    295 
    296             int ok = start;
    297             float okwidth = w;
    298             int okascent = 0, okdescent = 0, oktop = 0, okbottom = 0;
    299 
    300             int fit = start;
    301             float fitwidth = w;
    302             int fitascent = 0, fitdescent = 0, fittop = 0, fitbottom = 0;
    303 
    304             boolean tab = false;
    305 
    306             int next;
    307             for (int i = start; i < end; i = next) {
    308                 if (spanned == null)
    309                     next = end;
    310                 else
    311                     next = spanned.nextSpanTransition(i, end,
    312                                                       MetricAffectingSpan.
    313                                                       class);
    314 
    315                 if (spanned == null) {
    316                     paint.getTextWidths(sub, i, next, widths);
    317                     System.arraycopy(widths, 0, widths,
    318                                      end - start + (i - start), next - i);
    319 
    320                     paint.getFontMetricsInt(fm);
    321                 } else {
    322                     mWorkPaint.baselineShift = 0;
    323 
    324                     Styled.getTextWidths(paint, mWorkPaint,
    325                                          spanned, i, next,
    326                                          widths, fm);
    327                     System.arraycopy(widths, 0, widths,
    328                                      end - start + (i - start), next - i);
    329 
    330                     if (mWorkPaint.baselineShift < 0) {
    331                         fm.ascent += mWorkPaint.baselineShift;
    332                         fm.top += mWorkPaint.baselineShift;
    333                     } else {
    334                         fm.descent += mWorkPaint.baselineShift;
    335                         fm.bottom += mWorkPaint.baselineShift;
    336                     }
    337                 }
    338 
    339                 int fmtop = fm.top;
    340                 int fmbottom = fm.bottom;
    341                 int fmascent = fm.ascent;
    342                 int fmdescent = fm.descent;
    343 
    344                 if (false) {
    345                     StringBuilder sb = new StringBuilder();
    346                     for (int j = i; j < next; j++) {
    347                         sb.append(widths[j - start + (end - start)]);
    348                         sb.append(' ');
    349                     }
    350 
    351                     Log.e("text", sb.toString());
    352                 }
    353 
    354                 for (int j = i; j < next; j++) {
    355                     char c = chs[j - start];
    356                     float before = w;
    357 
    358                     if (c == '\n') {
    359                         ;
    360                     } else if (c == '\t') {
    361                         w = Layout.nextTab(sub, start, end, w, null);
    362                         tab = true;
    363                     } else if (c >= 0xD800 && c <= 0xDFFF && j + 1 < next) {
    364                         int emoji = Character.codePointAt(chs, j - start);
    365 
    366                         if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
    367                             Bitmap bm = EMOJI_FACTORY.
    368                                 getBitmapFromAndroidPua(emoji);
    369 
    370                             if (bm != null) {
    371                                 Paint whichPaint;
    372 
    373                                 if (spanned == null) {
    374                                     whichPaint = paint;
    375                                 } else {
    376                                     whichPaint = mWorkPaint;
    377                                 }
    378 
    379                                 float wid = (float) bm.getWidth() *
    380                                             -whichPaint.ascent() /
    381                                             bm.getHeight();
    382 
    383                                 w += wid;
    384                                 tab = true;
    385                                 j++;
    386                             } else {
    387                                 w += widths[j - start + (end - start)];
    388                             }
    389                         } else {
    390                             w += widths[j - start + (end - start)];
    391                         }
    392                     } else {
    393                         w += widths[j - start + (end - start)];
    394                     }
    395 
    396                     // Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width);
    397 
    398                     if (w <= width) {
    399                         fitwidth = w;
    400                         fit = j + 1;
    401 
    402                         if (fmtop < fittop)
    403                             fittop = fmtop;
    404                         if (fmascent < fitascent)
    405                             fitascent = fmascent;
    406                         if (fmdescent > fitdescent)
    407                             fitdescent = fmdescent;
    408                         if (fmbottom > fitbottom)
    409                             fitbottom = fmbottom;
    410 
    411                         /*
    412                          * From the Unicode Line Breaking Algorithm:
    413                          * (at least approximately)
    414                          *
    415                          * .,:; are class IS: breakpoints
    416                          *      except when adjacent to digits
    417                          * /    is class SY: a breakpoint
    418                          *      except when followed by a digit.
    419                          * -    is class HY: a breakpoint
    420                          *      except when followed by a digit.
    421                          *
    422                          * Ideographs are class ID: breakpoints when adjacent,
    423                          * except for NS (non-starters), which can be broken
    424                          * after but not before.
    425                          */
    426 
    427                         if (c == ' ' || c == '\t' ||
    428                             ((c == '.'  || c == ',' || c == ':' || c == ';') &&
    429                              (j - 1 < here || !Character.isDigit(chs[j - 1 - start])) &&
    430                              (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
    431                             ((c == '/' || c == '-') &&
    432                              (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
    433                             (c >= FIRST_CJK && isIdeographic(c, true) &&
    434                              j + 1 < next && isIdeographic(chs[j + 1 - start], false))) {
    435                             okwidth = w;
    436                             ok = j + 1;
    437 
    438                             if (fittop < oktop)
    439                                 oktop = fittop;
    440                             if (fitascent < okascent)
    441                                 okascent = fitascent;
    442                             if (fitdescent > okdescent)
    443                                 okdescent = fitdescent;
    444                             if (fitbottom > okbottom)
    445                                 okbottom = fitbottom;
    446                         }
    447                     } else if (breakOnlyAtSpaces) {
    448                         if (ok != here) {
    449                             // Log.e("text", "output ok " + here + " to " +ok);
    450 
    451                             while (ok < next && chs[ok - start] == ' ') {
    452                                 ok++;
    453                             }
    454 
    455                             v = out(source,
    456                                     here, ok,
    457                                     okascent, okdescent, oktop, okbottom,
    458                                     v,
    459                                     spacingmult, spacingadd, chooseht,
    460                                     choosehtv, fm, tab,
    461                                     needMultiply, start, chdirs, dir, easy,
    462                                     ok == bufend, includepad, trackpad,
    463                                     widths, start, end - start,
    464                                     where, ellipsizedWidth, okwidth,
    465                                     paint);
    466 
    467                             here = ok;
    468                         } else {
    469                             // Act like it fit even though it didn't.
    470 
    471                             fitwidth = w;
    472                             fit = j + 1;
    473 
    474                             if (fmtop < fittop)
    475                                 fittop = fmtop;
    476                             if (fmascent < fitascent)
    477                                 fitascent = fmascent;
    478                             if (fmdescent > fitdescent)
    479                                 fitdescent = fmdescent;
    480                             if (fmbottom > fitbottom)
    481                                 fitbottom = fmbottom;
    482                         }
    483                     } else {
    484                         if (ok != here) {
    485                             // Log.e("text", "output ok " + here + " to " +ok);
    486 
    487                             while (ok < next && chs[ok - start] == ' ') {
    488                                 ok++;
    489                             }
    490 
    491                             v = out(source,
    492                                     here, ok,
    493                                     okascent, okdescent, oktop, okbottom,
    494                                     v,
    495                                     spacingmult, spacingadd, chooseht,
    496                                     choosehtv, fm, tab,
    497                                     needMultiply, start, chdirs, dir, easy,
    498                                     ok == bufend, includepad, trackpad,
    499                                     widths, start, end - start,
    500                                     where, ellipsizedWidth, okwidth,
    501                                     paint);
    502 
    503                             here = ok;
    504                         } else if (fit != here) {
    505                             // Log.e("text", "output fit " + here + " to " +fit);
    506                             v = out(source,
    507                                     here, fit,
    508                                     fitascent, fitdescent,
    509                                     fittop, fitbottom,
    510                                     v,
    511                                     spacingmult, spacingadd, chooseht,
    512                                     choosehtv, fm, tab,
    513                                     needMultiply, start, chdirs, dir, easy,
    514                                     fit == bufend, includepad, trackpad,
    515                                     widths, start, end - start,
    516                                     where, ellipsizedWidth, fitwidth,
    517                                     paint);
    518 
    519                             here = fit;
    520                         } else {
    521                             // Log.e("text", "output one " + here + " to " +(here + 1));
    522                             measureText(paint, mWorkPaint,
    523                                         source, here, here + 1, fm, tab,
    524                                         null);
    525 
    526                             v = out(source,
    527                                     here, here+1,
    528                                     fm.ascent, fm.descent,
    529                                     fm.top, fm.bottom,
    530                                     v,
    531                                     spacingmult, spacingadd, chooseht,
    532                                     choosehtv, fm, tab,
    533                                     needMultiply, start, chdirs, dir, easy,
    534                                     here + 1 == bufend, includepad,
    535                                     trackpad,
    536                                     widths, start, end - start,
    537                                     where, ellipsizedWidth,
    538                                     widths[here - start], paint);
    539 
    540                             here = here + 1;
    541                         }
    542 
    543                         if (here < i) {
    544                             j = next = here; // must remeasure
    545                         } else {
    546                             j = here - 1;    // continue looping
    547                         }
    548 
    549                         ok = fit = here;
    550                         w = 0;
    551                         fitascent = fitdescent = fittop = fitbottom = 0;
    552                         okascent = okdescent = oktop = okbottom = 0;
    553 
    554                         if (--firstWidthLineCount <= 0) {
    555                             width = restwidth;
    556                         }
    557                     }
    558                 }
    559             }
    560 
    561             if (end != here) {
    562                 if ((fittop | fitbottom | fitdescent | fitascent) == 0) {
    563                     paint.getFontMetricsInt(fm);
    564 
    565                     fittop = fm.top;
    566                     fitbottom = fm.bottom;
    567                     fitascent = fm.ascent;
    568                     fitdescent = fm.descent;
    569                 }
    570 
    571                 // Log.e("text", "output rest " + here + " to " + end);
    572 
    573                 v = out(source,
    574                         here, end, fitascent, fitdescent,
    575                         fittop, fitbottom,
    576                         v,
    577                         spacingmult, spacingadd, chooseht,
    578                         choosehtv, fm, tab,
    579                         needMultiply, start, chdirs, dir, easy,
    580                         end == bufend, includepad, trackpad,
    581                         widths, start, end - start,
    582                         where, ellipsizedWidth, w, paint);
    583             }
    584 
    585             start = end;
    586 
    587             if (end == bufend)
    588                 break;
    589         }
    590 
    591         if (bufend == bufstart || source.charAt(bufend - 1) == '\n') {
    592             // Log.e("text", "output last " + bufend);
    593 
    594             paint.getFontMetricsInt(fm);
    595 
    596             v = out(source,
    597                     bufend, bufend, fm.ascent, fm.descent,
    598                     fm.top, fm.bottom,
    599                     v,
    600                     spacingmult, spacingadd, null,
    601                     null, fm, false,
    602                     needMultiply, bufend, chdirs, DEFAULT_DIR, true,
    603                     true, includepad, trackpad,
    604                     widths, bufstart, 0,
    605                     where, ellipsizedWidth, 0, paint);
    606         }
    607     }
    608 
    609     /**
    610      * Runs the unicode bidi algorithm on the first n chars in chs, returning
    611      * the char dirs in chInfo and the base line direction of the first
    612      * paragraph.
    613      *
    614      * XXX change result from dirs to levels
    615      *
    616      * @param dir the direction flag, either DIR_REQUEST_LTR,
    617      * DIR_REQUEST_RTL, DIR_REQUEST_DEFAULT_LTR, or DIR_REQUEST_DEFAULT_RTL.
    618      * @param chs the text to examine
    619      * @param chInfo on input, if hasInfo is true, override and other flags
    620      * representing out-of-band embedding information. On output, the generated
    621      * dirs of the text.
    622      * @param n the length of the text/information in chs and chInfo
    623      * @param hasInfo true if chInfo has input information, otherwise the
    624      * input data in chInfo is ignored.
    625      * @return the resolved direction level of the first paragraph, either
    626      * DIR_LEFT_TO_RIGHT or DIR_RIGHT_TO_LEFT.
    627      */
    628     /* package */ static int bidi(int dir, char[] chs, byte[] chInfo, int n,
    629             boolean hasInfo) {
    630 
    631         AndroidCharacter.getDirectionalities(chs, chInfo, n);
    632 
    633         /*
    634          * Determine primary paragraph direction if not specified
    635          */
    636         if (dir != DIR_REQUEST_LTR && dir != DIR_REQUEST_RTL) {
    637             // set up default
    638             dir = dir >= 0 ? DIR_LEFT_TO_RIGHT : DIR_RIGHT_TO_LEFT;
    639             for (int j = 0; j < n; j++) {
    640                 int d = chInfo[j];
    641 
    642                 if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT) {
    643                     dir = DIR_LEFT_TO_RIGHT;
    644                     break;
    645                 }
    646                 if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
    647                     dir = DIR_RIGHT_TO_LEFT;
    648                     break;
    649                 }
    650             }
    651         }
    652 
    653         final byte SOR = dir == DIR_LEFT_TO_RIGHT ?
    654                 Character.DIRECTIONALITY_LEFT_TO_RIGHT :
    655                 Character.DIRECTIONALITY_RIGHT_TO_LEFT;
    656 
    657         /*
    658          * XXX Explicit overrides should go here
    659          */
    660 
    661         /*
    662          * Weak type resolution
    663          */
    664 
    665         // dump(chdirs, n, "initial");
    666 
    667         // W1 non spacing marks
    668         for (int j = 0; j < n; j++) {
    669             if (chInfo[j] == Character.NON_SPACING_MARK) {
    670                 if (j == 0)
    671                     chInfo[j] = SOR;
    672                 else
    673                     chInfo[j] = chInfo[j - 1];
    674             }
    675         }
    676 
    677         // dump(chdirs, n, "W1");
    678 
    679         // W2 european numbers
    680         byte cur = SOR;
    681         for (int j = 0; j < n; j++) {
    682             byte d = chInfo[j];
    683 
    684             if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
    685                 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT ||
    686                 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
    687                 cur = d;
    688             else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) {
    689                  if (cur ==
    690                     Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
    691                     chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
    692             }
    693         }
    694 
    695         // dump(chdirs, n, "W2");
    696 
    697         // W3 arabic letters
    698         for (int j = 0; j < n; j++) {
    699             if (chInfo[j] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
    700                 chInfo[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
    701         }
    702 
    703         // dump(chdirs, n, "W3");
    704 
    705         // W4 single separator between numbers
    706         for (int j = 1; j < n - 1; j++) {
    707             byte d = chInfo[j];
    708             byte prev = chInfo[j - 1];
    709             byte next = chInfo[j + 1];
    710 
    711             if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR) {
    712                 if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
    713                     next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
    714                     chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
    715             } else if (d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) {
    716                 if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
    717                     next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
    718                     chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
    719                 if (prev == Character.DIRECTIONALITY_ARABIC_NUMBER &&
    720                     next == Character.DIRECTIONALITY_ARABIC_NUMBER)
    721                     chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
    722             }
    723         }
    724 
    725         // dump(chdirs, n, "W4");
    726 
    727         // W5 european number terminators
    728         boolean adjacent = false;
    729         for (int j = 0; j < n; j++) {
    730             byte d = chInfo[j];
    731 
    732             if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
    733                 adjacent = true;
    734             else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR && adjacent)
    735                 chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
    736             else
    737                 adjacent = false;
    738         }
    739 
    740         //dump(chdirs, n, "W5");
    741 
    742         // W5 european number terminators part 2,
    743         // W6 separators and terminators
    744         adjacent = false;
    745         for (int j = n - 1; j >= 0; j--) {
    746             byte d = chInfo[j];
    747 
    748             if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
    749                 adjacent = true;
    750             else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR) {
    751                 if (adjacent)
    752                     chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
    753                 else
    754                     chInfo[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
    755             }
    756             else {
    757                 adjacent = false;
    758 
    759                 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR ||
    760                     d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR ||
    761                     d == Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR ||
    762                     d == Character.DIRECTIONALITY_SEGMENT_SEPARATOR)
    763                     chInfo[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
    764             }
    765         }
    766 
    767         // dump(chdirs, n, "W6");
    768 
    769         // W7 strong direction of european numbers
    770         cur = SOR;
    771         for (int j = 0; j < n; j++) {
    772             byte d = chInfo[j];
    773 
    774             if (d == SOR ||
    775                 d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
    776                 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT)
    777                 cur = d;
    778 
    779             if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
    780                 chInfo[j] = cur;
    781         }
    782 
    783         // dump(chdirs, n, "W7");
    784 
    785         // N1, N2 neutrals
    786         cur = SOR;
    787         for (int j = 0; j < n; j++) {
    788             byte d = chInfo[j];
    789 
    790             if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
    791                 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
    792                 cur = d;
    793             } else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
    794                        d == Character.DIRECTIONALITY_ARABIC_NUMBER) {
    795                 cur = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
    796             } else {
    797                 byte dd = SOR;
    798                 int k;
    799 
    800                 for (k = j + 1; k < n; k++) {
    801                     dd = chInfo[k];
    802 
    803                     if (dd == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
    804                         dd == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
    805                         break;
    806                     }
    807                     if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
    808                         dd == Character.DIRECTIONALITY_ARABIC_NUMBER) {
    809                         dd = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
    810                         break;
    811                     }
    812                 }
    813 
    814                 for (int y = j; y < k; y++) {
    815                     if (dd == cur)
    816                         chInfo[y] = cur;
    817                     else
    818                         chInfo[y] = SOR;
    819                 }
    820 
    821                 j = k - 1;
    822             }
    823         }
    824 
    825         // dump(chdirs, n, "final");
    826 
    827         // extra: enforce that all tabs and surrogate characters go the
    828         // primary direction
    829         // TODO: actually do directions right for surrogates
    830 
    831         for (int j = 0; j < n; j++) {
    832             char c = chs[j];
    833 
    834             if (c == '\t' || (c >= 0xD800 && c <= 0xDFFF)) {
    835                 chInfo[j] = SOR;
    836             }
    837         }
    838 
    839         return dir;
    840     }
    841 
    842     private static final char FIRST_CJK = '\u2E80';
    843     /**
    844      * Returns true if the specified character is one of those specified
    845      * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm
    846      * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK
    847      * to break between a pair of.
    848      *
    849      * @param includeNonStarters also return true for category NS
    850      *                           (non-starters), which can be broken
    851      *                           after but not before.
    852      */
    853     private static final boolean isIdeographic(char c, boolean includeNonStarters) {
    854         if (c >= '\u2E80' && c <= '\u2FFF') {
    855             return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS
    856         }
    857         if (c == '\u3000') {
    858             return true; // IDEOGRAPHIC SPACE
    859         }
    860         if (c >= '\u3040' && c <= '\u309F') {
    861             if (!includeNonStarters) {
    862                 switch (c) {
    863                 case '\u3041': //  # HIRAGANA LETTER SMALL A
    864                 case '\u3043': //  # HIRAGANA LETTER SMALL I
    865                 case '\u3045': //  # HIRAGANA LETTER SMALL U
    866                 case '\u3047': //  # HIRAGANA LETTER SMALL E
    867                 case '\u3049': //  # HIRAGANA LETTER SMALL O
    868                 case '\u3063': //  # HIRAGANA LETTER SMALL TU
    869                 case '\u3083': //  # HIRAGANA LETTER SMALL YA
    870                 case '\u3085': //  # HIRAGANA LETTER SMALL YU
    871                 case '\u3087': //  # HIRAGANA LETTER SMALL YO
    872                 case '\u308E': //  # HIRAGANA LETTER SMALL WA
    873                 case '\u3095': //  # HIRAGANA LETTER SMALL KA
    874                 case '\u3096': //  # HIRAGANA LETTER SMALL KE
    875                 case '\u309B': //  # KATAKANA-HIRAGANA VOICED SOUND MARK
    876                 case '\u309C': //  # KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
    877                 case '\u309D': //  # HIRAGANA ITERATION MARK
    878                 case '\u309E': //  # HIRAGANA VOICED ITERATION MARK
    879                     return false;
    880                 }
    881             }
    882             return true; // Hiragana (except small characters)
    883         }
    884         if (c >= '\u30A0' && c <= '\u30FF') {
    885             if (!includeNonStarters) {
    886                 switch (c) {
    887                 case '\u30A0': //  # KATAKANA-HIRAGANA DOUBLE HYPHEN
    888                 case '\u30A1': //  # KATAKANA LETTER SMALL A
    889                 case '\u30A3': //  # KATAKANA LETTER SMALL I
    890                 case '\u30A5': //  # KATAKANA LETTER SMALL U
    891                 case '\u30A7': //  # KATAKANA LETTER SMALL E
    892                 case '\u30A9': //  # KATAKANA LETTER SMALL O
    893                 case '\u30C3': //  # KATAKANA LETTER SMALL TU
    894                 case '\u30E3': //  # KATAKANA LETTER SMALL YA
    895                 case '\u30E5': //  # KATAKANA LETTER SMALL YU
    896                 case '\u30E7': //  # KATAKANA LETTER SMALL YO
    897                 case '\u30EE': //  # KATAKANA LETTER SMALL WA
    898                 case '\u30F5': //  # KATAKANA LETTER SMALL KA
    899                 case '\u30F6': //  # KATAKANA LETTER SMALL KE
    900                 case '\u30FB': //  # KATAKANA MIDDLE DOT
    901                 case '\u30FC': //  # KATAKANA-HIRAGANA PROLONGED SOUND MARK
    902                 case '\u30FD': //  # KATAKANA ITERATION MARK
    903                 case '\u30FE': //  # KATAKANA VOICED ITERATION MARK
    904                     return false;
    905                 }
    906             }
    907             return true; // Katakana (except small characters)
    908         }
    909         if (c >= '\u3400' && c <= '\u4DB5') {
    910             return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A
    911         }
    912         if (c >= '\u4E00' && c <= '\u9FBB') {
    913             return true; // CJK UNIFIED IDEOGRAPHS
    914         }
    915         if (c >= '\uF900' && c <= '\uFAD9') {
    916             return true; // CJK COMPATIBILITY IDEOGRAPHS
    917         }
    918         if (c >= '\uA000' && c <= '\uA48F') {
    919             return true; // YI SYLLABLES
    920         }
    921         if (c >= '\uA490' && c <= '\uA4CF') {
    922             return true; // YI RADICALS
    923         }
    924         if (c >= '\uFE62' && c <= '\uFE66') {
    925             return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN
    926         }
    927         if (c >= '\uFF10' && c <= '\uFF19') {
    928             return true; // WIDE DIGITS
    929         }
    930 
    931         return false;
    932     }
    933 
    934 /*
    935     private static void dump(byte[] data, int count, String label) {
    936         if (false) {
    937             System.out.print(label);
    938 
    939             for (int i = 0; i < count; i++)
    940                 System.out.print(" " + data[i]);
    941 
    942             System.out.println();
    943         }
    944     }
    945 */
    946 
    947     private static int getFit(TextPaint paint,
    948                               TextPaint workPaint,
    949                        CharSequence text, int start, int end,
    950                        float wid) {
    951         int high = end + 1, low = start - 1, guess;
    952 
    953         while (high - low > 1) {
    954             guess = (high + low) / 2;
    955 
    956             if (measureText(paint, workPaint,
    957                             text, start, guess, null, true, null) > wid)
    958                 high = guess;
    959             else
    960                 low = guess;
    961         }
    962 
    963         if (low < start)
    964             return start;
    965         else
    966             return low;
    967     }
    968 
    969     private int out(CharSequence text, int start, int end,
    970                       int above, int below, int top, int bottom, int v,
    971                       float spacingmult, float spacingadd,
    972                       LineHeightSpan[] chooseht, int[] choosehtv,
    973                       Paint.FontMetricsInt fm, boolean tab,
    974                       boolean needMultiply, int pstart, byte[] chdirs,
    975                       int dir, boolean easy, boolean last,
    976                       boolean includepad, boolean trackpad,
    977                       float[] widths, int widstart, int widoff,
    978                       TextUtils.TruncateAt ellipsize, float ellipsiswidth,
    979                       float textwidth, TextPaint paint) {
    980         int j = mLineCount;
    981         int off = j * mColumns;
    982         int want = off + mColumns + TOP;
    983         int[] lines = mLines;
    984 
    985         // Log.e("text", "line " + start + " to " + end + (last ? "===" : ""));
    986 
    987         if (want >= lines.length) {
    988             int nlen = ArrayUtils.idealIntArraySize(want + 1);
    989             int[] grow = new int[nlen];
    990             System.arraycopy(lines, 0, grow, 0, lines.length);
    991             mLines = grow;
    992             lines = grow;
    993 
    994             Directions[] grow2 = new Directions[nlen];
    995             System.arraycopy(mLineDirections, 0, grow2, 0,
    996                              mLineDirections.length);
    997             mLineDirections = grow2;
    998         }
    999 
   1000         if (chooseht != null) {
   1001             fm.ascent = above;
   1002             fm.descent = below;
   1003             fm.top = top;
   1004             fm.bottom = bottom;
   1005 
   1006             for (int i = 0; i < chooseht.length; i++) {
   1007                 if (chooseht[i] instanceof LineHeightSpan.WithDensity) {
   1008                     ((LineHeightSpan.WithDensity) chooseht[i]).
   1009                         chooseHeight(text, start, end, choosehtv[i], v, fm, paint);
   1010 
   1011                 } else {
   1012                     chooseht[i].chooseHeight(text, start, end, choosehtv[i], v, fm);
   1013                 }
   1014             }
   1015 
   1016             above = fm.ascent;
   1017             below = fm.descent;
   1018             top = fm.top;
   1019             bottom = fm.bottom;
   1020         }
   1021 
   1022         if (j == 0) {
   1023             if (trackpad) {
   1024                 mTopPadding = top - above;
   1025             }
   1026 
   1027             if (includepad) {
   1028                 above = top;
   1029             }
   1030         }
   1031         if (last) {
   1032             if (trackpad) {
   1033                 mBottomPadding = bottom - below;
   1034             }
   1035 
   1036             if (includepad) {
   1037                 below = bottom;
   1038             }
   1039         }
   1040 
   1041         int extra;
   1042 
   1043         if (needMultiply) {
   1044             double ex = (below - above) * (spacingmult - 1) + spacingadd;
   1045             if (ex >= 0) {
   1046                 extra = (int)(ex + 0.5);
   1047             } else {
   1048                 extra = -(int)(-ex + 0.5);
   1049             }
   1050         } else {
   1051             extra = 0;
   1052         }
   1053 
   1054         lines[off + START] = start;
   1055         lines[off + TOP] = v;
   1056         lines[off + DESCENT] = below + extra;
   1057 
   1058         v += (below - above) + extra;
   1059         lines[off + mColumns + START] = end;
   1060         lines[off + mColumns + TOP] = v;
   1061 
   1062         if (tab)
   1063             lines[off + TAB] |= TAB_MASK;
   1064 
   1065         {
   1066             lines[off + DIR] |= dir << DIR_SHIFT;
   1067 
   1068             int cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
   1069             int count = 0;
   1070 
   1071             if (!easy) {
   1072                 for (int k = start; k < end; k++) {
   1073                     if (chdirs[k - pstart] != cur) {
   1074                         count++;
   1075                         cur = chdirs[k - pstart];
   1076                     }
   1077                 }
   1078             }
   1079 
   1080             Directions linedirs;
   1081 
   1082             if (count == 0) {
   1083                 linedirs = DIRS_ALL_LEFT_TO_RIGHT;
   1084             } else {
   1085                 short[] ld = new short[count + 1];
   1086 
   1087                 cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
   1088                 count = 0;
   1089                 int here = start;
   1090 
   1091                 for (int k = start; k < end; k++) {
   1092                     if (chdirs[k - pstart] != cur) {
   1093                         // XXX check to make sure we don't
   1094                         //     overflow short
   1095                         ld[count++] = (short) (k - here);
   1096                         cur = chdirs[k - pstart];
   1097                         here = k;
   1098                     }
   1099                 }
   1100 
   1101                 ld[count] = (short) (end - here);
   1102 
   1103                 if (count == 1 && ld[0] == 0) {
   1104                     linedirs = DIRS_ALL_RIGHT_TO_LEFT;
   1105                 } else {
   1106                     linedirs = new Directions(ld);
   1107                 }
   1108             }
   1109 
   1110             mLineDirections[j] = linedirs;
   1111 
   1112             // If ellipsize is in marquee mode, do not apply ellipsis on the first line
   1113             if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) {
   1114                 calculateEllipsis(start, end, widths, widstart, widoff,
   1115                                   ellipsiswidth, ellipsize, j,
   1116                                   textwidth, paint);
   1117             }
   1118         }
   1119 
   1120         mLineCount++;
   1121         return v;
   1122     }
   1123 
   1124     private void calculateEllipsis(int linestart, int lineend,
   1125                                    float[] widths, int widstart, int widoff,
   1126                                    float avail, TextUtils.TruncateAt where,
   1127                                    int line, float textwidth, TextPaint paint) {
   1128         int len = lineend - linestart;
   1129 
   1130         if (textwidth <= avail) {
   1131             // Everything fits!
   1132             mLines[mColumns * line + ELLIPSIS_START] = 0;
   1133             mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
   1134             return;
   1135         }
   1136 
   1137         float ellipsiswid = paint.measureText("\u2026");
   1138         int ellipsisStart, ellipsisCount;
   1139 
   1140         if (where == TextUtils.TruncateAt.START) {
   1141             float sum = 0;
   1142             int i;
   1143 
   1144             for (i = len; i >= 0; i--) {
   1145                 float w = widths[i - 1 + linestart - widstart + widoff];
   1146 
   1147                 if (w + sum + ellipsiswid > avail) {
   1148                     break;
   1149                 }
   1150 
   1151                 sum += w;
   1152             }
   1153 
   1154             ellipsisStart = 0;
   1155             ellipsisCount = i;
   1156         } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE) {
   1157             float sum = 0;
   1158             int i;
   1159 
   1160             for (i = 0; i < len; i++) {
   1161                 float w = widths[i + linestart - widstart + widoff];
   1162 
   1163                 if (w + sum + ellipsiswid > avail) {
   1164                     break;
   1165                 }
   1166 
   1167                 sum += w;
   1168             }
   1169 
   1170             ellipsisStart = i;
   1171             ellipsisCount = len - i;
   1172         } else /* where = TextUtils.TruncateAt.MIDDLE */ {
   1173             float lsum = 0, rsum = 0;
   1174             int left = 0, right = len;
   1175 
   1176             float ravail = (avail - ellipsiswid) / 2;
   1177             for (right = len; right >= 0; right--) {
   1178                 float w = widths[right - 1 + linestart - widstart + widoff];
   1179 
   1180                 if (w + rsum > ravail) {
   1181                     break;
   1182                 }
   1183 
   1184                 rsum += w;
   1185             }
   1186 
   1187             float lavail = avail - ellipsiswid - rsum;
   1188             for (left = 0; left < right; left++) {
   1189                 float w = widths[left + linestart - widstart + widoff];
   1190 
   1191                 if (w + lsum > lavail) {
   1192                     break;
   1193                 }
   1194 
   1195                 lsum += w;
   1196             }
   1197 
   1198             ellipsisStart = left;
   1199             ellipsisCount = right - left;
   1200         }
   1201 
   1202         mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
   1203         mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
   1204     }
   1205 
   1206     // Override the baseclass so we can directly access our members,
   1207     // rather than relying on member functions.
   1208     // The logic mirrors that of Layout.getLineForVertical
   1209     // FIXME: It may be faster to do a linear search for layouts without many lines.
   1210     public int getLineForVertical(int vertical) {
   1211         int high = mLineCount;
   1212         int low = -1;
   1213         int guess;
   1214         int[] lines = mLines;
   1215         while (high - low > 1) {
   1216             guess = (high + low) >> 1;
   1217             if (lines[mColumns * guess + TOP] > vertical){
   1218                 high = guess;
   1219             } else {
   1220                 low = guess;
   1221             }
   1222         }
   1223         if (low < 0) {
   1224             return 0;
   1225         } else {
   1226             return low;
   1227         }
   1228     }
   1229 
   1230     public int getLineCount() {
   1231         return mLineCount;
   1232     }
   1233 
   1234     public int getLineTop(int line) {
   1235         return mLines[mColumns * line + TOP];
   1236     }
   1237 
   1238     public int getLineDescent(int line) {
   1239         return mLines[mColumns * line + DESCENT];
   1240     }
   1241 
   1242     public int getLineStart(int line) {
   1243         return mLines[mColumns * line + START] & START_MASK;
   1244     }
   1245 
   1246     public int getParagraphDirection(int line) {
   1247         return mLines[mColumns * line + DIR] >> DIR_SHIFT;
   1248     }
   1249 
   1250     public boolean getLineContainsTab(int line) {
   1251         return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
   1252     }
   1253 
   1254     public final Directions getLineDirections(int line) {
   1255         return mLineDirections[line];
   1256     }
   1257 
   1258     public int getTopPadding() {
   1259         return mTopPadding;
   1260     }
   1261 
   1262     public int getBottomPadding() {
   1263         return mBottomPadding;
   1264     }
   1265 
   1266     @Override
   1267     public int getEllipsisCount(int line) {
   1268         if (mColumns < COLUMNS_ELLIPSIZE) {
   1269             return 0;
   1270         }
   1271 
   1272         return mLines[mColumns * line + ELLIPSIS_COUNT];
   1273     }
   1274 
   1275     @Override
   1276     public int getEllipsisStart(int line) {
   1277         if (mColumns < COLUMNS_ELLIPSIZE) {
   1278             return 0;
   1279         }
   1280 
   1281         return mLines[mColumns * line + ELLIPSIS_START];
   1282     }
   1283 
   1284     @Override
   1285     public int getEllipsizedWidth() {
   1286         return mEllipsizedWidth;
   1287     }
   1288 
   1289     private int mLineCount;
   1290     private int mTopPadding, mBottomPadding;
   1291     private int mColumns;
   1292     private int mEllipsizedWidth;
   1293 
   1294     private static final int COLUMNS_NORMAL = 3;
   1295     private static final int COLUMNS_ELLIPSIZE = 5;
   1296     private static final int START = 0;
   1297     private static final int DIR = START;
   1298     private static final int TAB = START;
   1299     private static final int TOP = 1;
   1300     private static final int DESCENT = 2;
   1301     private static final int ELLIPSIS_START = 3;
   1302     private static final int ELLIPSIS_COUNT = 4;
   1303 
   1304     private int[] mLines;
   1305     private Directions[] mLineDirections;
   1306 
   1307     private static final int START_MASK = 0x1FFFFFFF;
   1308     private static final int DIR_MASK   = 0xC0000000;
   1309     private static final int DIR_SHIFT  = 30;
   1310     private static final int TAB_MASK   = 0x20000000;
   1311 
   1312     private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
   1313 
   1314     /*
   1315      * These are reused across calls to generate()
   1316      */
   1317     private byte[] mChdirs;
   1318     private char[] mChs;
   1319     private float[] mWidths;
   1320     private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
   1321 }
   1322