blob: bdfe94048e5e9183bdc05cc74106767dd526e8c6 [file] [log] [blame]
Doug Feltc0ccf0c2011-06-23 16:13:18 -07001 /*
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002 * 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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.graphics.Canvas;
21import android.graphics.Paint;
Doug Felt9f7a4442010-03-01 12:45:56 -080022import android.graphics.Path;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.graphics.Rect;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.text.method.TextKeyListener;
Doug Felt9f7a4442010-03-01 12:45:56 -080025import android.text.style.AlignmentSpan;
26import android.text.style.LeadingMarginSpan;
Gilles Debunne162bf0f2010-11-16 16:23:53 -080027import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
Doug Felt9f7a4442010-03-01 12:45:56 -080028import android.text.style.LineBackgroundSpan;
29import android.text.style.ParagraphStyle;
30import android.text.style.ReplacementSpan;
31import android.text.style.TabStopSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032
Doug Feltcb3791202011-07-07 11:57:48 -070033import com.android.internal.util.ArrayUtils;
34
Doug Feltc982f602010-05-25 11:51:40 -070035import java.util.Arrays;
Doug Felt9f7a4442010-03-01 12:45:56 -080036
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037/**
Doug Felt9f7a4442010-03-01 12:45:56 -080038 * A base class that manages text layout in visual elements on
39 * the screen.
40 * <p>For text that will be edited, use a {@link DynamicLayout},
41 * which will be updated as the text changes.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042 * For text that will not change, use a {@link StaticLayout}.
43 */
44public abstract class Layout {
Doug Felt71b8dd72010-02-16 17:27:09 -080045 private static final ParagraphStyle[] NO_PARA_SPANS =
46 ArrayUtils.emptyArray(ParagraphStyle.class);
Dave Bort76c02262009-04-13 17:17:21 -070047
The Android Open Source Project10592532009-03-18 17:39:46 -070048 /* package */ static final EmojiFactory EMOJI_FACTORY =
49 EmojiFactory.newAvailableInstance();
50 /* package */ static final int MIN_EMOJI, MAX_EMOJI;
51
52 static {
53 if (EMOJI_FACTORY != null) {
54 MIN_EMOJI = EMOJI_FACTORY.getMinimumAndroidPua();
55 MAX_EMOJI = EMOJI_FACTORY.getMaximumAndroidPua();
56 } else {
57 MIN_EMOJI = -1;
58 MAX_EMOJI = -1;
59 }
Doug Felte8e45f22010-03-29 14:58:40 -070060 }
Eric Fischerc2d54f42009-03-27 15:52:38 -070061
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062 /**
Doug Felt71b8dd72010-02-16 17:27:09 -080063 * Return how wide a layout must be in order to display the
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064 * specified text with one line per paragraph.
65 */
66 public static float getDesiredWidth(CharSequence source,
67 TextPaint paint) {
68 return getDesiredWidth(source, 0, source.length(), paint);
69 }
Doug Felt9f7a4442010-03-01 12:45:56 -080070
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071 /**
Doug Felt71b8dd72010-02-16 17:27:09 -080072 * Return how wide a layout must be in order to display the
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073 * specified text slice with one line per paragraph.
74 */
75 public static float getDesiredWidth(CharSequence source,
76 int start, int end,
77 TextPaint paint) {
78 float need = 0;
79 TextPaint workPaint = new TextPaint();
80
81 int next;
82 for (int i = start; i <= end; i = next) {
83 next = TextUtils.indexOf(source, '\n', i, end);
84
85 if (next < 0)
86 next = end;
87
Doug Felt71b8dd72010-02-16 17:27:09 -080088 // note, omits trailing paragraph char
Doug Feltc982f602010-05-25 11:51:40 -070089 float w = measurePara(paint, workPaint, source, i, next);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090
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.
Doug Felt71b8dd72010-02-16 17:27:09 -0800103 * @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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112 */
113 protected Layout(CharSequence text, TextPaint paint,
114 int width, Alignment align,
Doug Felt71b8dd72010-02-16 17:27:09 -0800115 float spacingMult, float spacingAdd) {
Doug Feltcb3791202011-07-07 11:57:48 -0700116 this(text, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
117 spacingMult, spacingAdd);
118 }
119
120 /**
121 * Subclasses of Layout use this constructor to set the display text,
122 * width, and other standard properties.
123 * @param text the text to render
124 * @param paint the default paint for the layout. Styles can override
125 * various attributes of the paint.
126 * @param width the wrapping width for the text.
127 * @param align whether to left, right, or center the text. Styles can
128 * override the alignment.
129 * @param spacingMult factor by which to scale the font size to get the
130 * default line spacing
131 * @param spacingAdd amount to add to the default line spacing
132 *
133 * @hide
134 */
135 protected Layout(CharSequence text, TextPaint paint,
136 int width, Alignment align, TextDirectionHeuristic textDir,
137 float spacingMult, float spacingAdd) {
138
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139 if (width < 0)
140 throw new IllegalArgumentException("Layout: " + width + " < 0");
141
Doug Felte8e45f22010-03-29 14:58:40 -0700142 // Ensure paint doesn't have baselineShift set.
143 // While normally we don't modify the paint the user passed in,
144 // we were already doing this in Styled.drawUniformRun with both
145 // baselineShift and bgColor. We probably should reevaluate bgColor.
146 if (paint != null) {
147 paint.bgColor = 0;
148 paint.baselineShift = 0;
149 }
150
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800151 mText = text;
152 mPaint = paint;
153 mWorkPaint = new TextPaint();
154 mWidth = width;
155 mAlignment = align;
Doug Felt71b8dd72010-02-16 17:27:09 -0800156 mSpacingMult = spacingMult;
157 mSpacingAdd = spacingAdd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800158 mSpannedText = text instanceof Spanned;
Doug Feltcb3791202011-07-07 11:57:48 -0700159 mTextDir = textDir;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800160 }
161
162 /**
163 * Replace constructor properties of this Layout with new ones. Be careful.
164 */
165 /* package */ void replaceWith(CharSequence text, TextPaint paint,
166 int width, Alignment align,
167 float spacingmult, float spacingadd) {
168 if (width < 0) {
169 throw new IllegalArgumentException("Layout: " + width + " < 0");
170 }
171
172 mText = text;
173 mPaint = paint;
174 mWidth = width;
175 mAlignment = align;
176 mSpacingMult = spacingmult;
177 mSpacingAdd = spacingadd;
178 mSpannedText = text instanceof Spanned;
179 }
180
181 /**
182 * Draw this Layout on the specified Canvas.
183 */
184 public void draw(Canvas c) {
185 draw(c, null, null, 0);
186 }
187
188 /**
Doug Felt71b8dd72010-02-16 17:27:09 -0800189 * Draw this Layout on the specified canvas, with the highlight path drawn
190 * between the background and the text.
191 *
192 * @param c the canvas
193 * @param highlight the path of the highlight or cursor; can be null
194 * @param highlightPaint the paint for the highlight
195 * @param cursorOffsetVertical the amount to temporarily translate the
196 * canvas while rendering the highlight
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800197 */
Doug Felt71b8dd72010-02-16 17:27:09 -0800198 public void draw(Canvas c, Path highlight, Paint highlightPaint,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800199 int cursorOffsetVertical) {
200 int dtop, dbottom;
201
202 synchronized (sTempRect) {
203 if (!c.getClipBounds(sTempRect)) {
204 return;
205 }
206
207 dtop = sTempRect.top;
208 dbottom = sTempRect.bottom;
209 }
210
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800211 int top = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800212 int bottom = getLineTop(getLineCount());
213
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800214 if (dtop > top) {
215 top = dtop;
216 }
217 if (dbottom < bottom) {
218 bottom = dbottom;
219 }
Doug Felt9f7a4442010-03-01 12:45:56 -0800220
221 int first = getLineForVertical(top);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800222 int last = getLineForVertical(bottom);
Doug Felt9f7a4442010-03-01 12:45:56 -0800223
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800224 int previousLineBottom = getLineTop(first);
225 int previousLineEnd = getLineStart(first);
Doug Felt9f7a4442010-03-01 12:45:56 -0800226
Doug Felt71b8dd72010-02-16 17:27:09 -0800227 TextPaint paint = mPaint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800228 CharSequence buf = mText;
Doug Felt71b8dd72010-02-16 17:27:09 -0800229 int width = mWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230 boolean spannedText = mSpannedText;
231
Doug Felt71b8dd72010-02-16 17:27:09 -0800232 ParagraphStyle[] spans = NO_PARA_SPANS;
Doug Feltc982f602010-05-25 11:51:40 -0700233 int spanEnd = 0;
Doug Felt71b8dd72010-02-16 17:27:09 -0800234 int textLength = 0;
235
236 // First, draw LineBackgroundSpans.
Doug Felt0c702b82010-05-14 10:55:42 -0700237 // LineBackgroundSpans know nothing about the alignment, margins, or
Doug Feltc982f602010-05-25 11:51:40 -0700238 // direction of the layout or line. XXX: Should they?
239 // They are evaluated at each line.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800240 if (spannedText) {
Doug Feltc982f602010-05-25 11:51:40 -0700241 Spanned sp = (Spanned) buf;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800242 textLength = buf.length();
243 for (int i = first; i <= last; i++) {
244 int start = previousLineEnd;
245 int end = getLineStart(i+1);
246 previousLineEnd = end;
247
248 int ltop = previousLineBottom;
249 int lbottom = getLineTop(i+1);
250 previousLineBottom = lbottom;
251 int lbaseline = lbottom - getLineDescent(i);
252
Doug Feltc982f602010-05-25 11:51:40 -0700253 if (start >= spanEnd) {
254 // These should be infrequent, so we'll use this so that
255 // we don't have to check as often.
Doug Felt0c702b82010-05-14 10:55:42 -0700256 spanEnd = sp.nextSpanTransition(start, textLength,
Doug Feltc982f602010-05-25 11:51:40 -0700257 LineBackgroundSpan.class);
258 // All LineBackgroundSpans on a line contribute to its
259 // background.
Eric Fischer74d31ef2010-08-05 15:29:36 -0700260 spans = getParagraphSpans(sp, start, end, LineBackgroundSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800261 }
262
263 for (int n = 0; n < spans.length; n++) {
264 LineBackgroundSpan back = (LineBackgroundSpan) spans[n];
265
Doug Felt71b8dd72010-02-16 17:27:09 -0800266 back.drawBackground(c, paint, 0, width,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800267 ltop, lbaseline, lbottom,
268 buf, start, end,
269 i);
270 }
271 }
272 // reset to their original values
Doug Feltc982f602010-05-25 11:51:40 -0700273 spanEnd = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800274 previousLineBottom = getLineTop(first);
275 previousLineEnd = getLineStart(first);
Doug Felt71b8dd72010-02-16 17:27:09 -0800276 spans = NO_PARA_SPANS;
Doug Felt9f7a4442010-03-01 12:45:56 -0800277 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800278
279 // There can be a highlight even without spans if we are drawing
280 // a non-spanned transformation of a spanned editing buffer.
281 if (highlight != null) {
282 if (cursorOffsetVertical != 0) {
283 c.translate(0, cursorOffsetVertical);
284 }
285
Doug Felt71b8dd72010-02-16 17:27:09 -0800286 c.drawPath(highlight, highlightPaint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800287
288 if (cursorOffsetVertical != 0) {
289 c.translate(0, -cursorOffsetVertical);
290 }
291 }
292
Doug Feltc0ccf0c2011-06-23 16:13:18 -0700293 Alignment paraAlign = mAlignment;
Doug Feltc982f602010-05-25 11:51:40 -0700294 TabStops tabStops = null;
295 boolean tabStopsIsInitialized = false;
Doug Felt9f7a4442010-03-01 12:45:56 -0800296
Doug Felte8e45f22010-03-29 14:58:40 -0700297 TextLine tl = TextLine.obtain();
Doug Feltc982f602010-05-25 11:51:40 -0700298
Doug Felt71b8dd72010-02-16 17:27:09 -0800299 // Next draw the lines, one at a time.
300 // the baseline is the top of the following line minus the current
301 // line's descent.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800302 for (int i = first; i <= last; i++) {
303 int start = previousLineEnd;
304
305 previousLineEnd = getLineStart(i+1);
306 int end = getLineVisibleEnd(i, start, previousLineEnd);
307
308 int ltop = previousLineBottom;
309 int lbottom = getLineTop(i+1);
310 previousLineBottom = lbottom;
311 int lbaseline = lbottom - getLineDescent(i);
312
Doug Feltc982f602010-05-25 11:51:40 -0700313 int dir = getParagraphDirection(i);
314 int left = 0;
315 int right = mWidth;
316
Doug Felt9f7a4442010-03-01 12:45:56 -0800317 if (spannedText) {
Doug Feltc982f602010-05-25 11:51:40 -0700318 Spanned sp = (Spanned) buf;
319 boolean isFirstParaLine = (start == 0 ||
320 buf.charAt(start - 1) == '\n');
Doug Felt0c702b82010-05-14 10:55:42 -0700321
Doug Feltc982f602010-05-25 11:51:40 -0700322 // New batch of paragraph styles, collect into spans array.
323 // Compute the alignment, last alignment style wins.
324 // Reset tabStops, we'll rebuild if we encounter a line with
325 // tabs.
326 // We expect paragraph spans to be relatively infrequent, use
327 // spanEnd so that we can check less frequently. Since
328 // paragraph styles ought to apply to entire paragraphs, we can
329 // just collect the ones present at the start of the paragraph.
330 // If spanEnd is before the end of the paragraph, that's not
331 // our problem.
332 if (start >= spanEnd && (i == first || isFirstParaLine)) {
333 spanEnd = sp.nextSpanTransition(start, textLength,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800334 ParagraphStyle.class);
Eric Fischer74d31ef2010-08-05 15:29:36 -0700335 spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class);
Doug Felt9f7a4442010-03-01 12:45:56 -0800336
Doug Feltc0ccf0c2011-06-23 16:13:18 -0700337 paraAlign = mAlignment;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800338 for (int n = spans.length-1; n >= 0; n--) {
339 if (spans[n] instanceof AlignmentSpan) {
Doug Feltc0ccf0c2011-06-23 16:13:18 -0700340 paraAlign = ((AlignmentSpan) spans[n]).getAlignment();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800341 break;
342 }
343 }
Doug Felt0c702b82010-05-14 10:55:42 -0700344
Doug Feltc982f602010-05-25 11:51:40 -0700345 tabStopsIsInitialized = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800346 }
Doug Felt9f7a4442010-03-01 12:45:56 -0800347
Doug Feltc982f602010-05-25 11:51:40 -0700348 // Draw all leading margin spans. Adjust left or right according
349 // to the paragraph direction of the line.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800350 final int length = spans.length;
351 for (int n = 0; n < length; n++) {
352 if (spans[n] instanceof LeadingMarginSpan) {
353 LeadingMarginSpan margin = (LeadingMarginSpan) spans[n];
Doug Feltc982f602010-05-25 11:51:40 -0700354 boolean useFirstLineMargin = isFirstParaLine;
355 if (margin instanceof LeadingMarginSpan2) {
356 int count = ((LeadingMarginSpan2) margin).getLeadingMarginLineCount();
357 int startLine = getLineForOffset(sp.getSpanStart(margin));
358 useFirstLineMargin = i < startLine + count;
359 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800360
361 if (dir == DIR_RIGHT_TO_LEFT) {
362 margin.drawLeadingMargin(c, paint, right, dir, ltop,
363 lbaseline, lbottom, buf,
Doug Felt71b8dd72010-02-16 17:27:09 -0800364 start, end, isFirstParaLine, this);
Doug Feltc982f602010-05-25 11:51:40 -0700365 right -= margin.getLeadingMargin(useFirstLineMargin);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800366 } else {
367 margin.drawLeadingMargin(c, paint, left, dir, ltop,
368 lbaseline, lbottom, buf,
Doug Felt71b8dd72010-02-16 17:27:09 -0800369 start, end, isFirstParaLine, this);
Doug Feltc982f602010-05-25 11:51:40 -0700370 left += margin.getLeadingMargin(useFirstLineMargin);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800371 }
372 }
373 }
374 }
375
Doug Feltc982f602010-05-25 11:51:40 -0700376 boolean hasTabOrEmoji = getLineContainsTab(i);
377 // Can't tell if we have tabs for sure, currently
378 if (hasTabOrEmoji && !tabStopsIsInitialized) {
379 if (tabStops == null) {
380 tabStops = new TabStops(TAB_INCREMENT, spans);
381 } else {
382 tabStops.reset(TAB_INCREMENT, spans);
383 }
384 tabStopsIsInitialized = true;
385 }
386
Doug Feltc0ccf0c2011-06-23 16:13:18 -0700387 // Determine whether the line aligns to normal, opposite, or center.
388 Alignment align = paraAlign;
389 if (align == Alignment.ALIGN_LEFT) {
390 align = (dir == DIR_LEFT_TO_RIGHT) ?
391 Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
392 } else if (align == Alignment.ALIGN_RIGHT) {
393 align = (dir == DIR_LEFT_TO_RIGHT) ?
394 Alignment.ALIGN_OPPOSITE : Alignment.ALIGN_NORMAL;
395 }
396
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800397 int x;
398 if (align == Alignment.ALIGN_NORMAL) {
399 if (dir == DIR_LEFT_TO_RIGHT) {
400 x = left;
401 } else {
402 x = right;
403 }
404 } else {
Doug Feltc982f602010-05-25 11:51:40 -0700405 int max = (int)getLineExtent(i, tabStops, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800406 if (align == Alignment.ALIGN_OPPOSITE) {
Doug Feltc982f602010-05-25 11:51:40 -0700407 if (dir == DIR_LEFT_TO_RIGHT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800408 x = right - max;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409 } else {
Doug Feltc982f602010-05-25 11:51:40 -0700410 x = left - max;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800411 }
Doug Feltc982f602010-05-25 11:51:40 -0700412 } else { // Alignment.ALIGN_CENTER
413 max = max & ~1;
414 x = (right + left - max) >> 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800415 }
416 }
417
418 Directions directions = getLineDirections(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419 if (directions == DIRS_ALL_LEFT_TO_RIGHT &&
Doug Feltc982f602010-05-25 11:51:40 -0700420 !spannedText && !hasTabOrEmoji) {
Doug Felt71b8dd72010-02-16 17:27:09 -0800421 // XXX: assumes there's nothing additional to be done
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800422 c.drawText(buf, start, end, x, lbaseline, paint);
423 } else {
Doug Feltc982f602010-05-25 11:51:40 -0700424 tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops);
Doug Felte8e45f22010-03-29 14:58:40 -0700425 tl.draw(c, x, ltop, lbaseline, lbottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800426 }
427 }
Doug Feltc982f602010-05-25 11:51:40 -0700428
Doug Felte8e45f22010-03-29 14:58:40 -0700429 TextLine.recycle(tl);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430 }
431
432 /**
Doug Feltc982f602010-05-25 11:51:40 -0700433 * Return the start position of the line, given the left and right bounds
434 * of the margins.
Doug Felt0c702b82010-05-14 10:55:42 -0700435 *
Doug Feltc982f602010-05-25 11:51:40 -0700436 * @param line the line index
437 * @param left the left bounds (0, or leading margin if ltr para)
438 * @param right the right bounds (width, minus leading margin if rtl para)
439 * @return the start position of the line (to right of line if rtl para)
440 */
441 private int getLineStartPos(int line, int left, int right) {
442 // Adjust the point at which to start rendering depending on the
443 // alignment of the paragraph.
444 Alignment align = getParagraphAlignment(line);
445 int dir = getParagraphDirection(line);
446
447 int x;
Doug Feltc0ccf0c2011-06-23 16:13:18 -0700448 if (align == Alignment.ALIGN_LEFT) {
449 x = left;
450 } else if (align == Alignment.ALIGN_NORMAL) {
Doug Feltc982f602010-05-25 11:51:40 -0700451 if (dir == DIR_LEFT_TO_RIGHT) {
452 x = left;
453 } else {
454 x = right;
455 }
456 } else {
457 TabStops tabStops = null;
458 if (mSpannedText && getLineContainsTab(line)) {
459 Spanned spanned = (Spanned) mText;
460 int start = getLineStart(line);
461 int spanEnd = spanned.nextSpanTransition(start, spanned.length(),
462 TabStopSpan.class);
Eric Fischer74d31ef2010-08-05 15:29:36 -0700463 TabStopSpan[] tabSpans = getParagraphSpans(spanned, start, spanEnd, TabStopSpan.class);
Doug Feltc982f602010-05-25 11:51:40 -0700464 if (tabSpans.length > 0) {
465 tabStops = new TabStops(TAB_INCREMENT, tabSpans);
466 }
467 }
468 int max = (int)getLineExtent(line, tabStops, false);
Doug Feltc0ccf0c2011-06-23 16:13:18 -0700469 if (align == Alignment.ALIGN_RIGHT) {
470 x = right - max;
471 } else if (align == Alignment.ALIGN_OPPOSITE) {
Doug Feltc982f602010-05-25 11:51:40 -0700472 if (dir == DIR_LEFT_TO_RIGHT) {
473 x = right - max;
474 } else {
475 x = left - max;
476 }
477 } else { // Alignment.ALIGN_CENTER
478 max = max & ~1;
479 x = (left + right - max) >> 1;
480 }
481 }
482 return x;
483 }
484
485 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800486 * Return the text that is displayed by this Layout.
487 */
488 public final CharSequence getText() {
489 return mText;
490 }
491
492 /**
493 * Return the base Paint properties for this layout.
494 * Do NOT change the paint, which may result in funny
495 * drawing for this layout.
496 */
497 public final TextPaint getPaint() {
498 return mPaint;
499 }
500
501 /**
502 * Return the width of this layout.
503 */
504 public final int getWidth() {
505 return mWidth;
506 }
507
508 /**
509 * Return the width to which this Layout is ellipsizing, or
510 * {@link #getWidth} if it is not doing anything special.
511 */
512 public int getEllipsizedWidth() {
513 return mWidth;
514 }
515
516 /**
517 * Increase the width of this layout to the specified width.
Doug Felt71b8dd72010-02-16 17:27:09 -0800518 * Be careful to use this only when you know it is appropriate&mdash;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800519 * it does not cause the text to reflow to use the full new width.
520 */
521 public final void increaseWidthTo(int wid) {
522 if (wid < mWidth) {
523 throw new RuntimeException("attempted to reduce Layout width");
524 }
525
526 mWidth = wid;
527 }
Doug Felt9f7a4442010-03-01 12:45:56 -0800528
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800529 /**
530 * Return the total height of this layout.
531 */
532 public int getHeight() {
Doug Felt71b8dd72010-02-16 17:27:09 -0800533 return getLineTop(getLineCount());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800534 }
535
536 /**
537 * Return the base alignment of this layout.
538 */
539 public final Alignment getAlignment() {
540 return mAlignment;
541 }
542
543 /**
544 * Return what the text height is multiplied by to get the line height.
545 */
546 public final float getSpacingMultiplier() {
547 return mSpacingMult;
548 }
549
550 /**
551 * Return the number of units of leading that are added to each line.
552 */
553 public final float getSpacingAdd() {
554 return mSpacingAdd;
555 }
556
557 /**
Doug Feltcb3791202011-07-07 11:57:48 -0700558 * Return the heuristic used to determine paragraph text direction.
559 * @hide
560 */
561 public final TextDirectionHeuristic getTextDirectionHeuristic() {
562 return mTextDir;
563 }
564
565 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800566 * Return the number of lines of text in this layout.
567 */
568 public abstract int getLineCount();
Doug Felt9f7a4442010-03-01 12:45:56 -0800569
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800570 /**
571 * Return the baseline for the specified line (0&hellip;getLineCount() - 1)
572 * If bounds is not null, return the top, left, right, bottom extents
573 * of the specified line in it.
574 * @param line which line to examine (0..getLineCount() - 1)
575 * @param bounds Optional. If not null, it returns the extent of the line
576 * @return the Y-coordinate of the baseline
577 */
578 public int getLineBounds(int line, Rect bounds) {
579 if (bounds != null) {
580 bounds.left = 0; // ???
581 bounds.top = getLineTop(line);
582 bounds.right = mWidth; // ???
Doug Felt71b8dd72010-02-16 17:27:09 -0800583 bounds.bottom = getLineTop(line + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800584 }
585 return getLineBaseline(line);
586 }
587
588 /**
Doug Felt71b8dd72010-02-16 17:27:09 -0800589 * Return the vertical position of the top of the specified line
590 * (0&hellip;getLineCount()).
591 * If the specified line is equal to the line count, returns the
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800592 * bottom of the last line.
593 */
594 public abstract int getLineTop(int line);
595
596 /**
Doug Felt71b8dd72010-02-16 17:27:09 -0800597 * Return the descent of the specified line(0&hellip;getLineCount() - 1).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800598 */
599 public abstract int getLineDescent(int line);
600
601 /**
Doug Felt71b8dd72010-02-16 17:27:09 -0800602 * Return the text offset of the beginning of the specified line (
603 * 0&hellip;getLineCount()). If the specified line is equal to the line
604 * count, returns the length of the text.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800605 */
606 public abstract int getLineStart(int line);
607
608 /**
Doug Felt71b8dd72010-02-16 17:27:09 -0800609 * Returns the primary directionality of the paragraph containing the
610 * specified line, either 1 for left-to-right lines, or -1 for right-to-left
611 * lines (see {@link #DIR_LEFT_TO_RIGHT}, {@link #DIR_RIGHT_TO_LEFT}).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800612 */
613 public abstract int getParagraphDirection(int line);
614
615 /**
The Android Open Source Project10592532009-03-18 17:39:46 -0700616 * Returns whether the specified line contains one or more
617 * characters that need to be handled specially, like tabs
618 * or emoji.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800619 */
620 public abstract boolean getLineContainsTab(int line);
621
622 /**
Doug Felt71b8dd72010-02-16 17:27:09 -0800623 * Returns the directional run information for the specified line.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800624 * The array alternates counts of characters in left-to-right
625 * and right-to-left segments of the line.
Doug Felt71b8dd72010-02-16 17:27:09 -0800626 *
627 * <p>NOTE: this is inadequate to support bidirectional text, and will change.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800628 */
629 public abstract Directions getLineDirections(int line);
630
631 /**
632 * Returns the (negative) number of extra pixels of ascent padding in the
633 * top line of the Layout.
634 */
635 public abstract int getTopPadding();
636
637 /**
638 * Returns the number of extra pixels of descent padding in the
639 * bottom line of the Layout.
640 */
641 public abstract int getBottomPadding();
642
Doug Felt4e0c5e52010-03-15 16:56:02 -0700643
644 /**
645 * Returns true if the character at offset and the preceding character
646 * are at different run levels (and thus there's a split caret).
647 * @param offset the offset
648 * @return true if at a level boundary
Gilles Debunnef75c97e2011-02-10 16:09:53 -0800649 * @hide
Doug Felt4e0c5e52010-03-15 16:56:02 -0700650 */
Gilles Debunnef75c97e2011-02-10 16:09:53 -0800651 public boolean isLevelBoundary(int offset) {
Doug Felt9f7a4442010-03-01 12:45:56 -0800652 int line = getLineForOffset(offset);
Doug Felt4e0c5e52010-03-15 16:56:02 -0700653 Directions dirs = getLineDirections(line);
654 if (dirs == DIRS_ALL_LEFT_TO_RIGHT || dirs == DIRS_ALL_RIGHT_TO_LEFT) {
655 return false;
656 }
657
658 int[] runs = dirs.mDirections;
Doug Felt9f7a4442010-03-01 12:45:56 -0800659 int lineStart = getLineStart(line);
Doug Felt4e0c5e52010-03-15 16:56:02 -0700660 int lineEnd = getLineEnd(line);
661 if (offset == lineStart || offset == lineEnd) {
662 int paraLevel = getParagraphDirection(line) == 1 ? 0 : 1;
663 int runIndex = offset == lineStart ? 0 : runs.length - 2;
664 return ((runs[runIndex + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK) != paraLevel;
665 }
666
667 offset -= lineStart;
Doug Felt9f7a4442010-03-01 12:45:56 -0800668 for (int i = 0; i < runs.length; i += 2) {
Doug Felt4e0c5e52010-03-15 16:56:02 -0700669 if (offset == runs[i]) {
670 return true;
Doug Felt9f7a4442010-03-01 12:45:56 -0800671 }
672 }
Doug Felt4e0c5e52010-03-15 16:56:02 -0700673 return false;
Doug Felt9f7a4442010-03-01 12:45:56 -0800674 }
675
Fabrice Di Meglio34d2eba2011-08-31 19:46:15 -0700676 /**
677 * Returns true if the character at offset is right to left (RTL).
678 * @param offset the offset
679 * @return true if the character is RTL, false if it is LTR
680 */
681 public boolean isRtlCharAt(int offset) {
682 int line = getLineForOffset(offset);
683 Directions dirs = getLineDirections(line);
684 if (dirs == DIRS_ALL_LEFT_TO_RIGHT) {
685 return false;
686 }
687 if (dirs == DIRS_ALL_RIGHT_TO_LEFT) {
688 return true;
689 }
690 int[] runs = dirs.mDirections;
691 int lineStart = getLineStart(line);
692 for (int i = 0; i < runs.length; i += 2) {
693 int start = lineStart + (runs[i] & RUN_LENGTH_MASK);
694 // No need to test the end as an offset after the last run should return the value
695 // corresponding of the last run
696 if (offset >= start) {
697 int level = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
698 return ((level & 1) != 0);
699 }
700 }
701 // Should happen only if the offset is "out of bounds"
702 return false;
703 }
704
Doug Felt9f7a4442010-03-01 12:45:56 -0800705 private boolean primaryIsTrailingPrevious(int offset) {
706 int line = getLineForOffset(offset);
707 int lineStart = getLineStart(line);
Doug Felt4e0c5e52010-03-15 16:56:02 -0700708 int lineEnd = getLineEnd(line);
Doug Felt9f7a4442010-03-01 12:45:56 -0800709 int[] runs = getLineDirections(line).mDirections;
710
711 int levelAt = -1;
712 for (int i = 0; i < runs.length; i += 2) {
713 int start = lineStart + runs[i];
714 int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
715 if (limit > lineEnd) {
716 limit = lineEnd;
717 }
718 if (offset >= start && offset < limit) {
719 if (offset > start) {
720 // Previous character is at same level, so don't use trailing.
721 return false;
722 }
723 levelAt = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
724 break;
725 }
726 }
727 if (levelAt == -1) {
728 // Offset was limit of line.
729 levelAt = getParagraphDirection(line) == 1 ? 0 : 1;
730 }
731
732 // At level boundary, check previous level.
733 int levelBefore = -1;
734 if (offset == lineStart) {
735 levelBefore = getParagraphDirection(line) == 1 ? 0 : 1;
736 } else {
737 offset -= 1;
738 for (int i = 0; i < runs.length; i += 2) {
739 int start = lineStart + runs[i];
740 int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
741 if (limit > lineEnd) {
742 limit = lineEnd;
743 }
744 if (offset >= start && offset < limit) {
745 levelBefore = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
746 break;
747 }
748 }
749 }
750
751 return levelBefore < levelAt;
752 }
753
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800754 /**
755 * Get the primary horizontal position for the specified text offset.
756 * This is the location where a new character would be inserted in
757 * the paragraph's primary direction.
758 */
759 public float getPrimaryHorizontal(int offset) {
Doug Felt9f7a4442010-03-01 12:45:56 -0800760 boolean trailing = primaryIsTrailingPrevious(offset);
761 return getHorizontal(offset, trailing);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800762 }
763
764 /**
765 * Get the secondary horizontal position for the specified text offset.
766 * This is the location where a new character would be inserted in
767 * the direction other than the paragraph's primary direction.
768 */
769 public float getSecondaryHorizontal(int offset) {
Doug Felt9f7a4442010-03-01 12:45:56 -0800770 boolean trailing = primaryIsTrailingPrevious(offset);
771 return getHorizontal(offset, !trailing);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800772 }
773
Doug Felt9f7a4442010-03-01 12:45:56 -0800774 private float getHorizontal(int offset, boolean trailing) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800775 int line = getLineForOffset(offset);
776
Doug Felt9f7a4442010-03-01 12:45:56 -0800777 return getHorizontal(offset, trailing, line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800778 }
779
Doug Felt9f7a4442010-03-01 12:45:56 -0800780 private float getHorizontal(int offset, boolean trailing, int line) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800781 int start = getLineStart(line);
Doug Felte8e45f22010-03-29 14:58:40 -0700782 int end = getLineEnd(line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800783 int dir = getParagraphDirection(line);
Doug Feltc982f602010-05-25 11:51:40 -0700784 boolean hasTabOrEmoji = getLineContainsTab(line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800785 Directions directions = getLineDirections(line);
786
Doug Feltc982f602010-05-25 11:51:40 -0700787 TabStops tabStops = null;
788 if (hasTabOrEmoji && mText instanceof Spanned) {
789 // Just checking this line should be good enough, tabs should be
790 // consistent across all lines in a paragraph.
Eric Fischer74d31ef2010-08-05 15:29:36 -0700791 TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class);
Doug Feltc982f602010-05-25 11:51:40 -0700792 if (tabs.length > 0) {
793 tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
794 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800795 }
796
Doug Felte8e45f22010-03-29 14:58:40 -0700797 TextLine tl = TextLine.obtain();
Doug Feltc982f602010-05-25 11:51:40 -0700798 tl.set(mPaint, mText, start, end, dir, directions, hasTabOrEmoji, tabStops);
Doug Felte8e45f22010-03-29 14:58:40 -0700799 float wid = tl.measure(offset - start, trailing, null);
800 TextLine.recycle(tl);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800801
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800802 int left = getParagraphLeft(line);
803 int right = getParagraphRight(line);
804
Doug Feltc982f602010-05-25 11:51:40 -0700805 return getLineStartPos(line, left, right) + wid;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800806 }
807
808 /**
809 * Get the leftmost position that should be exposed for horizontal
810 * scrolling on the specified line.
811 */
812 public float getLineLeft(int line) {
813 int dir = getParagraphDirection(line);
814 Alignment align = getParagraphAlignment(line);
815
Doug Feltc0ccf0c2011-06-23 16:13:18 -0700816 if (align == Alignment.ALIGN_LEFT) {
817 return 0;
818 } else if (align == Alignment.ALIGN_NORMAL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800819 if (dir == DIR_RIGHT_TO_LEFT)
820 return getParagraphRight(line) - getLineMax(line);
821 else
822 return 0;
Doug Feltc0ccf0c2011-06-23 16:13:18 -0700823 } else if (align == Alignment.ALIGN_RIGHT) {
824 return mWidth - getLineMax(line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800825 } else if (align == Alignment.ALIGN_OPPOSITE) {
826 if (dir == DIR_RIGHT_TO_LEFT)
827 return 0;
828 else
829 return mWidth - getLineMax(line);
830 } else { /* align == Alignment.ALIGN_CENTER */
831 int left = getParagraphLeft(line);
832 int right = getParagraphRight(line);
833 int max = ((int) getLineMax(line)) & ~1;
834
835 return left + ((right - left) - max) / 2;
836 }
837 }
838
839 /**
840 * Get the rightmost position that should be exposed for horizontal
841 * scrolling on the specified line.
842 */
843 public float getLineRight(int line) {
844 int dir = getParagraphDirection(line);
845 Alignment align = getParagraphAlignment(line);
846
Doug Feltc0ccf0c2011-06-23 16:13:18 -0700847 if (align == Alignment.ALIGN_LEFT) {
848 return getParagraphLeft(line) + getLineMax(line);
849 } else if (align == Alignment.ALIGN_NORMAL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800850 if (dir == DIR_RIGHT_TO_LEFT)
851 return mWidth;
852 else
853 return getParagraphLeft(line) + getLineMax(line);
Doug Feltc0ccf0c2011-06-23 16:13:18 -0700854 } else if (align == Alignment.ALIGN_RIGHT) {
855 return mWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800856 } else if (align == Alignment.ALIGN_OPPOSITE) {
857 if (dir == DIR_RIGHT_TO_LEFT)
858 return getLineMax(line);
859 else
860 return mWidth;
861 } else { /* align == Alignment.ALIGN_CENTER */
862 int left = getParagraphLeft(line);
863 int right = getParagraphRight(line);
864 int max = ((int) getLineMax(line)) & ~1;
865
866 return right - ((right - left) - max) / 2;
867 }
868 }
869
870 /**
Doug Felt0c702b82010-05-14 10:55:42 -0700871 * Gets the unsigned horizontal extent of the specified line, including
Doug Feltc982f602010-05-25 11:51:40 -0700872 * leading margin indent, but excluding trailing whitespace.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800873 */
874 public float getLineMax(int line) {
Doug Feltc982f602010-05-25 11:51:40 -0700875 float margin = getParagraphLeadingMargin(line);
876 float signedExtent = getLineExtent(line, false);
877 return margin + signedExtent >= 0 ? signedExtent : -signedExtent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800878 }
879
880 /**
Doug Feltc982f602010-05-25 11:51:40 -0700881 * Gets the unsigned horizontal extent of the specified line, including
882 * leading margin indent and trailing whitespace.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800883 */
884 public float getLineWidth(int line) {
Doug Feltc982f602010-05-25 11:51:40 -0700885 float margin = getParagraphLeadingMargin(line);
886 float signedExtent = getLineExtent(line, true);
887 return margin + signedExtent >= 0 ? signedExtent : -signedExtent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800888 }
889
Doug Feltc982f602010-05-25 11:51:40 -0700890 /**
891 * Like {@link #getLineExtent(int,TabStops,boolean)} but determines the
892 * tab stops instead of using the ones passed in.
893 * @param line the index of the line
894 * @param full whether to include trailing whitespace
895 * @return the extent of the line
896 */
897 private float getLineExtent(int line, boolean full) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800898 int start = getLineStart(line);
Doug Felte8e45f22010-03-29 14:58:40 -0700899 int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
Doug Feltc982f602010-05-25 11:51:40 -0700900
901 boolean hasTabsOrEmoji = getLineContainsTab(line);
902 TabStops tabStops = null;
903 if (hasTabsOrEmoji && mText instanceof Spanned) {
904 // Just checking this line should be good enough, tabs should be
905 // consistent across all lines in a paragraph.
Eric Fischer74d31ef2010-08-05 15:29:36 -0700906 TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class);
Doug Feltc982f602010-05-25 11:51:40 -0700907 if (tabs.length > 0) {
908 tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
909 }
910 }
Doug Felte8e45f22010-03-29 14:58:40 -0700911 Directions directions = getLineDirections(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700912 // Returned directions can actually be null
913 if (directions == null) {
914 return 0f;
915 }
Doug Feltc982f602010-05-25 11:51:40 -0700916 int dir = getParagraphDirection(line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800917
Doug Felte8e45f22010-03-29 14:58:40 -0700918 TextLine tl = TextLine.obtain();
Doug Feltc982f602010-05-25 11:51:40 -0700919 tl.set(mPaint, mText, start, end, dir, directions, hasTabsOrEmoji, tabStops);
920 float width = tl.metrics(null);
921 TextLine.recycle(tl);
922 return width;
923 }
924
925 /**
926 * Returns the signed horizontal extent of the specified line, excluding
927 * leading margin. If full is false, excludes trailing whitespace.
928 * @param line the index of the line
929 * @param tabStops the tab stops, can be null if we know they're not used.
930 * @param full whether to include trailing whitespace
931 * @return the extent of the text on this line
932 */
933 private float getLineExtent(int line, TabStops tabStops, boolean full) {
934 int start = getLineStart(line);
935 int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
936 boolean hasTabsOrEmoji = getLineContainsTab(line);
937 Directions directions = getLineDirections(line);
938 int dir = getParagraphDirection(line);
939
940 TextLine tl = TextLine.obtain();
941 tl.set(mPaint, mText, start, end, dir, directions, hasTabsOrEmoji, tabStops);
Doug Felte8e45f22010-03-29 14:58:40 -0700942 float width = tl.metrics(null);
943 TextLine.recycle(tl);
944 return width;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800945 }
946
947 /**
948 * Get the line number corresponding to the specified vertical position.
949 * If you ask for a position above 0, you get 0; if you ask for a position
950 * below the bottom of the text, you get the last line.
951 */
952 // FIXME: It may be faster to do a linear search for layouts without many lines.
953 public int getLineForVertical(int vertical) {
954 int high = getLineCount(), low = -1, guess;
955
956 while (high - low > 1) {
957 guess = (high + low) / 2;
958
959 if (getLineTop(guess) > vertical)
960 high = guess;
961 else
962 low = guess;
963 }
964
965 if (low < 0)
966 return 0;
967 else
968 return low;
969 }
970
971 /**
972 * Get the line number on which the specified text offset appears.
973 * If you ask for a position before 0, you get 0; if you ask for a position
974 * beyond the end of the text, you get the last line.
975 */
976 public int getLineForOffset(int offset) {
977 int high = getLineCount(), low = -1, guess;
978
979 while (high - low > 1) {
980 guess = (high + low) / 2;
981
982 if (getLineStart(guess) > offset)
983 high = guess;
984 else
985 low = guess;
986 }
987
988 if (low < 0)
989 return 0;
990 else
991 return low;
992 }
993
994 /**
Doug Felt9f7a4442010-03-01 12:45:56 -0800995 * Get the character offset on the specified line whose position is
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800996 * closest to the specified horizontal position.
997 */
998 public int getOffsetForHorizontal(int line, float horiz) {
999 int max = getLineEnd(line) - 1;
1000 int min = getLineStart(line);
1001 Directions dirs = getLineDirections(line);
1002
1003 if (line == getLineCount() - 1)
1004 max++;
1005
1006 int best = min;
1007 float bestdist = Math.abs(getPrimaryHorizontal(best) - horiz);
1008
Doug Felt9f7a4442010-03-01 12:45:56 -08001009 for (int i = 0; i < dirs.mDirections.length; i += 2) {
1010 int here = min + dirs.mDirections[i];
1011 int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
1012 int swap = (dirs.mDirections[i+1] & RUN_RTL_FLAG) != 0 ? -1 : 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001013
1014 if (there > max)
1015 there = max;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001016 int high = there - 1 + 1, low = here + 1 - 1, guess;
1017
1018 while (high - low > 1) {
1019 guess = (high + low) / 2;
1020 int adguess = getOffsetAtStartOf(guess);
1021
1022 if (getPrimaryHorizontal(adguess) * swap >= horiz * swap)
1023 high = guess;
1024 else
1025 low = guess;
1026 }
1027
1028 if (low < here + 1)
1029 low = here + 1;
1030
1031 if (low < there) {
1032 low = getOffsetAtStartOf(low);
1033
1034 float dist = Math.abs(getPrimaryHorizontal(low) - horiz);
1035
1036 int aft = TextUtils.getOffsetAfter(mText, low);
1037 if (aft < there) {
1038 float other = Math.abs(getPrimaryHorizontal(aft) - horiz);
1039
1040 if (other < dist) {
1041 dist = other;
1042 low = aft;
1043 }
1044 }
1045
1046 if (dist < bestdist) {
1047 bestdist = dist;
Doug Felt9f7a4442010-03-01 12:45:56 -08001048 best = low;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001049 }
1050 }
1051
1052 float dist = Math.abs(getPrimaryHorizontal(here) - horiz);
1053
1054 if (dist < bestdist) {
1055 bestdist = dist;
1056 best = here;
1057 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001058 }
1059
1060 float dist = Math.abs(getPrimaryHorizontal(max) - horiz);
1061
1062 if (dist < bestdist) {
1063 bestdist = dist;
1064 best = max;
1065 }
1066
1067 return best;
1068 }
1069
1070 /**
1071 * Return the text offset after the last character on the specified line.
1072 */
1073 public final int getLineEnd(int line) {
1074 return getLineStart(line + 1);
1075 }
1076
Doug Felt9f7a4442010-03-01 12:45:56 -08001077 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001078 * Return the text offset after the last visible character (so whitespace
1079 * is not counted) on the specified line.
1080 */
1081 public int getLineVisibleEnd(int line) {
1082 return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1));
1083 }
Doug Felt9f7a4442010-03-01 12:45:56 -08001084
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001085 private int getLineVisibleEnd(int line, int start, int end) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001086 CharSequence text = mText;
1087 char ch;
1088 if (line == getLineCount() - 1) {
1089 return end;
1090 }
1091
1092 for (; end > start; end--) {
1093 ch = text.charAt(end - 1);
1094
1095 if (ch == '\n') {
1096 return end - 1;
1097 }
1098
1099 if (ch != ' ' && ch != '\t') {
1100 break;
1101 }
1102
1103 }
1104
1105 return end;
1106 }
1107
1108 /**
1109 * Return the vertical position of the bottom of the specified line.
1110 */
1111 public final int getLineBottom(int line) {
1112 return getLineTop(line + 1);
1113 }
1114
1115 /**
1116 * Return the vertical position of the baseline of the specified line.
1117 */
1118 public final int getLineBaseline(int line) {
1119 // getLineTop(line+1) == getLineTop(line)
1120 return getLineTop(line+1) - getLineDescent(line);
1121 }
1122
1123 /**
1124 * Get the ascent of the text on the specified line.
1125 * The return value is negative to match the Paint.ascent() convention.
1126 */
1127 public final int getLineAscent(int line) {
1128 // getLineTop(line+1) - getLineDescent(line) == getLineBaseLine(line)
1129 return getLineTop(line) - (getLineTop(line+1) - getLineDescent(line));
1130 }
1131
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001132 public int getOffsetToLeftOf(int offset) {
Doug Felt9f7a4442010-03-01 12:45:56 -08001133 return getOffsetToLeftRightOf(offset, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001134 }
1135
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001136 public int getOffsetToRightOf(int offset) {
Doug Felt9f7a4442010-03-01 12:45:56 -08001137 return getOffsetToLeftRightOf(offset, false);
1138 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001139
Doug Felt9f7a4442010-03-01 12:45:56 -08001140 private int getOffsetToLeftRightOf(int caret, boolean toLeft) {
1141 int line = getLineForOffset(caret);
1142 int lineStart = getLineStart(line);
1143 int lineEnd = getLineEnd(line);
Doug Felte8e45f22010-03-29 14:58:40 -07001144 int lineDir = getParagraphDirection(line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001145
Gilles Debunne162bf0f2010-11-16 16:23:53 -08001146 boolean lineChanged = false;
Doug Felte8e45f22010-03-29 14:58:40 -07001147 boolean advance = toLeft == (lineDir == DIR_RIGHT_TO_LEFT);
Gilles Debunne162bf0f2010-11-16 16:23:53 -08001148 // if walking off line, look at the line we're headed to
1149 if (advance) {
1150 if (caret == lineEnd) {
Doug Felte8e45f22010-03-29 14:58:40 -07001151 if (line < getLineCount() - 1) {
Gilles Debunne162bf0f2010-11-16 16:23:53 -08001152 lineChanged = true;
Doug Felte8e45f22010-03-29 14:58:40 -07001153 ++line;
1154 } else {
1155 return caret; // at very end, don't move
1156 }
Doug Felt9f7a4442010-03-01 12:45:56 -08001157 }
Gilles Debunne162bf0f2010-11-16 16:23:53 -08001158 } else {
1159 if (caret == lineStart) {
1160 if (line > 0) {
1161 lineChanged = true;
1162 --line;
1163 } else {
1164 return caret; // at very start, don't move
1165 }
1166 }
1167 }
Doug Felt9f7a4442010-03-01 12:45:56 -08001168
Gilles Debunne162bf0f2010-11-16 16:23:53 -08001169 if (lineChanged) {
Doug Felte8e45f22010-03-29 14:58:40 -07001170 lineStart = getLineStart(line);
1171 lineEnd = getLineEnd(line);
1172 int newDir = getParagraphDirection(line);
1173 if (newDir != lineDir) {
1174 // unusual case. we want to walk onto the line, but it runs
1175 // in a different direction than this one, so we fake movement
1176 // in the opposite direction.
1177 toLeft = !toLeft;
1178 lineDir = newDir;
1179 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001180 }
Doug Felt9f7a4442010-03-01 12:45:56 -08001181
Doug Felte8e45f22010-03-29 14:58:40 -07001182 Directions directions = getLineDirections(line);
Doug Felt9f7a4442010-03-01 12:45:56 -08001183
Doug Felte8e45f22010-03-29 14:58:40 -07001184 TextLine tl = TextLine.obtain();
1185 // XXX: we don't care about tabs
1186 tl.set(mPaint, mText, lineStart, lineEnd, lineDir, directions, false, null);
1187 caret = lineStart + tl.getOffsetToLeftRightOf(caret - lineStart, toLeft);
1188 tl = TextLine.recycle(tl);
1189 return caret;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001190 }
1191
1192 private int getOffsetAtStartOf(int offset) {
Doug Felt9f7a4442010-03-01 12:45:56 -08001193 // XXX this probably should skip local reorderings and
1194 // zero-width characters, look at callers
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001195 if (offset == 0)
1196 return 0;
1197
1198 CharSequence text = mText;
1199 char c = text.charAt(offset);
1200
1201 if (c >= '\uDC00' && c <= '\uDFFF') {
1202 char c1 = text.charAt(offset - 1);
1203
1204 if (c1 >= '\uD800' && c1 <= '\uDBFF')
1205 offset -= 1;
1206 }
1207
1208 if (mSpannedText) {
1209 ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset,
1210 ReplacementSpan.class);
1211
1212 for (int i = 0; i < spans.length; i++) {
1213 int start = ((Spanned) text).getSpanStart(spans[i]);
1214 int end = ((Spanned) text).getSpanEnd(spans[i]);
1215
1216 if (start < offset && end > offset)
1217 offset = start;
1218 }
1219 }
1220
1221 return offset;
1222 }
1223
1224 /**
1225 * Fills in the specified Path with a representation of a cursor
1226 * at the specified offset. This will often be a vertical line
Doug Felt4e0c5e52010-03-15 16:56:02 -07001227 * but can be multiple discontinuous lines in text with multiple
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001228 * directionalities.
1229 */
1230 public void getCursorPath(int point, Path dest,
1231 CharSequence editingBuffer) {
1232 dest.reset();
1233
1234 int line = getLineForOffset(point);
1235 int top = getLineTop(line);
1236 int bottom = getLineTop(line+1);
1237
1238 float h1 = getPrimaryHorizontal(point) - 0.5f;
Gilles Debunnef75c97e2011-02-10 16:09:53 -08001239 float h2 = isLevelBoundary(point) ? getSecondaryHorizontal(point) - 0.5f : h1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001240
Jeff Brown497a92c2010-09-12 17:55:08 -07001241 int caps = TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SHIFT_ON) |
1242 TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SELECTING);
1243 int fn = TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_ALT_ON);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001244 int dist = 0;
1245
1246 if (caps != 0 || fn != 0) {
1247 dist = (bottom - top) >> 2;
1248
1249 if (fn != 0)
1250 top += dist;
1251 if (caps != 0)
1252 bottom -= dist;
1253 }
1254
1255 if (h1 < 0.5f)
1256 h1 = 0.5f;
1257 if (h2 < 0.5f)
1258 h2 = 0.5f;
1259
Jozef BABJAK2fb503f2011-03-17 09:54:51 +01001260 if (Float.compare(h1, h2) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001261 dest.moveTo(h1, top);
1262 dest.lineTo(h1, bottom);
1263 } else {
1264 dest.moveTo(h1, top);
1265 dest.lineTo(h1, (top + bottom) >> 1);
1266
1267 dest.moveTo(h2, (top + bottom) >> 1);
1268 dest.lineTo(h2, bottom);
1269 }
1270
1271 if (caps == 2) {
1272 dest.moveTo(h2, bottom);
1273 dest.lineTo(h2 - dist, bottom + dist);
1274 dest.lineTo(h2, bottom);
1275 dest.lineTo(h2 + dist, bottom + dist);
1276 } else if (caps == 1) {
1277 dest.moveTo(h2, bottom);
1278 dest.lineTo(h2 - dist, bottom + dist);
1279
1280 dest.moveTo(h2 - dist, bottom + dist - 0.5f);
1281 dest.lineTo(h2 + dist, bottom + dist - 0.5f);
1282
1283 dest.moveTo(h2 + dist, bottom + dist);
1284 dest.lineTo(h2, bottom);
1285 }
1286
1287 if (fn == 2) {
1288 dest.moveTo(h1, top);
1289 dest.lineTo(h1 - dist, top - dist);
1290 dest.lineTo(h1, top);
1291 dest.lineTo(h1 + dist, top - dist);
1292 } else if (fn == 1) {
1293 dest.moveTo(h1, top);
1294 dest.lineTo(h1 - dist, top - dist);
1295
1296 dest.moveTo(h1 - dist, top - dist + 0.5f);
1297 dest.lineTo(h1 + dist, top - dist + 0.5f);
1298
1299 dest.moveTo(h1 + dist, top - dist);
1300 dest.lineTo(h1, top);
1301 }
1302 }
1303
1304 private void addSelection(int line, int start, int end,
1305 int top, int bottom, Path dest) {
1306 int linestart = getLineStart(line);
1307 int lineend = getLineEnd(line);
1308 Directions dirs = getLineDirections(line);
1309
1310 if (lineend > linestart && mText.charAt(lineend - 1) == '\n')
1311 lineend--;
1312
Doug Felt9f7a4442010-03-01 12:45:56 -08001313 for (int i = 0; i < dirs.mDirections.length; i += 2) {
1314 int here = linestart + dirs.mDirections[i];
1315 int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
1316
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001317 if (there > lineend)
1318 there = lineend;
1319
1320 if (start <= there && end >= here) {
1321 int st = Math.max(start, here);
1322 int en = Math.min(end, there);
1323
1324 if (st != en) {
Doug Felt9f7a4442010-03-01 12:45:56 -08001325 float h1 = getHorizontal(st, false, line);
1326 float h2 = getHorizontal(en, true, line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001327
Fabrice Di Meglio37166012011-08-31 13:56:37 -07001328 float left = Math.min(h1, h2);
1329 float right = Math.max(h1, h2);
1330
1331 dest.addRect(left, top, right, bottom, Path.Direction.CW);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001332 }
1333 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001334 }
1335 }
1336
1337 /**
1338 * Fills in the specified Path with a representation of a highlight
1339 * between the specified offsets. This will often be a rectangle
1340 * or a potentially discontinuous set of rectangles. If the start
1341 * and end are the same, the returned path is empty.
1342 */
1343 public void getSelectionPath(int start, int end, Path dest) {
1344 dest.reset();
1345
1346 if (start == end)
1347 return;
1348
1349 if (end < start) {
1350 int temp = end;
1351 end = start;
1352 start = temp;
1353 }
1354
1355 int startline = getLineForOffset(start);
1356 int endline = getLineForOffset(end);
1357
1358 int top = getLineTop(startline);
1359 int bottom = getLineBottom(endline);
1360
1361 if (startline == endline) {
1362 addSelection(startline, start, end, top, bottom, dest);
1363 } else {
1364 final float width = mWidth;
1365
1366 addSelection(startline, start, getLineEnd(startline),
1367 top, getLineBottom(startline), dest);
Doug Felt9f7a4442010-03-01 12:45:56 -08001368
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001369 if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT)
1370 dest.addRect(getLineLeft(startline), top,
1371 0, getLineBottom(startline), Path.Direction.CW);
1372 else
1373 dest.addRect(getLineRight(startline), top,
1374 width, getLineBottom(startline), Path.Direction.CW);
1375
1376 for (int i = startline + 1; i < endline; i++) {
1377 top = getLineTop(i);
1378 bottom = getLineBottom(i);
1379 dest.addRect(0, top, width, bottom, Path.Direction.CW);
1380 }
1381
1382 top = getLineTop(endline);
1383 bottom = getLineBottom(endline);
1384
1385 addSelection(endline, getLineStart(endline), end,
1386 top, bottom, dest);
1387
1388 if (getParagraphDirection(endline) == DIR_RIGHT_TO_LEFT)
1389 dest.addRect(width, top, getLineRight(endline), bottom, Path.Direction.CW);
1390 else
1391 dest.addRect(0, top, getLineLeft(endline), bottom, Path.Direction.CW);
1392 }
1393 }
1394
1395 /**
1396 * Get the alignment of the specified paragraph, taking into account
1397 * markup attached to it.
1398 */
1399 public final Alignment getParagraphAlignment(int line) {
1400 Alignment align = mAlignment;
1401
1402 if (mSpannedText) {
1403 Spanned sp = (Spanned) mText;
Eric Fischer74d31ef2010-08-05 15:29:36 -07001404 AlignmentSpan[] spans = getParagraphSpans(sp, getLineStart(line),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001405 getLineEnd(line),
1406 AlignmentSpan.class);
1407
1408 int spanLength = spans.length;
1409 if (spanLength > 0) {
1410 align = spans[spanLength-1].getAlignment();
1411 }
1412 }
1413
1414 return align;
1415 }
1416
1417 /**
1418 * Get the left edge of the specified paragraph, inset by left margins.
1419 */
1420 public final int getParagraphLeft(int line) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001421 int left = 0;
Doug Feltc982f602010-05-25 11:51:40 -07001422 int dir = getParagraphDirection(line);
1423 if (dir == DIR_RIGHT_TO_LEFT || !mSpannedText) {
1424 return left; // leading margin has no impact, or no styles
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001425 }
Doug Feltc982f602010-05-25 11:51:40 -07001426 return getParagraphLeadingMargin(line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001427 }
1428
1429 /**
1430 * Get the right edge of the specified paragraph, inset by right margins.
1431 */
1432 public final int getParagraphRight(int line) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001433 int right = mWidth;
Doug Feltc982f602010-05-25 11:51:40 -07001434 int dir = getParagraphDirection(line);
1435 if (dir == DIR_LEFT_TO_RIGHT || !mSpannedText) {
1436 return right; // leading margin has no impact, or no styles
1437 }
1438 return right - getParagraphLeadingMargin(line);
1439 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001440
Doug Feltc982f602010-05-25 11:51:40 -07001441 /**
1442 * Returns the effective leading margin (unsigned) for this line,
1443 * taking into account LeadingMarginSpan and LeadingMarginSpan2.
1444 * @param line the line index
1445 * @return the leading margin of this line
1446 */
1447 private int getParagraphLeadingMargin(int line) {
1448 if (!mSpannedText) {
1449 return 0;
1450 }
1451 Spanned spanned = (Spanned) mText;
Doug Felt0c702b82010-05-14 10:55:42 -07001452
Doug Feltc982f602010-05-25 11:51:40 -07001453 int lineStart = getLineStart(line);
1454 int lineEnd = getLineEnd(line);
Doug Felt0c702b82010-05-14 10:55:42 -07001455 int spanEnd = spanned.nextSpanTransition(lineStart, lineEnd,
Doug Feltc982f602010-05-25 11:51:40 -07001456 LeadingMarginSpan.class);
Eric Fischer74d31ef2010-08-05 15:29:36 -07001457 LeadingMarginSpan[] spans = getParagraphSpans(spanned, lineStart, spanEnd,
Doug Feltc982f602010-05-25 11:51:40 -07001458 LeadingMarginSpan.class);
1459 if (spans.length == 0) {
1460 return 0; // no leading margin span;
1461 }
Doug Felt0c702b82010-05-14 10:55:42 -07001462
Doug Feltc982f602010-05-25 11:51:40 -07001463 int margin = 0;
Doug Felt0c702b82010-05-14 10:55:42 -07001464
1465 boolean isFirstParaLine = lineStart == 0 ||
Doug Feltc982f602010-05-25 11:51:40 -07001466 spanned.charAt(lineStart - 1) == '\n';
Doug Felt0c702b82010-05-14 10:55:42 -07001467
Doug Feltc982f602010-05-25 11:51:40 -07001468 for (int i = 0; i < spans.length; i++) {
1469 LeadingMarginSpan span = spans[i];
1470 boolean useFirstLineMargin = isFirstParaLine;
1471 if (span instanceof LeadingMarginSpan2) {
1472 int spStart = spanned.getSpanStart(span);
1473 int spanLine = getLineForOffset(spStart);
1474 int count = ((LeadingMarginSpan2)span).getLeadingMarginLineCount();
Doug Felt0c702b82010-05-14 10:55:42 -07001475 useFirstLineMargin = line < spanLine + count;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001476 }
Doug Feltc982f602010-05-25 11:51:40 -07001477 margin += span.getLeadingMargin(useFirstLineMargin);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001478 }
1479
Doug Feltc982f602010-05-25 11:51:40 -07001480 return margin;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001481 }
1482
Doug Felte8e45f22010-03-29 14:58:40 -07001483 /* package */
1484 static float measurePara(TextPaint paint, TextPaint workPaint,
Doug Feltc982f602010-05-25 11:51:40 -07001485 CharSequence text, int start, int end) {
Doug Felte8e45f22010-03-29 14:58:40 -07001486
1487 MeasuredText mt = MeasuredText.obtain();
1488 TextLine tl = TextLine.obtain();
1489 try {
Doug Feltcb3791202011-07-07 11:57:48 -07001490 mt.setPara(text, start, end, TextDirectionHeuristics.LTR);
Doug Felte8e45f22010-03-29 14:58:40 -07001491 Directions directions;
Doug Feltc982f602010-05-25 11:51:40 -07001492 int dir;
1493 if (mt.mEasy) {
Doug Felte8e45f22010-03-29 14:58:40 -07001494 directions = DIRS_ALL_LEFT_TO_RIGHT;
Doug Feltc982f602010-05-25 11:51:40 -07001495 dir = Layout.DIR_LEFT_TO_RIGHT;
Doug Felte8e45f22010-03-29 14:58:40 -07001496 } else {
1497 directions = AndroidBidi.directions(mt.mDir, mt.mLevels,
1498 0, mt.mChars, 0, mt.mLen);
Doug Feltc982f602010-05-25 11:51:40 -07001499 dir = mt.mDir;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001500 }
Doug Feltc982f602010-05-25 11:51:40 -07001501 char[] chars = mt.mChars;
1502 int len = mt.mLen;
1503 boolean hasTabs = false;
1504 TabStops tabStops = null;
1505 for (int i = 0; i < len; ++i) {
1506 if (chars[i] == '\t') {
1507 hasTabs = true;
1508 if (text instanceof Spanned) {
1509 Spanned spanned = (Spanned) text;
Doug Felt0c702b82010-05-14 10:55:42 -07001510 int spanEnd = spanned.nextSpanTransition(start, end,
Doug Feltc982f602010-05-25 11:51:40 -07001511 TabStopSpan.class);
Eric Fischer74d31ef2010-08-05 15:29:36 -07001512 TabStopSpan[] spans = getParagraphSpans(spanned, start, spanEnd,
Doug Feltc982f602010-05-25 11:51:40 -07001513 TabStopSpan.class);
1514 if (spans.length > 0) {
1515 tabStops = new TabStops(TAB_INCREMENT, spans);
1516 }
1517 }
1518 break;
1519 }
1520 }
1521 tl.set(paint, text, start, end, dir, directions, hasTabs, tabStops);
Doug Felte8e45f22010-03-29 14:58:40 -07001522 return tl.metrics(null);
1523 } finally {
1524 TextLine.recycle(tl);
1525 MeasuredText.recycle(mt);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001526 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001527 }
1528
Doug Felt71b8dd72010-02-16 17:27:09 -08001529 /**
Doug Feltc982f602010-05-25 11:51:40 -07001530 * @hide
1531 */
1532 /* package */ static class TabStops {
1533 private int[] mStops;
1534 private int mNumStops;
1535 private int mIncrement;
Doug Felt0c702b82010-05-14 10:55:42 -07001536
Doug Feltc982f602010-05-25 11:51:40 -07001537 TabStops(int increment, Object[] spans) {
1538 reset(increment, spans);
1539 }
Doug Felt0c702b82010-05-14 10:55:42 -07001540
Doug Feltc982f602010-05-25 11:51:40 -07001541 void reset(int increment, Object[] spans) {
1542 this.mIncrement = increment;
1543
1544 int ns = 0;
1545 if (spans != null) {
1546 int[] stops = this.mStops;
1547 for (Object o : spans) {
1548 if (o instanceof TabStopSpan) {
1549 if (stops == null) {
1550 stops = new int[10];
1551 } else if (ns == stops.length) {
1552 int[] nstops = new int[ns * 2];
1553 for (int i = 0; i < ns; ++i) {
1554 nstops[i] = stops[i];
1555 }
1556 stops = nstops;
1557 }
1558 stops[ns++] = ((TabStopSpan) o).getTabStop();
1559 }
1560 }
1561 if (ns > 1) {
1562 Arrays.sort(stops, 0, ns);
1563 }
1564 if (stops != this.mStops) {
1565 this.mStops = stops;
1566 }
1567 }
1568 this.mNumStops = ns;
1569 }
Doug Felt0c702b82010-05-14 10:55:42 -07001570
Doug Feltc982f602010-05-25 11:51:40 -07001571 float nextTab(float h) {
1572 int ns = this.mNumStops;
1573 if (ns > 0) {
1574 int[] stops = this.mStops;
1575 for (int i = 0; i < ns; ++i) {
1576 int stop = stops[i];
1577 if (stop > h) {
1578 return stop;
1579 }
1580 }
1581 }
1582 return nextDefaultStop(h, mIncrement);
1583 }
1584
1585 public static float nextDefaultStop(float h, int inc) {
1586 return ((int) ((h + inc) / inc)) * inc;
1587 }
1588 }
Doug Felt0c702b82010-05-14 10:55:42 -07001589
Doug Feltc982f602010-05-25 11:51:40 -07001590 /**
Doug Felt71b8dd72010-02-16 17:27:09 -08001591 * Returns the position of the next tab stop after h on the line.
1592 *
1593 * @param text the text
1594 * @param start start of the line
1595 * @param end limit of the line
1596 * @param h the current horizontal offset
1597 * @param tabs the tabs, can be null. If it is null, any tabs in effect
1598 * on the line will be used. If there are no tabs, a default offset
1599 * will be used to compute the tab stop.
1600 * @return the offset of the next tab stop.
1601 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001602 /* package */ static float nextTab(CharSequence text, int start, int end,
1603 float h, Object[] tabs) {
1604 float nh = Float.MAX_VALUE;
1605 boolean alltabs = false;
1606
1607 if (text instanceof Spanned) {
1608 if (tabs == null) {
Eric Fischer74d31ef2010-08-05 15:29:36 -07001609 tabs = getParagraphSpans((Spanned) text, start, end, TabStopSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001610 alltabs = true;
1611 }
1612
1613 for (int i = 0; i < tabs.length; i++) {
1614 if (!alltabs) {
1615 if (!(tabs[i] instanceof TabStopSpan))
1616 continue;
1617 }
1618
1619 int where = ((TabStopSpan) tabs[i]).getTabStop();
1620
1621 if (where < nh && where > h)
1622 nh = where;
1623 }
1624
1625 if (nh != Float.MAX_VALUE)
1626 return nh;
1627 }
1628
1629 return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT;
1630 }
1631
1632 protected final boolean isSpanned() {
1633 return mSpannedText;
1634 }
1635
Eric Fischer74d31ef2010-08-05 15:29:36 -07001636 /**
1637 * Returns the same as <code>text.getSpans()</code>, except where
1638 * <code>start</code> and <code>end</code> are the same and are not
1639 * at the very beginning of the text, in which case an empty array
1640 * is returned instead.
1641 * <p>
1642 * This is needed because of the special case that <code>getSpans()</code>
1643 * on an empty range returns the spans adjacent to that range, which is
1644 * primarily for the sake of <code>TextWatchers</code> so they will get
1645 * notifications when text goes from empty to non-empty. But it also
1646 * has the unfortunate side effect that if the text ends with an empty
1647 * paragraph, that paragraph accidentally picks up the styles of the
1648 * preceding paragraph (even though those styles will not be picked up
1649 * by new text that is inserted into the empty paragraph).
1650 * <p>
1651 * The reason it just checks whether <code>start</code> and <code>end</code>
1652 * is the same is that the only time a line can contain 0 characters
1653 * is if it is the final paragraph of the Layout; otherwise any line will
1654 * contain at least one printing or newline character. The reason for the
1655 * additional check if <code>start</code> is greater than 0 is that
1656 * if the empty paragraph is the entire content of the buffer, paragraph
1657 * styles that are already applied to the buffer will apply to text that
1658 * is inserted into it.
1659 */
1660 /* package */ static <T> T[] getParagraphSpans(Spanned text, int start, int end, Class<T> type) {
1661 if (start == end && start > 0) {
1662 return (T[]) ArrayUtils.emptyArray(type);
1663 }
1664
1665 return text.getSpans(start, end, type);
1666 }
1667
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001668 private void ellipsize(int start, int end, int line,
1669 char[] dest, int destoff) {
1670 int ellipsisCount = getEllipsisCount(line);
1671
1672 if (ellipsisCount == 0) {
1673 return;
1674 }
1675
1676 int ellipsisStart = getEllipsisStart(line);
1677 int linestart = getLineStart(line);
1678
1679 for (int i = ellipsisStart; i < ellipsisStart + ellipsisCount; i++) {
1680 char c;
1681
1682 if (i == ellipsisStart) {
1683 c = '\u2026'; // ellipsis
1684 } else {
1685 c = '\uFEFF'; // 0-width space
1686 }
1687
1688 int a = i + linestart;
1689
1690 if (a >= start && a < end) {
1691 dest[destoff + a - start] = c;
1692 }
1693 }
1694 }
1695
1696 /**
1697 * Stores information about bidirectional (left-to-right or right-to-left)
Doug Felt9f7a4442010-03-01 12:45:56 -08001698 * text within the layout of a line.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001699 */
1700 public static class Directions {
Doug Felt9f7a4442010-03-01 12:45:56 -08001701 // Directions represents directional runs within a line of text.
1702 // Runs are pairs of ints listed in visual order, starting from the
1703 // leading margin. The first int of each pair is the offset from
1704 // the first character of the line to the start of the run. The
1705 // second int represents both the length and level of the run.
1706 // The length is in the lower bits, accessed by masking with
1707 // DIR_LENGTH_MASK. The level is in the higher bits, accessed
1708 // by shifting by DIR_LEVEL_SHIFT and masking by DIR_LEVEL_MASK.
1709 // To simply test for an RTL direction, test the bit using
1710 // DIR_RTL_FLAG, if set then the direction is rtl.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001711
Doug Felt9f7a4442010-03-01 12:45:56 -08001712 /* package */ int[] mDirections;
1713 /* package */ Directions(int[] dirs) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001714 mDirections = dirs;
1715 }
1716 }
1717
1718 /**
1719 * Return the offset of the first character to be ellipsized away,
1720 * relative to the start of the line. (So 0 if the beginning of the
1721 * line is ellipsized, not getLineStart().)
1722 */
1723 public abstract int getEllipsisStart(int line);
Doug Felte8e45f22010-03-29 14:58:40 -07001724
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001725 /**
1726 * Returns the number of characters to be ellipsized away, or 0 if
1727 * no ellipsis is to take place.
1728 */
1729 public abstract int getEllipsisCount(int line);
1730
1731 /* package */ static class Ellipsizer implements CharSequence, GetChars {
1732 /* package */ CharSequence mText;
1733 /* package */ Layout mLayout;
1734 /* package */ int mWidth;
1735 /* package */ TextUtils.TruncateAt mMethod;
1736
1737 public Ellipsizer(CharSequence s) {
1738 mText = s;
1739 }
1740
1741 public char charAt(int off) {
1742 char[] buf = TextUtils.obtain(1);
1743 getChars(off, off + 1, buf, 0);
1744 char ret = buf[0];
1745
1746 TextUtils.recycle(buf);
1747 return ret;
1748 }
1749
1750 public void getChars(int start, int end, char[] dest, int destoff) {
1751 int line1 = mLayout.getLineForOffset(start);
1752 int line2 = mLayout.getLineForOffset(end);
1753
1754 TextUtils.getChars(mText, start, end, dest, destoff);
1755
1756 for (int i = line1; i <= line2; i++) {
1757 mLayout.ellipsize(start, end, i, dest, destoff);
1758 }
1759 }
1760
1761 public int length() {
1762 return mText.length();
1763 }
Doug Felt9f7a4442010-03-01 12:45:56 -08001764
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001765 public CharSequence subSequence(int start, int end) {
1766 char[] s = new char[end - start];
1767 getChars(start, end, s, 0);
1768 return new String(s);
1769 }
1770
Gilles Debunne162bf0f2010-11-16 16:23:53 -08001771 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001772 public String toString() {
1773 char[] s = new char[length()];
1774 getChars(0, length(), s, 0);
1775 return new String(s);
1776 }
1777
1778 }
1779
1780 /* package */ static class SpannedEllipsizer
1781 extends Ellipsizer implements Spanned {
1782 private Spanned mSpanned;
1783
1784 public SpannedEllipsizer(CharSequence display) {
1785 super(display);
1786 mSpanned = (Spanned) display;
1787 }
1788
1789 public <T> T[] getSpans(int start, int end, Class<T> type) {
1790 return mSpanned.getSpans(start, end, type);
1791 }
1792
1793 public int getSpanStart(Object tag) {
1794 return mSpanned.getSpanStart(tag);
1795 }
1796
1797 public int getSpanEnd(Object tag) {
1798 return mSpanned.getSpanEnd(tag);
1799 }
1800
1801 public int getSpanFlags(Object tag) {
1802 return mSpanned.getSpanFlags(tag);
1803 }
1804
1805 public int nextSpanTransition(int start, int limit, Class type) {
1806 return mSpanned.nextSpanTransition(start, limit, type);
1807 }
1808
Gilles Debunne162bf0f2010-11-16 16:23:53 -08001809 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001810 public CharSequence subSequence(int start, int end) {
1811 char[] s = new char[end - start];
1812 getChars(start, end, s, 0);
1813
1814 SpannableString ss = new SpannableString(new String(s));
1815 TextUtils.copySpansFrom(mSpanned, start, end, Object.class, ss, 0);
1816 return ss;
1817 }
1818 }
1819
1820 private CharSequence mText;
1821 private TextPaint mPaint;
1822 /* package */ TextPaint mWorkPaint;
1823 private int mWidth;
1824 private Alignment mAlignment = Alignment.ALIGN_NORMAL;
1825 private float mSpacingMult;
1826 private float mSpacingAdd;
Romain Guyc4d8eb62010-08-18 20:48:33 -07001827 private static final Rect sTempRect = new Rect();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001828 private boolean mSpannedText;
Doug Feltcb3791202011-07-07 11:57:48 -07001829 private TextDirectionHeuristic mTextDir;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001830
1831 public static final int DIR_LEFT_TO_RIGHT = 1;
1832 public static final int DIR_RIGHT_TO_LEFT = -1;
Doug Felt9f7a4442010-03-01 12:45:56 -08001833
Doug Felt20178d62010-02-22 13:39:01 -08001834 /* package */ static final int DIR_REQUEST_LTR = 1;
1835 /* package */ static final int DIR_REQUEST_RTL = -1;
1836 /* package */ static final int DIR_REQUEST_DEFAULT_LTR = 2;
1837 /* package */ static final int DIR_REQUEST_DEFAULT_RTL = -2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001838
Doug Felt9f7a4442010-03-01 12:45:56 -08001839 /* package */ static final int RUN_LENGTH_MASK = 0x03ffffff;
1840 /* package */ static final int RUN_LEVEL_SHIFT = 26;
1841 /* package */ static final int RUN_LEVEL_MASK = 0x3f;
1842 /* package */ static final int RUN_RTL_FLAG = 1 << RUN_LEVEL_SHIFT;
1843
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001844 public enum Alignment {
1845 ALIGN_NORMAL,
1846 ALIGN_OPPOSITE,
1847 ALIGN_CENTER,
Doug Feltc0ccf0c2011-06-23 16:13:18 -07001848 /** @hide */
1849 ALIGN_LEFT,
1850 /** @hide */
1851 ALIGN_RIGHT,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001852 }
1853
1854 private static final int TAB_INCREMENT = 20;
1855
1856 /* package */ static final Directions DIRS_ALL_LEFT_TO_RIGHT =
Doug Felt9f7a4442010-03-01 12:45:56 -08001857 new Directions(new int[] { 0, RUN_LENGTH_MASK });
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001858 /* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT =
Doug Felt9f7a4442010-03-01 12:45:56 -08001859 new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08001860
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001861}