• 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.emoji.EmojiFactory;
20 import android.graphics.Bitmap;
21 import android.graphics.Canvas;
22 import android.graphics.Paint;
23 import android.graphics.Rect;
24 import android.graphics.RectF;
25 import android.graphics.Path;
26 import com.android.internal.util.ArrayUtils;
27 
28 import junit.framework.Assert;
29 import android.text.style.*;
30 import android.text.method.TextKeyListener;
31 import android.view.KeyEvent;
32 
33 /**
34  * A base class that manages text layout in visual elements on
35  * the screen.
36  * <p>For text that will be edited, use a {@link DynamicLayout},
37  * which will be updated as the text changes.
38  * For text that will not change, use a {@link StaticLayout}.
39  */
40 public abstract class Layout {
41     private static final boolean DEBUG = false;
42     private static final ParagraphStyle[] NO_PARA_SPANS =
43         ArrayUtils.emptyArray(ParagraphStyle.class);
44 
45     /* package */ static final EmojiFactory EMOJI_FACTORY =
46         EmojiFactory.newAvailableInstance();
47     /* package */ static final int MIN_EMOJI, MAX_EMOJI;
48 
49     static {
50         if (EMOJI_FACTORY != null) {
51             MIN_EMOJI = EMOJI_FACTORY.getMinimumAndroidPua();
52             MAX_EMOJI = EMOJI_FACTORY.getMaximumAndroidPua();
53         } else {
54             MIN_EMOJI = -1;
55             MAX_EMOJI = -1;
56         }
57     };
58 
59     private RectF mEmojiRect;
60 
61     /**
62      * Return how wide a layout must be in order to display the
63      * specified text with one line per paragraph.
64      */
getDesiredWidth(CharSequence source, TextPaint paint)65     public static float getDesiredWidth(CharSequence source,
66                                         TextPaint paint) {
67         return getDesiredWidth(source, 0, source.length(), paint);
68     }
69 
70     /**
71      * Return how wide a layout must be in order to display the
72      * specified text slice with one line per paragraph.
73      */
getDesiredWidth(CharSequence source, int start, int end, TextPaint paint)74     public static float getDesiredWidth(CharSequence source,
75                                         int start, int end,
76                                         TextPaint paint) {
77         float need = 0;
78         TextPaint workPaint = new TextPaint();
79 
80         int next;
81         for (int i = start; i <= end; i = next) {
82             next = TextUtils.indexOf(source, '\n', i, end);
83 
84             if (next < 0)
85                 next = end;
86 
87             // note, omits trailing paragraph char
88             float w = measureText(paint, workPaint,
89                                   source, i, next, null, true, null);
90 
91             if (w > need)
92                 need = w;
93 
94             next++;
95         }
96 
97         return need;
98     }
99 
100     /**
101      * Subclasses of Layout use this constructor to set the display text,
102      * width, and other standard properties.
103      * @param text the text to render
104      * @param paint the default paint for the layout.  Styles can override
105      * various attributes of the paint.
106      * @param width the wrapping width for the text.
107      * @param align whether to left, right, or center the text.  Styles can
108      * override the alignment.
109      * @param spacingMult factor by which to scale the font size to get the
110      * default line spacing
111      * @param spacingAdd amount to add to the default line spacing
112      */
Layout(CharSequence text, TextPaint paint, int width, Alignment align, float spacingMult, float spacingAdd)113     protected Layout(CharSequence text, TextPaint paint,
114                      int width, Alignment align,
115                      float spacingMult, float spacingAdd) {
116         if (width < 0)
117             throw new IllegalArgumentException("Layout: " + width + " < 0");
118 
119         mText = text;
120         mPaint = paint;
121         mWorkPaint = new TextPaint();
122         mWidth = width;
123         mAlignment = align;
124         mSpacingMult = spacingMult;
125         mSpacingAdd = spacingAdd;
126         mSpannedText = text instanceof Spanned;
127     }
128 
129     /**
130      * Replace constructor properties of this Layout with new ones.  Be careful.
131      */
replaceWith(CharSequence text, TextPaint paint, int width, Alignment align, float spacingmult, float spacingadd)132     /* package */ void replaceWith(CharSequence text, TextPaint paint,
133                               int width, Alignment align,
134                               float spacingmult, float spacingadd) {
135         if (width < 0) {
136             throw new IllegalArgumentException("Layout: " + width + " < 0");
137         }
138 
139         mText = text;
140         mPaint = paint;
141         mWidth = width;
142         mAlignment = align;
143         mSpacingMult = spacingmult;
144         mSpacingAdd = spacingadd;
145         mSpannedText = text instanceof Spanned;
146     }
147 
148     /**
149      * Draw this Layout on the specified Canvas.
150      */
draw(Canvas c)151     public void draw(Canvas c) {
152         draw(c, null, null, 0);
153     }
154 
155     /**
156      * Draw this Layout on the specified canvas, with the highlight path drawn
157      * between the background and the text.
158      *
159      * @param c the canvas
160      * @param highlight the path of the highlight or cursor; can be null
161      * @param highlightPaint the paint for the highlight
162      * @param cursorOffsetVertical the amount to temporarily translate the
163      *        canvas while rendering the highlight
164      */
draw(Canvas c, Path highlight, Paint highlightPaint, int cursorOffsetVertical)165     public void draw(Canvas c, Path highlight, Paint highlightPaint,
166                      int cursorOffsetVertical) {
167         int dtop, dbottom;
168 
169         synchronized (sTempRect) {
170             if (!c.getClipBounds(sTempRect)) {
171                 return;
172             }
173 
174             dtop = sTempRect.top;
175             dbottom = sTempRect.bottom;
176         }
177 
178 
179         int top = 0;
180         int bottom = getLineTop(getLineCount());
181 
182         if (dtop > top) {
183             top = dtop;
184         }
185         if (dbottom < bottom) {
186             bottom = dbottom;
187         }
188 
189         int first = getLineForVertical(top);
190         int last = getLineForVertical(bottom);
191 
192         int previousLineBottom = getLineTop(first);
193         int previousLineEnd = getLineStart(first);
194 
195         TextPaint paint = mPaint;
196         CharSequence buf = mText;
197         int width = mWidth;
198         boolean spannedText = mSpannedText;
199 
200         ParagraphStyle[] spans = NO_PARA_SPANS;
201         int spanend = 0;
202         int textLength = 0;
203 
204         // First, draw LineBackgroundSpans.
205         // LineBackgroundSpans know nothing about the alignment or direction of
206         // the layout or line.  XXX: Should they?
207         if (spannedText) {
208             textLength = buf.length();
209             for (int i = first; i <= last; i++) {
210                 int start = previousLineEnd;
211                 int end = getLineStart(i+1);
212                 previousLineEnd = end;
213 
214                 int ltop = previousLineBottom;
215                 int lbottom = getLineTop(i+1);
216                 previousLineBottom = lbottom;
217                 int lbaseline = lbottom - getLineDescent(i);
218 
219                 if (start >= spanend) {
220                    Spanned sp = (Spanned) buf;
221                    spanend = sp.nextSpanTransition(start, textLength,
222                                                    LineBackgroundSpan.class);
223                    spans = sp.getSpans(start, spanend,
224                                        LineBackgroundSpan.class);
225                 }
226 
227                 for (int n = 0; n < spans.length; n++) {
228                     LineBackgroundSpan back = (LineBackgroundSpan) spans[n];
229 
230                     back.drawBackground(c, paint, 0, width,
231                                        ltop, lbaseline, lbottom,
232                                        buf, start, end,
233                                        i);
234                 }
235             }
236             // reset to their original values
237             spanend = 0;
238             previousLineBottom = getLineTop(first);
239             previousLineEnd = getLineStart(first);
240             spans = NO_PARA_SPANS;
241         }
242 
243         // There can be a highlight even without spans if we are drawing
244         // a non-spanned transformation of a spanned editing buffer.
245         if (highlight != null) {
246             if (cursorOffsetVertical != 0) {
247                 c.translate(0, cursorOffsetVertical);
248             }
249 
250             c.drawPath(highlight, highlightPaint);
251 
252             if (cursorOffsetVertical != 0) {
253                 c.translate(0, -cursorOffsetVertical);
254             }
255         }
256 
257         Alignment align = mAlignment;
258 
259         // Next draw the lines, one at a time.
260         // the baseline is the top of the following line minus the current
261         // line's descent.
262         for (int i = first; i <= last; i++) {
263             int start = previousLineEnd;
264 
265             previousLineEnd = getLineStart(i+1);
266             int end = getLineVisibleEnd(i, start, previousLineEnd);
267 
268             int ltop = previousLineBottom;
269             int lbottom = getLineTop(i+1);
270             previousLineBottom = lbottom;
271             int lbaseline = lbottom - getLineDescent(i);
272 
273             boolean isFirstParaLine = false;
274             if (spannedText) {
275                 if (start == 0 || buf.charAt(start - 1) == '\n') {
276                     isFirstParaLine = true;
277                 }
278                 // New batch of paragraph styles, compute the alignment.
279                 // Last alignment style wins.
280                 if (start >= spanend) {
281                     Spanned sp = (Spanned) buf;
282                     spanend = sp.nextSpanTransition(start, textLength,
283                                                     ParagraphStyle.class);
284                     spans = sp.getSpans(start, spanend, ParagraphStyle.class);
285 
286                     align = mAlignment;
287                     for (int n = spans.length-1; n >= 0; n--) {
288                         if (spans[n] instanceof AlignmentSpan) {
289                             align = ((AlignmentSpan) spans[n]).getAlignment();
290                             break;
291                         }
292                     }
293                 }
294             }
295 
296             int dir = getParagraphDirection(i);
297             int left = 0;
298             int right = mWidth;
299 
300             // Draw all leading margin spans.  Adjust left or right according
301             // to the paragraph direction of the line.
302             if (spannedText) {
303                 final int length = spans.length;
304                 for (int n = 0; n < length; n++) {
305                     if (spans[n] instanceof LeadingMarginSpan) {
306                         LeadingMarginSpan margin = (LeadingMarginSpan) spans[n];
307 
308                         if (dir == DIR_RIGHT_TO_LEFT) {
309                             margin.drawLeadingMargin(c, paint, right, dir, ltop,
310                                                      lbaseline, lbottom, buf,
311                                                      start, end, isFirstParaLine, this);
312 
313                             right -= margin.getLeadingMargin(isFirstParaLine);
314                         } else {
315                             margin.drawLeadingMargin(c, paint, left, dir, ltop,
316                                                      lbaseline, lbottom, buf,
317                                                      start, end, isFirstParaLine, this);
318 
319                             boolean useMargin = isFirstParaLine;
320                             if (margin instanceof LeadingMarginSpan.LeadingMarginSpan2) {
321                                 int count = ((LeadingMarginSpan.LeadingMarginSpan2)margin).getLeadingMarginLineCount();
322                                 useMargin = count > i;
323                             }
324                             left += margin.getLeadingMargin(useMargin);
325                         }
326                     }
327                 }
328             }
329 
330             // Adjust the point at which to start rendering depending on the
331             // alignment of the paragraph.
332             int x;
333             if (align == Alignment.ALIGN_NORMAL) {
334                 if (dir == DIR_LEFT_TO_RIGHT) {
335                     x = left;
336                 } else {
337                     x = right;
338                 }
339             } else {
340                 int max = (int)getLineMax(i, spans, false);
341                 if (align == Alignment.ALIGN_OPPOSITE) {
342                     if (dir == DIR_RIGHT_TO_LEFT) {
343                         x = left + max;
344                     } else {
345                         x = right - max;
346                     }
347                 } else {
348                     // Alignment.ALIGN_CENTER
349                     max = max & ~1;
350                     int half = (right - left - max) >> 1;
351                     if (dir == DIR_RIGHT_TO_LEFT) {
352                         x = right - half;
353                     } else {
354                         x = left + half;
355                     }
356                 }
357             }
358 
359             Directions directions = getLineDirections(i);
360             boolean hasTab = getLineContainsTab(i);
361             if (directions == DIRS_ALL_LEFT_TO_RIGHT &&
362                     !spannedText && !hasTab) {
363                 if (DEBUG) {
364                     Assert.assertTrue(dir == DIR_LEFT_TO_RIGHT);
365                     Assert.assertNotNull(c);
366                 }
367                 // XXX: assumes there's nothing additional to be done
368                 c.drawText(buf, start, end, x, lbaseline, paint);
369             } else {
370                 drawText(c, buf, start, end, dir, directions,
371                     x, ltop, lbaseline, lbottom, paint, mWorkPaint,
372                     hasTab, spans);
373             }
374         }
375     }
376 
377     /**
378      * Return the text that is displayed by this Layout.
379      */
getText()380     public final CharSequence getText() {
381         return mText;
382     }
383 
384     /**
385      * Return the base Paint properties for this layout.
386      * Do NOT change the paint, which may result in funny
387      * drawing for this layout.
388      */
getPaint()389     public final TextPaint getPaint() {
390         return mPaint;
391     }
392 
393     /**
394      * Return the width of this layout.
395      */
getWidth()396     public final int getWidth() {
397         return mWidth;
398     }
399 
400     /**
401      * Return the width to which this Layout is ellipsizing, or
402      * {@link #getWidth} if it is not doing anything special.
403      */
getEllipsizedWidth()404     public int getEllipsizedWidth() {
405         return mWidth;
406     }
407 
408     /**
409      * Increase the width of this layout to the specified width.
410      * Be careful to use this only when you know it is appropriate&mdash;
411      * it does not cause the text to reflow to use the full new width.
412      */
increaseWidthTo(int wid)413     public final void increaseWidthTo(int wid) {
414         if (wid < mWidth) {
415             throw new RuntimeException("attempted to reduce Layout width");
416         }
417 
418         mWidth = wid;
419     }
420 
421     /**
422      * Return the total height of this layout.
423      */
getHeight()424     public int getHeight() {
425         return getLineTop(getLineCount());
426     }
427 
428     /**
429      * Return the base alignment of this layout.
430      */
getAlignment()431     public final Alignment getAlignment() {
432         return mAlignment;
433     }
434 
435     /**
436      * Return what the text height is multiplied by to get the line height.
437      */
getSpacingMultiplier()438     public final float getSpacingMultiplier() {
439         return mSpacingMult;
440     }
441 
442     /**
443      * Return the number of units of leading that are added to each line.
444      */
getSpacingAdd()445     public final float getSpacingAdd() {
446         return mSpacingAdd;
447     }
448 
449     /**
450      * Return the number of lines of text in this layout.
451      */
getLineCount()452     public abstract int getLineCount();
453 
454     /**
455      * Return the baseline for the specified line (0&hellip;getLineCount() - 1)
456      * If bounds is not null, return the top, left, right, bottom extents
457      * of the specified line in it.
458      * @param line which line to examine (0..getLineCount() - 1)
459      * @param bounds Optional. If not null, it returns the extent of the line
460      * @return the Y-coordinate of the baseline
461      */
getLineBounds(int line, Rect bounds)462     public int getLineBounds(int line, Rect bounds) {
463         if (bounds != null) {
464             bounds.left = 0;     // ???
465             bounds.top = getLineTop(line);
466             bounds.right = mWidth;   // ???
467             bounds.bottom = getLineTop(line + 1);
468         }
469         return getLineBaseline(line);
470     }
471 
472     /**
473      * Return the vertical position of the top of the specified line
474      * (0&hellip;getLineCount()).
475      * If the specified line is equal to the line count, returns the
476      * bottom of the last line.
477      */
getLineTop(int line)478     public abstract int getLineTop(int line);
479 
480     /**
481      * Return the descent of the specified line(0&hellip;getLineCount() - 1).
482      */
getLineDescent(int line)483     public abstract int getLineDescent(int line);
484 
485     /**
486      * Return the text offset of the beginning of the specified line (
487      * 0&hellip;getLineCount()). If the specified line is equal to the line
488      * count, returns the length of the text.
489      */
getLineStart(int line)490     public abstract int getLineStart(int line);
491 
492     /**
493      * Returns the primary directionality of the paragraph containing the
494      * specified line, either 1 for left-to-right lines, or -1 for right-to-left
495      * lines (see {@link #DIR_LEFT_TO_RIGHT}, {@link #DIR_RIGHT_TO_LEFT}).
496      */
getParagraphDirection(int line)497     public abstract int getParagraphDirection(int line);
498 
499     /**
500      * Returns whether the specified line contains one or more
501      * characters that need to be handled specially, like tabs
502      * or emoji.
503      */
getLineContainsTab(int line)504     public abstract boolean getLineContainsTab(int line);
505 
506     /**
507      * Returns the directional run information for the specified line.
508      * The array alternates counts of characters in left-to-right
509      * and right-to-left segments of the line.
510      *
511      * <p>NOTE: this is inadequate to support bidirectional text, and will change.
512      */
getLineDirections(int line)513     public abstract Directions getLineDirections(int line);
514 
515     /**
516      * Returns the (negative) number of extra pixels of ascent padding in the
517      * top line of the Layout.
518      */
getTopPadding()519     public abstract int getTopPadding();
520 
521     /**
522      * Returns the number of extra pixels of descent padding in the
523      * bottom line of the Layout.
524      */
getBottomPadding()525     public abstract int getBottomPadding();
526 
527     /**
528      * Get the primary horizontal position for the specified text offset.
529      * This is the location where a new character would be inserted in
530      * the paragraph's primary direction.
531      */
getPrimaryHorizontal(int offset)532     public float getPrimaryHorizontal(int offset) {
533         return getHorizontal(offset, false, true);
534     }
535 
536     /**
537      * Get the secondary horizontal position for the specified text offset.
538      * This is the location where a new character would be inserted in
539      * the direction other than the paragraph's primary direction.
540      */
getSecondaryHorizontal(int offset)541     public float getSecondaryHorizontal(int offset) {
542         return getHorizontal(offset, true, true);
543     }
544 
getHorizontal(int offset, boolean trailing, boolean alt)545     private float getHorizontal(int offset, boolean trailing, boolean alt) {
546         int line = getLineForOffset(offset);
547 
548         return getHorizontal(offset, trailing, alt, line);
549     }
550 
getHorizontal(int offset, boolean trailing, boolean alt, int line)551     private float getHorizontal(int offset, boolean trailing, boolean alt,
552                                 int line) {
553         int start = getLineStart(line);
554         int end = getLineVisibleEnd(line);
555         int dir = getParagraphDirection(line);
556         boolean tab = getLineContainsTab(line);
557         Directions directions = getLineDirections(line);
558 
559         TabStopSpan[] tabs = null;
560         if (tab && mText instanceof Spanned) {
561             tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
562         }
563 
564         float wid = measureText(mPaint, mWorkPaint, mText, start, offset, end,
565                                 dir, directions, trailing, alt, tab, tabs);
566 
567         if (offset > end) {
568             if (dir == DIR_RIGHT_TO_LEFT)
569                 wid -= measureText(mPaint, mWorkPaint,
570                                    mText, end, offset, null, tab, tabs);
571             else
572                 wid += measureText(mPaint, mWorkPaint,
573                                    mText, end, offset, null, tab, tabs);
574         }
575 
576         Alignment align = getParagraphAlignment(line);
577         int left = getParagraphLeft(line);
578         int right = getParagraphRight(line);
579 
580         if (align == Alignment.ALIGN_NORMAL) {
581             if (dir == DIR_RIGHT_TO_LEFT)
582                 return right + wid;
583             else
584                 return left + wid;
585         }
586 
587         float max = getLineMax(line);
588 
589         if (align == Alignment.ALIGN_OPPOSITE) {
590             if (dir == DIR_RIGHT_TO_LEFT)
591                 return left + max + wid;
592             else
593                 return right - max + wid;
594         } else { /* align == Alignment.ALIGN_CENTER */
595             int imax = ((int) max) & ~1;
596 
597             if (dir == DIR_RIGHT_TO_LEFT)
598                 return right - (((right - left) - imax) / 2) + wid;
599             else
600                 return left + ((right - left) - imax) / 2 + wid;
601         }
602     }
603 
604     /**
605      * Get the leftmost position that should be exposed for horizontal
606      * scrolling on the specified line.
607      */
getLineLeft(int line)608     public float getLineLeft(int line) {
609         int dir = getParagraphDirection(line);
610         Alignment align = getParagraphAlignment(line);
611 
612         if (align == Alignment.ALIGN_NORMAL) {
613             if (dir == DIR_RIGHT_TO_LEFT)
614                 return getParagraphRight(line) - getLineMax(line);
615             else
616                 return 0;
617         } else if (align == Alignment.ALIGN_OPPOSITE) {
618             if (dir == DIR_RIGHT_TO_LEFT)
619                 return 0;
620             else
621                 return mWidth - getLineMax(line);
622         } else { /* align == Alignment.ALIGN_CENTER */
623             int left = getParagraphLeft(line);
624             int right = getParagraphRight(line);
625             int max = ((int) getLineMax(line)) & ~1;
626 
627             return left + ((right - left) - max) / 2;
628         }
629     }
630 
631     /**
632      * Get the rightmost position that should be exposed for horizontal
633      * scrolling on the specified line.
634      */
getLineRight(int line)635     public float getLineRight(int line) {
636         int dir = getParagraphDirection(line);
637         Alignment align = getParagraphAlignment(line);
638 
639         if (align == Alignment.ALIGN_NORMAL) {
640             if (dir == DIR_RIGHT_TO_LEFT)
641                 return mWidth;
642             else
643                 return getParagraphLeft(line) + getLineMax(line);
644         } else if (align == Alignment.ALIGN_OPPOSITE) {
645             if (dir == DIR_RIGHT_TO_LEFT)
646                 return getLineMax(line);
647             else
648                 return mWidth;
649         } else { /* align == Alignment.ALIGN_CENTER */
650             int left = getParagraphLeft(line);
651             int right = getParagraphRight(line);
652             int max = ((int) getLineMax(line)) & ~1;
653 
654             return right - ((right - left) - max) / 2;
655         }
656     }
657 
658     /**
659      * Gets the horizontal extent of the specified line, excluding
660      * trailing whitespace.
661      */
getLineMax(int line)662     public float getLineMax(int line) {
663         return getLineMax(line, null, false);
664     }
665 
666     /**
667      * Gets the horizontal extent of the specified line, including
668      * trailing whitespace.
669      */
getLineWidth(int line)670     public float getLineWidth(int line) {
671         return getLineMax(line, null, true);
672     }
673 
getLineMax(int line, Object[] tabs, boolean full)674     private float getLineMax(int line, Object[] tabs, boolean full) {
675         int start = getLineStart(line);
676         int end;
677 
678         if (full) {
679             end = getLineEnd(line);
680         } else {
681             end = getLineVisibleEnd(line);
682         }
683         boolean tab = getLineContainsTab(line);
684 
685         if (tabs == null && tab && mText instanceof Spanned) {
686             tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
687         }
688 
689         return measureText(mPaint, mWorkPaint,
690                            mText, start, end, null, tab, tabs);
691     }
692 
693     /**
694      * Get the line number corresponding to the specified vertical position.
695      * If you ask for a position above 0, you get 0; if you ask for a position
696      * below the bottom of the text, you get the last line.
697      */
698     // FIXME: It may be faster to do a linear search for layouts without many lines.
getLineForVertical(int vertical)699     public int getLineForVertical(int vertical) {
700         int high = getLineCount(), low = -1, guess;
701 
702         while (high - low > 1) {
703             guess = (high + low) / 2;
704 
705             if (getLineTop(guess) > vertical)
706                 high = guess;
707             else
708                 low = guess;
709         }
710 
711         if (low < 0)
712             return 0;
713         else
714             return low;
715     }
716 
717     /**
718      * Get the line number on which the specified text offset appears.
719      * If you ask for a position before 0, you get 0; if you ask for a position
720      * beyond the end of the text, you get the last line.
721      */
getLineForOffset(int offset)722     public int getLineForOffset(int offset) {
723         int high = getLineCount(), low = -1, guess;
724 
725         while (high - low > 1) {
726             guess = (high + low) / 2;
727 
728             if (getLineStart(guess) > offset)
729                 high = guess;
730             else
731                 low = guess;
732         }
733 
734         if (low < 0)
735             return 0;
736         else
737             return low;
738     }
739 
740     /**
741      * Get the character offset on the specfied line whose position is
742      * closest to the specified horizontal position.
743      */
getOffsetForHorizontal(int line, float horiz)744     public int getOffsetForHorizontal(int line, float horiz) {
745         int max = getLineEnd(line) - 1;
746         int min = getLineStart(line);
747         Directions dirs = getLineDirections(line);
748 
749         if (line == getLineCount() - 1)
750             max++;
751 
752         int best = min;
753         float bestdist = Math.abs(getPrimaryHorizontal(best) - horiz);
754 
755         int here = min;
756         for (int i = 0; i < dirs.mDirections.length; i++) {
757             int there = here + dirs.mDirections[i];
758             int swap = ((i & 1) == 0) ? 1 : -1;
759 
760             if (there > max)
761                 there = max;
762 
763             int high = there - 1 + 1, low = here + 1 - 1, guess;
764 
765             while (high - low > 1) {
766                 guess = (high + low) / 2;
767                 int adguess = getOffsetAtStartOf(guess);
768 
769                 if (getPrimaryHorizontal(adguess) * swap >= horiz * swap)
770                     high = guess;
771                 else
772                     low = guess;
773             }
774 
775             if (low < here + 1)
776                 low = here + 1;
777 
778             if (low < there) {
779                 low = getOffsetAtStartOf(low);
780 
781                 float dist = Math.abs(getPrimaryHorizontal(low) - horiz);
782 
783                 int aft = TextUtils.getOffsetAfter(mText, low);
784                 if (aft < there) {
785                     float other = Math.abs(getPrimaryHorizontal(aft) - horiz);
786 
787                     if (other < dist) {
788                         dist = other;
789                         low = aft;
790                     }
791                 }
792 
793                 if (dist < bestdist) {
794                     bestdist = dist;
795                     best = low;
796                 }
797             }
798 
799             float dist = Math.abs(getPrimaryHorizontal(here) - horiz);
800 
801             if (dist < bestdist) {
802                 bestdist = dist;
803                 best = here;
804             }
805 
806             here = there;
807         }
808 
809         float dist = Math.abs(getPrimaryHorizontal(max) - horiz);
810 
811         if (dist < bestdist) {
812             bestdist = dist;
813             best = max;
814         }
815 
816         return best;
817     }
818 
819     /**
820      * Return the text offset after the last character on the specified line.
821      */
getLineEnd(int line)822     public final int getLineEnd(int line) {
823         return getLineStart(line + 1);
824     }
825 
826     /**
827      * Return the text offset after the last visible character (so whitespace
828      * is not counted) on the specified line.
829      */
getLineVisibleEnd(int line)830     public int getLineVisibleEnd(int line) {
831         return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1));
832     }
833 
getLineVisibleEnd(int line, int start, int end)834     private int getLineVisibleEnd(int line, int start, int end) {
835         if (DEBUG) {
836             Assert.assertTrue(getLineStart(line) == start && getLineStart(line+1) == end);
837         }
838 
839         CharSequence text = mText;
840         char ch;
841         if (line == getLineCount() - 1) {
842             return end;
843         }
844 
845         for (; end > start; end--) {
846             ch = text.charAt(end - 1);
847 
848             if (ch == '\n') {
849                 return end - 1;
850             }
851 
852             if (ch != ' ' && ch != '\t') {
853                 break;
854             }
855 
856         }
857 
858         return end;
859     }
860 
861     /**
862      * Return the vertical position of the bottom of the specified line.
863      */
getLineBottom(int line)864     public final int getLineBottom(int line) {
865         return getLineTop(line + 1);
866     }
867 
868     /**
869      * Return the vertical position of the baseline of the specified line.
870      */
getLineBaseline(int line)871     public final int getLineBaseline(int line) {
872         // getLineTop(line+1) == getLineTop(line)
873         return getLineTop(line+1) - getLineDescent(line);
874     }
875 
876     /**
877      * Get the ascent of the text on the specified line.
878      * The return value is negative to match the Paint.ascent() convention.
879      */
getLineAscent(int line)880     public final int getLineAscent(int line) {
881         // getLineTop(line+1) - getLineDescent(line) == getLineBaseLine(line)
882         return getLineTop(line) - (getLineTop(line+1) - getLineDescent(line));
883     }
884 
885     /**
886      * Return the text offset that would be reached by moving left
887      * (possibly onto another line) from the specified offset.
888      */
getOffsetToLeftOf(int offset)889     public int getOffsetToLeftOf(int offset) {
890         int line = getLineForOffset(offset);
891         int start = getLineStart(line);
892         int end = getLineEnd(line);
893         Directions dirs = getLineDirections(line);
894 
895         if (line != getLineCount() - 1)
896             end--;
897 
898         float horiz = getPrimaryHorizontal(offset);
899 
900         int best = offset;
901         float besth = Integer.MIN_VALUE;
902         int candidate;
903 
904         candidate = TextUtils.getOffsetBefore(mText, offset);
905         if (candidate >= start && candidate <= end) {
906             float h = getPrimaryHorizontal(candidate);
907 
908             if (h < horiz && h > besth) {
909                 best = candidate;
910                 besth = h;
911             }
912         }
913 
914         candidate = TextUtils.getOffsetAfter(mText, offset);
915         if (candidate >= start && candidate <= end) {
916             float h = getPrimaryHorizontal(candidate);
917 
918             if (h < horiz && h > besth) {
919                 best = candidate;
920                 besth = h;
921             }
922         }
923 
924         int here = start;
925         for (int i = 0; i < dirs.mDirections.length; i++) {
926             int there = here + dirs.mDirections[i];
927             if (there > end)
928                 there = end;
929 
930             float h = getPrimaryHorizontal(here);
931 
932             if (h < horiz && h > besth) {
933                 best = here;
934                 besth = h;
935             }
936 
937             candidate = TextUtils.getOffsetAfter(mText, here);
938             if (candidate >= start && candidate <= end) {
939                 h = getPrimaryHorizontal(candidate);
940 
941                 if (h < horiz && h > besth) {
942                     best = candidate;
943                     besth = h;
944                 }
945             }
946 
947             candidate = TextUtils.getOffsetBefore(mText, there);
948             if (candidate >= start && candidate <= end) {
949                 h = getPrimaryHorizontal(candidate);
950 
951                 if (h < horiz && h > besth) {
952                     best = candidate;
953                     besth = h;
954                 }
955             }
956 
957             here = there;
958         }
959 
960         float h = getPrimaryHorizontal(end);
961 
962         if (h < horiz && h > besth) {
963             best = end;
964             besth = h;
965         }
966 
967         if (best != offset)
968             return best;
969 
970         int dir = getParagraphDirection(line);
971 
972         if (dir > 0) {
973             if (line == 0)
974                 return best;
975             else
976                 return getOffsetForHorizontal(line - 1, 10000);
977         } else {
978             if (line == getLineCount() - 1)
979                 return best;
980             else
981                 return getOffsetForHorizontal(line + 1, 10000);
982         }
983     }
984 
985     /**
986      * Return the text offset that would be reached by moving right
987      * (possibly onto another line) from the specified offset.
988      */
getOffsetToRightOf(int offset)989     public int getOffsetToRightOf(int offset) {
990         int line = getLineForOffset(offset);
991         int start = getLineStart(line);
992         int end = getLineEnd(line);
993         Directions dirs = getLineDirections(line);
994 
995         if (line != getLineCount() - 1)
996             end--;
997 
998         float horiz = getPrimaryHorizontal(offset);
999 
1000         int best = offset;
1001         float besth = Integer.MAX_VALUE;
1002         int candidate;
1003 
1004         candidate = TextUtils.getOffsetBefore(mText, offset);
1005         if (candidate >= start && candidate <= end) {
1006             float h = getPrimaryHorizontal(candidate);
1007 
1008             if (h > horiz && h < besth) {
1009                 best = candidate;
1010                 besth = h;
1011             }
1012         }
1013 
1014         candidate = TextUtils.getOffsetAfter(mText, offset);
1015         if (candidate >= start && candidate <= end) {
1016             float h = getPrimaryHorizontal(candidate);
1017 
1018             if (h > horiz && h < besth) {
1019                 best = candidate;
1020                 besth = h;
1021             }
1022         }
1023 
1024         int here = start;
1025         for (int i = 0; i < dirs.mDirections.length; i++) {
1026             int there = here + dirs.mDirections[i];
1027             if (there > end)
1028                 there = end;
1029 
1030             float h = getPrimaryHorizontal(here);
1031 
1032             if (h > horiz && h < besth) {
1033                 best = here;
1034                 besth = h;
1035             }
1036 
1037             candidate = TextUtils.getOffsetAfter(mText, here);
1038             if (candidate >= start && candidate <= end) {
1039                 h = getPrimaryHorizontal(candidate);
1040 
1041                 if (h > horiz && h < besth) {
1042                     best = candidate;
1043                     besth = h;
1044                 }
1045             }
1046 
1047             candidate = TextUtils.getOffsetBefore(mText, there);
1048             if (candidate >= start && candidate <= end) {
1049                 h = getPrimaryHorizontal(candidate);
1050 
1051                 if (h > horiz && h < besth) {
1052                     best = candidate;
1053                     besth = h;
1054                 }
1055             }
1056 
1057             here = there;
1058         }
1059 
1060         float h = getPrimaryHorizontal(end);
1061 
1062         if (h > horiz && h < besth) {
1063             best = end;
1064             besth = h;
1065         }
1066 
1067         if (best != offset)
1068             return best;
1069 
1070         int dir = getParagraphDirection(line);
1071 
1072         if (dir > 0) {
1073             if (line == getLineCount() - 1)
1074                 return best;
1075             else
1076                 return getOffsetForHorizontal(line + 1, -10000);
1077         } else {
1078             if (line == 0)
1079                 return best;
1080             else
1081                 return getOffsetForHorizontal(line - 1, -10000);
1082         }
1083     }
1084 
getOffsetAtStartOf(int offset)1085     private int getOffsetAtStartOf(int offset) {
1086         if (offset == 0)
1087             return 0;
1088 
1089         CharSequence text = mText;
1090         char c = text.charAt(offset);
1091 
1092         if (c >= '\uDC00' && c <= '\uDFFF') {
1093             char c1 = text.charAt(offset - 1);
1094 
1095             if (c1 >= '\uD800' && c1 <= '\uDBFF')
1096                 offset -= 1;
1097         }
1098 
1099         if (mSpannedText) {
1100             ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset,
1101                                                        ReplacementSpan.class);
1102 
1103             for (int i = 0; i < spans.length; i++) {
1104                 int start = ((Spanned) text).getSpanStart(spans[i]);
1105                 int end = ((Spanned) text).getSpanEnd(spans[i]);
1106 
1107                 if (start < offset && end > offset)
1108                     offset = start;
1109             }
1110         }
1111 
1112         return offset;
1113     }
1114 
1115     /**
1116      * Fills in the specified Path with a representation of a cursor
1117      * at the specified offset.  This will often be a vertical line
1118      * but can be multiple discontinous lines in text with multiple
1119      * directionalities.
1120      */
getCursorPath(int point, Path dest, CharSequence editingBuffer)1121     public void getCursorPath(int point, Path dest,
1122                               CharSequence editingBuffer) {
1123         dest.reset();
1124 
1125         int line = getLineForOffset(point);
1126         int top = getLineTop(line);
1127         int bottom = getLineTop(line+1);
1128 
1129         float h1 = getPrimaryHorizontal(point) - 0.5f;
1130         float h2 = getSecondaryHorizontal(point) - 0.5f;
1131 
1132         int caps = TextKeyListener.getMetaState(editingBuffer,
1133                                                 KeyEvent.META_SHIFT_ON) |
1134                    TextKeyListener.getMetaState(editingBuffer,
1135                                                 TextKeyListener.META_SELECTING);
1136         int fn = TextKeyListener.getMetaState(editingBuffer,
1137                                               KeyEvent.META_ALT_ON);
1138         int dist = 0;
1139 
1140         if (caps != 0 || fn != 0) {
1141             dist = (bottom - top) >> 2;
1142 
1143             if (fn != 0)
1144                 top += dist;
1145             if (caps != 0)
1146                 bottom -= dist;
1147         }
1148 
1149         if (h1 < 0.5f)
1150             h1 = 0.5f;
1151         if (h2 < 0.5f)
1152             h2 = 0.5f;
1153 
1154         if (h1 == h2) {
1155             dest.moveTo(h1, top);
1156             dest.lineTo(h1, bottom);
1157         } else {
1158             dest.moveTo(h1, top);
1159             dest.lineTo(h1, (top + bottom) >> 1);
1160 
1161             dest.moveTo(h2, (top + bottom) >> 1);
1162             dest.lineTo(h2, bottom);
1163         }
1164 
1165         if (caps == 2) {
1166             dest.moveTo(h2, bottom);
1167             dest.lineTo(h2 - dist, bottom + dist);
1168             dest.lineTo(h2, bottom);
1169             dest.lineTo(h2 + dist, bottom + dist);
1170         } else if (caps == 1) {
1171             dest.moveTo(h2, bottom);
1172             dest.lineTo(h2 - dist, bottom + dist);
1173 
1174             dest.moveTo(h2 - dist, bottom + dist - 0.5f);
1175             dest.lineTo(h2 + dist, bottom + dist - 0.5f);
1176 
1177             dest.moveTo(h2 + dist, bottom + dist);
1178             dest.lineTo(h2, bottom);
1179         }
1180 
1181         if (fn == 2) {
1182             dest.moveTo(h1, top);
1183             dest.lineTo(h1 - dist, top - dist);
1184             dest.lineTo(h1, top);
1185             dest.lineTo(h1 + dist, top - dist);
1186         } else if (fn == 1) {
1187             dest.moveTo(h1, top);
1188             dest.lineTo(h1 - dist, top - dist);
1189 
1190             dest.moveTo(h1 - dist, top - dist + 0.5f);
1191             dest.lineTo(h1 + dist, top - dist + 0.5f);
1192 
1193             dest.moveTo(h1 + dist, top - dist);
1194             dest.lineTo(h1, top);
1195         }
1196     }
1197 
addSelection(int line, int start, int end, int top, int bottom, Path dest)1198     private void addSelection(int line, int start, int end,
1199                               int top, int bottom, Path dest) {
1200         int linestart = getLineStart(line);
1201         int lineend = getLineEnd(line);
1202         Directions dirs = getLineDirections(line);
1203 
1204         if (lineend > linestart && mText.charAt(lineend - 1) == '\n')
1205             lineend--;
1206 
1207         int here = linestart;
1208         for (int i = 0; i < dirs.mDirections.length; i++) {
1209             int there = here + dirs.mDirections[i];
1210             if (there > lineend)
1211                 there = lineend;
1212 
1213             if (start <= there && end >= here) {
1214                 int st = Math.max(start, here);
1215                 int en = Math.min(end, there);
1216 
1217                 if (st != en) {
1218                     float h1 = getHorizontal(st, false, false, line);
1219                     float h2 = getHorizontal(en, true, false, line);
1220 
1221                     dest.addRect(h1, top, h2, bottom, Path.Direction.CW);
1222                 }
1223             }
1224 
1225             here = there;
1226         }
1227     }
1228 
1229     /**
1230      * Fills in the specified Path with a representation of a highlight
1231      * between the specified offsets.  This will often be a rectangle
1232      * or a potentially discontinuous set of rectangles.  If the start
1233      * and end are the same, the returned path is empty.
1234      */
getSelectionPath(int start, int end, Path dest)1235     public void getSelectionPath(int start, int end, Path dest) {
1236         dest.reset();
1237 
1238         if (start == end)
1239             return;
1240 
1241         if (end < start) {
1242             int temp = end;
1243             end = start;
1244             start = temp;
1245         }
1246 
1247         int startline = getLineForOffset(start);
1248         int endline = getLineForOffset(end);
1249 
1250         int top = getLineTop(startline);
1251         int bottom = getLineBottom(endline);
1252 
1253         if (startline == endline) {
1254             addSelection(startline, start, end, top, bottom, dest);
1255         } else {
1256             final float width = mWidth;
1257 
1258             addSelection(startline, start, getLineEnd(startline),
1259                          top, getLineBottom(startline), dest);
1260 
1261             if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT)
1262                 dest.addRect(getLineLeft(startline), top,
1263                               0, getLineBottom(startline), Path.Direction.CW);
1264             else
1265                 dest.addRect(getLineRight(startline), top,
1266                               width, getLineBottom(startline), Path.Direction.CW);
1267 
1268             for (int i = startline + 1; i < endline; i++) {
1269                 top = getLineTop(i);
1270                 bottom = getLineBottom(i);
1271                 dest.addRect(0, top, width, bottom, Path.Direction.CW);
1272             }
1273 
1274             top = getLineTop(endline);
1275             bottom = getLineBottom(endline);
1276 
1277             addSelection(endline, getLineStart(endline), end,
1278                          top, bottom, dest);
1279 
1280             if (getParagraphDirection(endline) == DIR_RIGHT_TO_LEFT)
1281                 dest.addRect(width, top, getLineRight(endline), bottom, Path.Direction.CW);
1282             else
1283                 dest.addRect(0, top, getLineLeft(endline), bottom, Path.Direction.CW);
1284         }
1285     }
1286 
1287     /**
1288      * Get the alignment of the specified paragraph, taking into account
1289      * markup attached to it.
1290      */
getParagraphAlignment(int line)1291     public final Alignment getParagraphAlignment(int line) {
1292         Alignment align = mAlignment;
1293 
1294         if (mSpannedText) {
1295             Spanned sp = (Spanned) mText;
1296             AlignmentSpan[] spans = sp.getSpans(getLineStart(line),
1297                                                 getLineEnd(line),
1298                                                 AlignmentSpan.class);
1299 
1300             int spanLength = spans.length;
1301             if (spanLength > 0) {
1302                 align = spans[spanLength-1].getAlignment();
1303             }
1304         }
1305 
1306         return align;
1307     }
1308 
1309     /**
1310      * Get the left edge of the specified paragraph, inset by left margins.
1311      */
getParagraphLeft(int line)1312     public final int getParagraphLeft(int line) {
1313         int dir = getParagraphDirection(line);
1314 
1315         int left = 0;
1316 
1317         boolean par = false;
1318         int off = getLineStart(line);
1319         if (off == 0 || mText.charAt(off - 1) == '\n')
1320             par = true;
1321 
1322         if (dir == DIR_LEFT_TO_RIGHT) {
1323             if (mSpannedText) {
1324                 Spanned sp = (Spanned) mText;
1325                 LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line),
1326                                                         getLineEnd(line),
1327                                                         LeadingMarginSpan.class);
1328 
1329                 for (int i = 0; i < spans.length; i++) {
1330                     boolean margin = par;
1331                     LeadingMarginSpan span = spans[i];
1332                     if (span instanceof LeadingMarginSpan.LeadingMarginSpan2) {
1333                         int count = ((LeadingMarginSpan.LeadingMarginSpan2)span).getLeadingMarginLineCount();
1334                         margin = count >= line;
1335                     }
1336                     left += span.getLeadingMargin(margin);
1337                 }
1338             }
1339         }
1340 
1341         return left;
1342     }
1343 
1344     /**
1345      * Get the right edge of the specified paragraph, inset by right margins.
1346      */
getParagraphRight(int line)1347     public final int getParagraphRight(int line) {
1348         int dir = getParagraphDirection(line);
1349 
1350         int right = mWidth;
1351 
1352         boolean par = false;
1353         int off = getLineStart(line);
1354         if (off == 0 || mText.charAt(off - 1) == '\n')
1355             par = true;
1356 
1357 
1358         if (dir == DIR_RIGHT_TO_LEFT) {
1359             if (mSpannedText) {
1360                 Spanned sp = (Spanned) mText;
1361                 LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line),
1362                                                         getLineEnd(line),
1363                                                         LeadingMarginSpan.class);
1364 
1365                 for (int i = 0; i < spans.length; i++) {
1366                     right -= spans[i].getLeadingMargin(par);
1367                 }
1368             }
1369         }
1370 
1371         return right;
1372     }
1373 
drawText(Canvas canvas, CharSequence text, int start, int end, int dir, Directions directions, float x, int top, int y, int bottom, TextPaint paint, TextPaint workPaint, boolean hasTabs, Object[] parspans)1374     private void drawText(Canvas canvas,
1375                                  CharSequence text, int start, int end,
1376                                  int dir, Directions directions,
1377                                  float x, int top, int y, int bottom,
1378                                  TextPaint paint,
1379                                  TextPaint workPaint,
1380                                  boolean hasTabs, Object[] parspans) {
1381         char[] buf;
1382         if (!hasTabs) {
1383             if (directions == DIRS_ALL_LEFT_TO_RIGHT) {
1384                 if (DEBUG) {
1385                     Assert.assertTrue(DIR_LEFT_TO_RIGHT == dir);
1386                 }
1387                 Styled.drawText(canvas, text, start, end, dir, false, x, top, y, bottom, paint, workPaint, false);
1388                 return;
1389             }
1390             buf = null;
1391         } else {
1392             buf = TextUtils.obtain(end - start);
1393             TextUtils.getChars(text, start, end, buf, 0);
1394         }
1395 
1396         float h = 0;
1397 
1398         int here = 0;
1399         for (int i = 0; i < directions.mDirections.length; i++) {
1400             int there = here + directions.mDirections[i];
1401             if (there > end - start)
1402                 there = end - start;
1403 
1404             int segstart = here;
1405             for (int j = hasTabs ? here : there; j <= there; j++) {
1406                 if (j == there || buf[j] == '\t') {
1407                     h += Styled.drawText(canvas, text,
1408                                          start + segstart, start + j,
1409                                          dir, (i & 1) != 0, x + h,
1410                                          top, y, bottom, paint, workPaint,
1411                                          start + j != end);
1412 
1413                     if (j != there && buf[j] == '\t')
1414                         h = dir * nextTab(text, start, end, h * dir, parspans);
1415 
1416                     segstart = j + 1;
1417                 } else if (hasTabs && buf[j] >= 0xD800 && buf[j] <= 0xDFFF && j + 1 < there) {
1418                     int emoji = Character.codePointAt(buf, j);
1419 
1420                     if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
1421                         Bitmap bm = EMOJI_FACTORY.
1422                             getBitmapFromAndroidPua(emoji);
1423 
1424                         if (bm != null) {
1425                             h += Styled.drawText(canvas, text,
1426                                                  start + segstart, start + j,
1427                                                  dir, (i & 1) != 0, x + h,
1428                                                  top, y, bottom, paint, workPaint,
1429                                                  start + j != end);
1430 
1431                             if (mEmojiRect == null) {
1432                                 mEmojiRect = new RectF();
1433                             }
1434 
1435                             workPaint.set(paint);
1436                             Styled.measureText(paint, workPaint, text,
1437                                                start + j, start + j + 1,
1438                                                null);
1439 
1440                             float bitmapHeight = bm.getHeight();
1441                             float textHeight = -workPaint.ascent();
1442                             float scale = textHeight / bitmapHeight;
1443                             float width = bm.getWidth() * scale;
1444 
1445                             mEmojiRect.set(x + h, y - textHeight,
1446                                            x + h + width, y);
1447 
1448                             canvas.drawBitmap(bm, null, mEmojiRect, paint);
1449                             h += width;
1450 
1451                             j++;
1452                             segstart = j + 1;
1453                         }
1454                     }
1455                 }
1456             }
1457 
1458             here = there;
1459         }
1460 
1461         if (hasTabs)
1462             TextUtils.recycle(buf);
1463     }
1464 
measureText(TextPaint paint, TextPaint workPaint, CharSequence text, int start, int offset, int end, int dir, Directions directions, boolean trailing, boolean alt, boolean hasTabs, Object[] tabs)1465     private static float measureText(TextPaint paint,
1466                                      TextPaint workPaint,
1467                                      CharSequence text,
1468                                      int start, int offset, int end,
1469                                      int dir, Directions directions,
1470                                      boolean trailing, boolean alt,
1471                                      boolean hasTabs, Object[] tabs) {
1472         char[] buf = null;
1473 
1474         if (hasTabs) {
1475             buf = TextUtils.obtain(end - start);
1476             TextUtils.getChars(text, start, end, buf, 0);
1477         }
1478 
1479         float h = 0;
1480 
1481         if (alt) {
1482             if (dir == DIR_RIGHT_TO_LEFT)
1483                 trailing = !trailing;
1484         }
1485 
1486         int here = 0;
1487         for (int i = 0; i < directions.mDirections.length; i++) {
1488             if (alt)
1489                 trailing = !trailing;
1490 
1491             int there = here + directions.mDirections[i];
1492             if (there > end - start)
1493                 there = end - start;
1494 
1495             int segstart = here;
1496             for (int j = hasTabs ? here : there; j <= there; j++) {
1497                 int codept = 0;
1498                 Bitmap bm = null;
1499 
1500                 if (hasTabs && j < there) {
1501                     codept = buf[j];
1502                 }
1503 
1504                 if (codept >= 0xD800 && codept <= 0xDFFF && j + 1 < there) {
1505                     codept = Character.codePointAt(buf, j);
1506 
1507                     if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) {
1508                         bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
1509                     }
1510                 }
1511 
1512                 if (j == there || codept == '\t' || bm != null) {
1513                     float segw;
1514 
1515                     if (offset < start + j ||
1516                        (trailing && offset <= start + j)) {
1517                         if (dir == DIR_LEFT_TO_RIGHT && (i & 1) == 0) {
1518                             h += Styled.measureText(paint, workPaint, text,
1519                                                     start + segstart, offset,
1520                                                     null);
1521                             return h;
1522                         }
1523 
1524                         if (dir == DIR_RIGHT_TO_LEFT && (i & 1) != 0) {
1525                             h -= Styled.measureText(paint, workPaint, text,
1526                                                     start + segstart, offset,
1527                                                     null);
1528                             return h;
1529                         }
1530                     }
1531 
1532                     segw = Styled.measureText(paint, workPaint, text,
1533                                               start + segstart, start + j,
1534                                               null);
1535 
1536                     if (offset < start + j ||
1537                         (trailing && offset <= start + j)) {
1538                         if (dir == DIR_LEFT_TO_RIGHT) {
1539                             h += segw - Styled.measureText(paint, workPaint,
1540                                                            text,
1541                                                            start + segstart,
1542                                                            offset, null);
1543                             return h;
1544                         }
1545 
1546                         if (dir == DIR_RIGHT_TO_LEFT) {
1547                             h -= segw - Styled.measureText(paint, workPaint,
1548                                                            text,
1549                                                            start + segstart,
1550                                                            offset, null);
1551                             return h;
1552                         }
1553                     }
1554 
1555                     if (dir == DIR_RIGHT_TO_LEFT)
1556                         h -= segw;
1557                     else
1558                         h += segw;
1559 
1560                     if (j != there && buf[j] == '\t') {
1561                         if (offset == start + j)
1562                             return h;
1563 
1564                         h = dir * nextTab(text, start, end, h * dir, tabs);
1565                     }
1566 
1567                     if (bm != null) {
1568                         workPaint.set(paint);
1569                         Styled.measureText(paint, workPaint, text,
1570                                            j, j + 2, null);
1571 
1572                         float wid = (float) bm.getWidth() *
1573                                     -workPaint.ascent() / bm.getHeight();
1574 
1575                         if (dir == DIR_RIGHT_TO_LEFT) {
1576                             h -= wid;
1577                         } else {
1578                             h += wid;
1579                         }
1580 
1581                         j++;
1582                     }
1583 
1584                     segstart = j + 1;
1585                 }
1586             }
1587 
1588             here = there;
1589         }
1590 
1591         if (hasTabs)
1592             TextUtils.recycle(buf);
1593 
1594         return h;
1595     }
1596 
1597     /**
1598      * Measure width of a run of text on a single line that is known to all be
1599      * in the same direction as the paragraph base direction. Returns the width,
1600      * and the line metrics in fm if fm is not null.
1601      *
1602      * @param paint the paint for the text; will not be modified
1603      * @param workPaint paint available for modification
1604      * @param text text
1605      * @param start start of the line
1606      * @param end limit of the line
1607      * @param fm object to return integer metrics in, can be null
1608      * @param hasTabs true if it is known that the line has tabs
1609      * @param tabs tab position information
1610      * @return the width of the text from start to end
1611      */
measureText(TextPaint paint, TextPaint workPaint, CharSequence text, int start, int end, Paint.FontMetricsInt fm, boolean hasTabs, Object[] tabs)1612     /* package */ static float measureText(TextPaint paint,
1613                                            TextPaint workPaint,
1614                                            CharSequence text,
1615                                            int start, int end,
1616                                            Paint.FontMetricsInt fm,
1617                                            boolean hasTabs, Object[] tabs) {
1618         char[] buf = null;
1619 
1620         if (hasTabs) {
1621             buf = TextUtils.obtain(end - start);
1622             TextUtils.getChars(text, start, end, buf, 0);
1623         }
1624 
1625         int len = end - start;
1626 
1627         int lastPos = 0;
1628         float width = 0;
1629         int ascent = 0, descent = 0, top = 0, bottom = 0;
1630 
1631         if (fm != null) {
1632             fm.ascent = 0;
1633             fm.descent = 0;
1634         }
1635 
1636         for (int pos = hasTabs ? 0 : len; pos <= len; pos++) {
1637             int codept = 0;
1638             Bitmap bm = null;
1639 
1640             if (hasTabs && pos < len) {
1641                 codept = buf[pos];
1642             }
1643 
1644             if (codept >= 0xD800 && codept <= 0xDFFF && pos < len) {
1645                 codept = Character.codePointAt(buf, pos);
1646 
1647                 if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) {
1648                     bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
1649                 }
1650             }
1651 
1652             if (pos == len || codept == '\t' || bm != null) {
1653                 workPaint.baselineShift = 0;
1654 
1655                 width += Styled.measureText(paint, workPaint, text,
1656                                         start + lastPos, start + pos,
1657                                         fm);
1658 
1659                 if (fm != null) {
1660                     if (workPaint.baselineShift < 0) {
1661                         fm.ascent += workPaint.baselineShift;
1662                         fm.top += workPaint.baselineShift;
1663                     } else {
1664                         fm.descent += workPaint.baselineShift;
1665                         fm.bottom += workPaint.baselineShift;
1666                     }
1667                 }
1668 
1669                 if (pos != len) {
1670                     if (bm == null) {
1671                         // no emoji, must have hit a tab
1672                         width = nextTab(text, start, end, width, tabs);
1673                     } else {
1674                         // This sets up workPaint with the font on the emoji
1675                         // text, so that we can extract the ascent and scale.
1676 
1677                         // We can't use the result of the previous call to
1678                         // measureText because the emoji might have its own style.
1679                         // We have to initialize workPaint here because if the
1680                         // text is unstyled measureText might not use workPaint
1681                         // at all.
1682                         workPaint.set(paint);
1683                         Styled.measureText(paint, workPaint, text,
1684                                            start + pos, start + pos + 1, null);
1685 
1686                         width += (float) bm.getWidth() *
1687                                     -workPaint.ascent() / bm.getHeight();
1688 
1689                         // Since we had an emoji, we bump past the second half
1690                         // of the surrogate pair.
1691                         pos++;
1692                     }
1693                 }
1694 
1695                 if (fm != null) {
1696                     if (fm.ascent < ascent) {
1697                         ascent = fm.ascent;
1698                     }
1699                     if (fm.descent > descent) {
1700                         descent = fm.descent;
1701                     }
1702 
1703                     if (fm.top < top) {
1704                         top = fm.top;
1705                     }
1706                     if (fm.bottom > bottom) {
1707                         bottom = fm.bottom;
1708                     }
1709 
1710                     // No need to take bitmap height into account here,
1711                     // since it is scaled to match the text height.
1712                 }
1713 
1714                 lastPos = pos + 1;
1715             }
1716         }
1717 
1718         if (fm != null) {
1719             fm.ascent = ascent;
1720             fm.descent = descent;
1721             fm.top = top;
1722             fm.bottom = bottom;
1723         }
1724 
1725         if (hasTabs)
1726             TextUtils.recycle(buf);
1727 
1728         return width;
1729     }
1730 
1731     /**
1732      * Returns the position of the next tab stop after h on the line.
1733      *
1734      * @param text the text
1735      * @param start start of the line
1736      * @param end limit of the line
1737      * @param h the current horizontal offset
1738      * @param tabs the tabs, can be null.  If it is null, any tabs in effect
1739      * on the line will be used.  If there are no tabs, a default offset
1740      * will be used to compute the tab stop.
1741      * @return the offset of the next tab stop.
1742      */
nextTab(CharSequence text, int start, int end, float h, Object[] tabs)1743     /* package */ static float nextTab(CharSequence text, int start, int end,
1744                                        float h, Object[] tabs) {
1745         float nh = Float.MAX_VALUE;
1746         boolean alltabs = false;
1747 
1748         if (text instanceof Spanned) {
1749             if (tabs == null) {
1750                 tabs = ((Spanned) text).getSpans(start, end, TabStopSpan.class);
1751                 alltabs = true;
1752             }
1753 
1754             for (int i = 0; i < tabs.length; i++) {
1755                 if (!alltabs) {
1756                     if (!(tabs[i] instanceof TabStopSpan))
1757                         continue;
1758                 }
1759 
1760                 int where = ((TabStopSpan) tabs[i]).getTabStop();
1761 
1762                 if (where < nh && where > h)
1763                     nh = where;
1764             }
1765 
1766             if (nh != Float.MAX_VALUE)
1767                 return nh;
1768         }
1769 
1770         return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT;
1771     }
1772 
isSpanned()1773     protected final boolean isSpanned() {
1774         return mSpannedText;
1775     }
1776 
ellipsize(int start, int end, int line, char[] dest, int destoff)1777     private void ellipsize(int start, int end, int line,
1778                            char[] dest, int destoff) {
1779         int ellipsisCount = getEllipsisCount(line);
1780 
1781         if (ellipsisCount == 0) {
1782             return;
1783         }
1784 
1785         int ellipsisStart = getEllipsisStart(line);
1786         int linestart = getLineStart(line);
1787 
1788         for (int i = ellipsisStart; i < ellipsisStart + ellipsisCount; i++) {
1789             char c;
1790 
1791             if (i == ellipsisStart) {
1792                 c = '\u2026'; // ellipsis
1793             } else {
1794                 c = '\uFEFF'; // 0-width space
1795             }
1796 
1797             int a = i + linestart;
1798 
1799             if (a >= start && a < end) {
1800                 dest[destoff + a - start] = c;
1801             }
1802         }
1803     }
1804 
1805     /**
1806      * Stores information about bidirectional (left-to-right or right-to-left)
1807      * text within the layout of a line.  TODO: This work is not complete
1808      * or correct and will be fleshed out in a later revision.
1809      */
1810     public static class Directions {
1811         private short[] mDirections;
1812 
1813         // The values in mDirections are the offsets from the first character
1814         // in the line to the next flip in direction.  Runs at even indices
1815         // are left-to-right, the others are right-to-left.  So, for example,
1816         // a line that starts with a right-to-left run has 0 at mDirections[0],
1817         // since the 'first' (ltr) run is zero length.
1818         //
1819         // The code currently assumes that each run is adjacent to the previous
1820         // one, progressing in the base line direction.  This isn't sufficient
1821         // to handle nested runs, for example numeric text in an rtl context
1822         // in an ltr paragraph.
Directions(short[] dirs)1823         /* package */ Directions(short[] dirs) {
1824             mDirections = dirs;
1825         }
1826     }
1827 
1828     /**
1829      * Return the offset of the first character to be ellipsized away,
1830      * relative to the start of the line.  (So 0 if the beginning of the
1831      * line is ellipsized, not getLineStart().)
1832      */
getEllipsisStart(int line)1833     public abstract int getEllipsisStart(int line);
1834     /**
1835      * Returns the number of characters to be ellipsized away, or 0 if
1836      * no ellipsis is to take place.
1837      */
getEllipsisCount(int line)1838     public abstract int getEllipsisCount(int line);
1839 
1840     /* package */ static class Ellipsizer implements CharSequence, GetChars {
1841         /* package */ CharSequence mText;
1842         /* package */ Layout mLayout;
1843         /* package */ int mWidth;
1844         /* package */ TextUtils.TruncateAt mMethod;
1845 
Ellipsizer(CharSequence s)1846         public Ellipsizer(CharSequence s) {
1847             mText = s;
1848         }
1849 
charAt(int off)1850         public char charAt(int off) {
1851             char[] buf = TextUtils.obtain(1);
1852             getChars(off, off + 1, buf, 0);
1853             char ret = buf[0];
1854 
1855             TextUtils.recycle(buf);
1856             return ret;
1857         }
1858 
getChars(int start, int end, char[] dest, int destoff)1859         public void getChars(int start, int end, char[] dest, int destoff) {
1860             int line1 = mLayout.getLineForOffset(start);
1861             int line2 = mLayout.getLineForOffset(end);
1862 
1863             TextUtils.getChars(mText, start, end, dest, destoff);
1864 
1865             for (int i = line1; i <= line2; i++) {
1866                 mLayout.ellipsize(start, end, i, dest, destoff);
1867             }
1868         }
1869 
length()1870         public int length() {
1871             return mText.length();
1872         }
1873 
subSequence(int start, int end)1874         public CharSequence subSequence(int start, int end) {
1875             char[] s = new char[end - start];
1876             getChars(start, end, s, 0);
1877             return new String(s);
1878         }
1879 
toString()1880         public String toString() {
1881             char[] s = new char[length()];
1882             getChars(0, length(), s, 0);
1883             return new String(s);
1884         }
1885 
1886     }
1887 
1888     /* package */ static class SpannedEllipsizer
1889                     extends Ellipsizer implements Spanned {
1890         private Spanned mSpanned;
1891 
SpannedEllipsizer(CharSequence display)1892         public SpannedEllipsizer(CharSequence display) {
1893             super(display);
1894             mSpanned = (Spanned) display;
1895         }
1896 
getSpans(int start, int end, Class<T> type)1897         public <T> T[] getSpans(int start, int end, Class<T> type) {
1898             return mSpanned.getSpans(start, end, type);
1899         }
1900 
getSpanStart(Object tag)1901         public int getSpanStart(Object tag) {
1902             return mSpanned.getSpanStart(tag);
1903         }
1904 
getSpanEnd(Object tag)1905         public int getSpanEnd(Object tag) {
1906             return mSpanned.getSpanEnd(tag);
1907         }
1908 
getSpanFlags(Object tag)1909         public int getSpanFlags(Object tag) {
1910             return mSpanned.getSpanFlags(tag);
1911         }
1912 
nextSpanTransition(int start, int limit, Class type)1913         public int nextSpanTransition(int start, int limit, Class type) {
1914             return mSpanned.nextSpanTransition(start, limit, type);
1915         }
1916 
subSequence(int start, int end)1917         public CharSequence subSequence(int start, int end) {
1918             char[] s = new char[end - start];
1919             getChars(start, end, s, 0);
1920 
1921             SpannableString ss = new SpannableString(new String(s));
1922             TextUtils.copySpansFrom(mSpanned, start, end, Object.class, ss, 0);
1923             return ss;
1924         }
1925     }
1926 
1927     private CharSequence mText;
1928     private TextPaint mPaint;
1929     /* package */ TextPaint mWorkPaint;
1930     private int mWidth;
1931     private Alignment mAlignment = Alignment.ALIGN_NORMAL;
1932     private float mSpacingMult;
1933     private float mSpacingAdd;
1934     private static Rect sTempRect = new Rect();
1935     private boolean mSpannedText;
1936 
1937     public static final int DIR_LEFT_TO_RIGHT = 1;
1938     public static final int DIR_RIGHT_TO_LEFT = -1;
1939 
1940     /* package */ static final int DIR_REQUEST_LTR = 1;
1941     /* package */ static final int DIR_REQUEST_RTL = -1;
1942     /* package */ static final int DIR_REQUEST_DEFAULT_LTR = 2;
1943     /* package */ static final int DIR_REQUEST_DEFAULT_RTL = -2;
1944 
1945     public enum Alignment {
1946         ALIGN_NORMAL,
1947         ALIGN_OPPOSITE,
1948         ALIGN_CENTER,
1949         // XXX ALIGN_LEFT,
1950         // XXX ALIGN_RIGHT,
1951     }
1952 
1953     private static final int TAB_INCREMENT = 20;
1954 
1955     /* package */ static final Directions DIRS_ALL_LEFT_TO_RIGHT =
1956                                        new Directions(new short[] { 32767 });
1957     /* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT =
1958                                        new Directions(new short[] { 0, 32767 });
1959 
1960 }
1961 
1962