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