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