blob: e34a0efccf21ee26bad9d6238f4881bfc2262c65 [file] [log] [blame]
Doug Felte8e45f22010-03-29 14:58:40 -07001/*
2 * Copyright (C) 2010 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
Doug Felte8e45f22010-03-29 14:58:40 -070019import android.graphics.Bitmap;
20import android.graphics.Canvas;
21import android.graphics.Paint;
Doug Felte8e45f22010-03-29 14:58:40 -070022import android.graphics.Paint.FontMetricsInt;
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -070023import android.graphics.RectF;
Doug Felte8e45f22010-03-29 14:58:40 -070024import android.text.Layout.Directions;
Doug Feltc982f602010-05-25 11:51:40 -070025import android.text.Layout.TabStops;
Doug Felte8e45f22010-03-29 14:58:40 -070026import android.text.style.CharacterStyle;
27import android.text.style.MetricAffectingSpan;
28import android.text.style.ReplacementSpan;
Doug Felte8e45f22010-03-29 14:58:40 -070029import android.util.Log;
30
Gilles Debunnedd8f5ed2011-08-10 18:25:49 -070031import com.android.internal.util.ArrayUtils;
32
Doug Felte8e45f22010-03-29 14:58:40 -070033/**
34 * Represents a line of styled text, for measuring in visual order and
35 * for rendering.
36 *
37 * <p>Get a new instance using obtain(), and when finished with it, return it
38 * to the pool using recycle().
39 *
40 * <p>Call set to prepare the instance for use, then either draw, measure,
41 * metrics, or caretToLeftRightOf.
42 *
43 * @hide
44 */
45class TextLine {
Romain Guybc7cdb62011-05-26 18:48:49 -070046 private static final boolean DEBUG = false;
47
Doug Felte8e45f22010-03-29 14:58:40 -070048 private TextPaint mPaint;
49 private CharSequence mText;
50 private int mStart;
51 private int mLen;
52 private int mDir;
53 private Directions mDirections;
54 private boolean mHasTabs;
Doug Feltc982f602010-05-25 11:51:40 -070055 private TabStops mTabs;
Doug Felte8e45f22010-03-29 14:58:40 -070056 private char[] mChars;
57 private boolean mCharsValid;
58 private Spanned mSpanned;
Gilles Debunne345cb032010-06-16 17:13:23 -070059 private final TextPaint mWorkPaint = new TextPaint();
Gilles Debunnec1f44832011-12-08 16:03:00 -080060 private final SpanSet<MetricAffectingSpan> mMetricAffectingSpanSpanSet =
61 new SpanSet<MetricAffectingSpan>(MetricAffectingSpan.class);
62 private final SpanSet<CharacterStyle> mCharacterStyleSpanSet =
63 new SpanSet<CharacterStyle>(CharacterStyle.class);
64 private final SpanSet<ReplacementSpan> mReplacementSpanSpanSet =
65 new SpanSet<ReplacementSpan>(ReplacementSpan.class);
Doug Felte8e45f22010-03-29 14:58:40 -070066
Romain Guybc7cdb62011-05-26 18:48:49 -070067 private static final TextLine[] sCached = new TextLine[3];
Doug Felte8e45f22010-03-29 14:58:40 -070068
69 /**
70 * Returns a new TextLine from the shared pool.
71 *
72 * @return an uninitialized TextLine
73 */
74 static TextLine obtain() {
75 TextLine tl;
Romain Guybc7cdb62011-05-26 18:48:49 -070076 synchronized (sCached) {
77 for (int i = sCached.length; --i >= 0;) {
78 if (sCached[i] != null) {
79 tl = sCached[i];
80 sCached[i] = null;
Doug Felte8e45f22010-03-29 14:58:40 -070081 return tl;
82 }
83 }
84 }
85 tl = new TextLine();
Romain Guybc7cdb62011-05-26 18:48:49 -070086 if (DEBUG) {
87 Log.v("TLINE", "new: " + tl);
88 }
Doug Felte8e45f22010-03-29 14:58:40 -070089 return tl;
90 }
91
92 /**
93 * Puts a TextLine back into the shared pool. Do not use this TextLine once
94 * it has been returned.
95 * @param tl the textLine
96 * @return null, as a convenience from clearing references to the provided
97 * TextLine
98 */
99 static TextLine recycle(TextLine tl) {
100 tl.mText = null;
101 tl.mPaint = null;
102 tl.mDirections = null;
Gilles Debunnec3fb7a12011-12-12 14:03:40 -0800103
104 tl.mMetricAffectingSpanSpanSet.recycle();
105 tl.mCharacterStyleSpanSet.recycle();
106 tl.mReplacementSpanSpanSet.recycle();
107
Romain Guybc7cdb62011-05-26 18:48:49 -0700108 synchronized(sCached) {
109 for (int i = 0; i < sCached.length; ++i) {
110 if (sCached[i] == null) {
111 sCached[i] = tl;
Gilles Debunnef902d7b2011-01-25 09:09:46 -0800112 break;
Doug Felte8e45f22010-03-29 14:58:40 -0700113 }
114 }
115 }
116 return null;
117 }
118
119 /**
120 * Initializes a TextLine and prepares it for use.
121 *
122 * @param paint the base paint for the line
123 * @param text the text, can be Styled
124 * @param start the start of the line relative to the text
125 * @param limit the limit of the line relative to the text
126 * @param dir the paragraph direction of this line
127 * @param directions the directions information of this line
128 * @param hasTabs true if the line might contain tabs or emoji
Doug Feltc982f602010-05-25 11:51:40 -0700129 * @param tabStops the tabStops. Can be null.
Doug Felte8e45f22010-03-29 14:58:40 -0700130 */
131 void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
Doug Feltc982f602010-05-25 11:51:40 -0700132 Directions directions, boolean hasTabs, TabStops tabStops) {
Doug Felte8e45f22010-03-29 14:58:40 -0700133 mPaint = paint;
134 mText = text;
135 mStart = start;
136 mLen = limit - start;
137 mDir = dir;
138 mDirections = directions;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700139 if (mDirections == null) {
140 throw new IllegalArgumentException("Directions cannot be null");
141 }
Doug Felte8e45f22010-03-29 14:58:40 -0700142 mHasTabs = hasTabs;
143 mSpanned = null;
Doug Felte8e45f22010-03-29 14:58:40 -0700144
145 boolean hasReplacement = false;
146 if (text instanceof Spanned) {
147 mSpanned = (Spanned) text;
Gilles Debunnec1f44832011-12-08 16:03:00 -0800148 mReplacementSpanSpanSet.init(mSpanned, start, limit);
149 hasReplacement = mReplacementSpanSpanSet.numberOfSpans > 0;
Doug Felte8e45f22010-03-29 14:58:40 -0700150 }
151
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800152 mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
Doug Felte8e45f22010-03-29 14:58:40 -0700153
154 if (mCharsValid) {
155 if (mChars == null || mChars.length < mLen) {
156 mChars = new char[ArrayUtils.idealCharArraySize(mLen)];
157 }
158 TextUtils.getChars(text, start, limit, mChars, 0);
Doug Felt0c702b82010-05-14 10:55:42 -0700159 if (hasReplacement) {
160 // Handle these all at once so we don't have to do it as we go.
161 // Replace the first character of each replacement run with the
162 // object-replacement character and the remainder with zero width
163 // non-break space aka BOM. Cursor movement code skips these
164 // zero-width characters.
165 char[] chars = mChars;
166 for (int i = start, inext; i < limit; i = inext) {
Gilles Debunnec1f44832011-12-08 16:03:00 -0800167 inext = mReplacementSpanSpanSet.getNextTransition(i, limit);
168 if (mReplacementSpanSpanSet.hasSpansIntersecting(i, inext)) {
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800169 // transition into a span
Doug Felt0c702b82010-05-14 10:55:42 -0700170 chars[i - start] = '\ufffc';
171 for (int j = i - start + 1, e = inext - start; j < e; ++j) {
172 chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip
173 }
174 }
175 }
176 }
Doug Felte8e45f22010-03-29 14:58:40 -0700177 }
Doug Feltc982f602010-05-25 11:51:40 -0700178 mTabs = tabStops;
Doug Felte8e45f22010-03-29 14:58:40 -0700179 }
180
181 /**
182 * Renders the TextLine.
183 *
184 * @param c the canvas to render on
185 * @param x the leading margin position
186 * @param top the top of the line
187 * @param y the baseline
188 * @param bottom the bottom of the line
189 */
190 void draw(Canvas c, float x, int top, int y, int bottom) {
191 if (!mHasTabs) {
192 if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
Romain Guybc7cdb62011-05-26 18:48:49 -0700193 drawRun(c, 0, mLen, false, x, top, y, bottom, false);
Doug Felte8e45f22010-03-29 14:58:40 -0700194 return;
195 }
196 if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
Romain Guybc7cdb62011-05-26 18:48:49 -0700197 drawRun(c, 0, mLen, true, x, top, y, bottom, false);
Doug Felte8e45f22010-03-29 14:58:40 -0700198 return;
199 }
200 }
201
202 float h = 0;
203 int[] runs = mDirections.mDirections;
204 RectF emojiRect = null;
205
206 int lastRunIndex = runs.length - 2;
207 for (int i = 0; i < runs.length; i += 2) {
208 int runStart = runs[i];
209 int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
210 if (runLimit > mLen) {
211 runLimit = mLen;
212 }
213 boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
214
215 int segstart = runStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700216 for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
217 int codept = 0;
218 Bitmap bm = null;
219
220 if (mHasTabs && j < runLimit) {
221 codept = mChars[j];
222 if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) {
223 codept = Character.codePointAt(mChars, j);
224 if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) {
225 bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
226 } else if (codept > 0xffff) {
227 ++j;
228 continue;
229 }
230 }
231 }
232
233 if (j == runLimit || codept == '\t' || bm != null) {
Romain Guybc7cdb62011-05-26 18:48:49 -0700234 h += drawRun(c, segstart, j, runIsRtl, x+h, top, y, bottom,
Doug Felte8e45f22010-03-29 14:58:40 -0700235 i != lastRunIndex || j != mLen);
236
237 if (codept == '\t') {
238 h = mDir * nextTab(h * mDir);
239 } else if (bm != null) {
240 float bmAscent = ascent(j);
241 float bitmapHeight = bm.getHeight();
242 float scale = -bmAscent / bitmapHeight;
243 float width = bm.getWidth() * scale;
244
245 if (emojiRect == null) {
246 emojiRect = new RectF();
247 }
248 emojiRect.set(x + h, y + bmAscent,
249 x + h + width, y);
250 c.drawBitmap(bm, null, emojiRect, mPaint);
251 h += width;
252 j++;
253 }
254 segstart = j + 1;
255 }
256 }
257 }
258 }
259
260 /**
261 * Returns metrics information for the entire line.
262 *
263 * @param fmi receives font metrics information, can be null
264 * @return the signed width of the line
265 */
266 float metrics(FontMetricsInt fmi) {
267 return measure(mLen, false, fmi);
268 }
269
270 /**
271 * Returns information about a position on the line.
272 *
273 * @param offset the line-relative character offset, between 0 and the
274 * line length, inclusive
275 * @param trailing true to measure the trailing edge of the character
276 * before offset, false to measure the leading edge of the character
277 * at offset.
278 * @param fmi receives metrics information about the requested
279 * character, can be null.
280 * @return the signed offset from the leading margin to the requested
281 * character edge.
282 */
283 float measure(int offset, boolean trailing, FontMetricsInt fmi) {
284 int target = trailing ? offset - 1 : offset;
285 if (target < 0) {
286 return 0;
287 }
288
289 float h = 0;
290
291 if (!mHasTabs) {
292 if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
Romain Guybc7cdb62011-05-26 18:48:49 -0700293 return measureRun(0, offset, mLen, false, fmi);
Doug Felte8e45f22010-03-29 14:58:40 -0700294 }
295 if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
Romain Guybc7cdb62011-05-26 18:48:49 -0700296 return measureRun(0, offset, mLen, true, fmi);
Doug Felte8e45f22010-03-29 14:58:40 -0700297 }
298 }
299
300 char[] chars = mChars;
301 int[] runs = mDirections.mDirections;
302 for (int i = 0; i < runs.length; i += 2) {
303 int runStart = runs[i];
304 int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
305 if (runLimit > mLen) {
306 runLimit = mLen;
307 }
308 boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
309
310 int segstart = runStart;
311 for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
312 int codept = 0;
313 Bitmap bm = null;
314
315 if (mHasTabs && j < runLimit) {
316 codept = chars[j];
317 if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) {
318 codept = Character.codePointAt(chars, j);
319 if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) {
320 bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
321 } else if (codept > 0xffff) {
322 ++j;
323 continue;
324 }
325 }
326 }
327
328 if (j == runLimit || codept == '\t' || bm != null) {
329 boolean inSegment = target >= segstart && target < j;
330
331 boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
332 if (inSegment && advance) {
Romain Guybc7cdb62011-05-26 18:48:49 -0700333 return h += measureRun(segstart, offset, j, runIsRtl, fmi);
Doug Felte8e45f22010-03-29 14:58:40 -0700334 }
335
Romain Guybc7cdb62011-05-26 18:48:49 -0700336 float w = measureRun(segstart, j, j, runIsRtl, fmi);
Doug Felte8e45f22010-03-29 14:58:40 -0700337 h += advance ? w : -w;
338
339 if (inSegment) {
Romain Guybc7cdb62011-05-26 18:48:49 -0700340 return h += measureRun(segstart, offset, j, runIsRtl, null);
Doug Felte8e45f22010-03-29 14:58:40 -0700341 }
342
343 if (codept == '\t') {
344 if (offset == j) {
345 return h;
346 }
347 h = mDir * nextTab(h * mDir);
348 if (target == j) {
349 return h;
350 }
351 }
352
353 if (bm != null) {
354 float bmAscent = ascent(j);
355 float wid = bm.getWidth() * -bmAscent / bm.getHeight();
356 h += mDir * wid;
357 j++;
358 }
359
360 segstart = j + 1;
361 }
362 }
363 }
364
365 return h;
366 }
367
368 /**
369 * Draws a unidirectional (but possibly multi-styled) run of text.
370 *
Romain Guybc7cdb62011-05-26 18:48:49 -0700371 *
Doug Felte8e45f22010-03-29 14:58:40 -0700372 * @param c the canvas to draw on
Doug Felte8e45f22010-03-29 14:58:40 -0700373 * @param start the line-relative start
374 * @param limit the line-relative limit
375 * @param runIsRtl true if the run is right-to-left
376 * @param x the position of the run that is closest to the leading margin
377 * @param top the top of the line
378 * @param y the baseline
379 * @param bottom the bottom of the line
380 * @param needWidth true if the width value is required.
381 * @return the signed width of the run, based on the paragraph direction.
382 * Only valid if needWidth is true.
383 */
Romain Guybc7cdb62011-05-26 18:48:49 -0700384 private float drawRun(Canvas c, int start,
Doug Felte8e45f22010-03-29 14:58:40 -0700385 int limit, boolean runIsRtl, float x, int top, int y, int bottom,
386 boolean needWidth) {
387
388 if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
Romain Guybc7cdb62011-05-26 18:48:49 -0700389 float w = -measureRun(start, limit, limit, runIsRtl, null);
390 handleRun(start, limit, limit, runIsRtl, c, x + w, top,
Doug Felt0c702b82010-05-14 10:55:42 -0700391 y, bottom, null, false);
Doug Felte8e45f22010-03-29 14:58:40 -0700392 return w;
393 }
394
Romain Guybc7cdb62011-05-26 18:48:49 -0700395 return handleRun(start, limit, limit, runIsRtl, c, x, top,
Doug Felt0c702b82010-05-14 10:55:42 -0700396 y, bottom, null, needWidth);
Doug Felte8e45f22010-03-29 14:58:40 -0700397 }
398
399 /**
400 * Measures a unidirectional (but possibly multi-styled) run of text.
401 *
Romain Guybc7cdb62011-05-26 18:48:49 -0700402 *
Doug Felte8e45f22010-03-29 14:58:40 -0700403 * @param start the line-relative start of the run
404 * @param offset the offset to measure to, between start and limit inclusive
405 * @param limit the line-relative limit of the run
406 * @param runIsRtl true if the run is right-to-left
407 * @param fmi receives metrics information about the requested
408 * run, can be null.
409 * @return the signed width from the start of the run to the leading edge
410 * of the character at offset, based on the run (not paragraph) direction
411 */
Romain Guybc7cdb62011-05-26 18:48:49 -0700412 private float measureRun(int start, int offset, int limit, boolean runIsRtl,
413 FontMetricsInt fmi) {
414 return handleRun(start, offset, limit, runIsRtl, null, 0, 0, 0, 0, fmi, true);
Doug Felte8e45f22010-03-29 14:58:40 -0700415 }
416
417 /**
418 * Walk the cursor through this line, skipping conjuncts and
419 * zero-width characters.
420 *
421 * <p>This function cannot properly walk the cursor off the ends of the line
422 * since it does not know about any shaping on the previous/following line
423 * that might affect the cursor position. Callers must either avoid these
424 * situations or handle the result specially.
425 *
Doug Felte8e45f22010-03-29 14:58:40 -0700426 * @param cursor the starting position of the cursor, between 0 and the
427 * length of the line, inclusive
428 * @param toLeft true if the caret is moving to the left.
429 * @return the new offset. If it is less than 0 or greater than the length
430 * of the line, the previous/following line should be examined to get the
431 * actual offset.
432 */
433 int getOffsetToLeftRightOf(int cursor, boolean toLeft) {
434 // 1) The caret marks the leading edge of a character. The character
435 // logically before it might be on a different level, and the active caret
436 // position is on the character at the lower level. If that character
437 // was the previous character, the caret is on its trailing edge.
438 // 2) Take this character/edge and move it in the indicated direction.
439 // This gives you a new character and a new edge.
440 // 3) This position is between two visually adjacent characters. One of
441 // these might be at a lower level. The active position is on the
442 // character at the lower level.
443 // 4) If the active position is on the trailing edge of the character,
444 // the new caret position is the following logical character, else it
445 // is the character.
446
447 int lineStart = 0;
448 int lineEnd = mLen;
449 boolean paraIsRtl = mDir == -1;
450 int[] runs = mDirections.mDirections;
451
452 int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1;
453 boolean trailing = false;
454
455 if (cursor == lineStart) {
456 runIndex = -2;
457 } else if (cursor == lineEnd) {
458 runIndex = runs.length;
459 } else {
460 // First, get information about the run containing the character with
461 // the active caret.
462 for (runIndex = 0; runIndex < runs.length; runIndex += 2) {
463 runStart = lineStart + runs[runIndex];
464 if (cursor >= runStart) {
465 runLimit = runStart + (runs[runIndex+1] & Layout.RUN_LENGTH_MASK);
466 if (runLimit > lineEnd) {
467 runLimit = lineEnd;
468 }
469 if (cursor < runLimit) {
470 runLevel = (runs[runIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
471 Layout.RUN_LEVEL_MASK;
472 if (cursor == runStart) {
473 // The caret is on a run boundary, see if we should
474 // use the position on the trailing edge of the previous
475 // logical character instead.
476 int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit;
477 int pos = cursor - 1;
478 for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) {
479 prevRunStart = lineStart + runs[prevRunIndex];
480 if (pos >= prevRunStart) {
481 prevRunLimit = prevRunStart +
482 (runs[prevRunIndex+1] & Layout.RUN_LENGTH_MASK);
483 if (prevRunLimit > lineEnd) {
484 prevRunLimit = lineEnd;
485 }
486 if (pos < prevRunLimit) {
487 prevRunLevel = (runs[prevRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT)
488 & Layout.RUN_LEVEL_MASK;
489 if (prevRunLevel < runLevel) {
490 // Start from logically previous character.
491 runIndex = prevRunIndex;
492 runLevel = prevRunLevel;
493 runStart = prevRunStart;
494 runLimit = prevRunLimit;
495 trailing = true;
496 break;
497 }
498 }
499 }
500 }
501 }
502 break;
503 }
504 }
505 }
506
507 // caret might be == lineEnd. This is generally a space or paragraph
508 // separator and has an associated run, but might be the end of
509 // text, in which case it doesn't. If that happens, we ran off the
510 // end of the run list, and runIndex == runs.length. In this case,
511 // we are at a run boundary so we skip the below test.
512 if (runIndex != runs.length) {
513 boolean runIsRtl = (runLevel & 0x1) != 0;
514 boolean advance = toLeft == runIsRtl;
515 if (cursor != (advance ? runLimit : runStart) || advance != trailing) {
516 // Moving within or into the run, so we can move logically.
Doug Felt0c702b82010-05-14 10:55:42 -0700517 newCaret = getOffsetBeforeAfter(runIndex, runStart, runLimit,
518 runIsRtl, cursor, advance);
Doug Felte8e45f22010-03-29 14:58:40 -0700519 // If the new position is internal to the run, we're at the strong
520 // position already so we're finished.
521 if (newCaret != (advance ? runLimit : runStart)) {
522 return newCaret;
523 }
524 }
525 }
526 }
527
528 // If newCaret is -1, we're starting at a run boundary and crossing
529 // into another run. Otherwise we've arrived at a run boundary, and
530 // need to figure out which character to attach to. Note we might
531 // need to run this twice, if we cross a run boundary and end up at
532 // another run boundary.
533 while (true) {
534 boolean advance = toLeft == paraIsRtl;
535 int otherRunIndex = runIndex + (advance ? 2 : -2);
536 if (otherRunIndex >= 0 && otherRunIndex < runs.length) {
537 int otherRunStart = lineStart + runs[otherRunIndex];
538 int otherRunLimit = otherRunStart +
539 (runs[otherRunIndex+1] & Layout.RUN_LENGTH_MASK);
540 if (otherRunLimit > lineEnd) {
541 otherRunLimit = lineEnd;
542 }
543 int otherRunLevel = (runs[otherRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
544 Layout.RUN_LEVEL_MASK;
545 boolean otherRunIsRtl = (otherRunLevel & 1) != 0;
546
547 advance = toLeft == otherRunIsRtl;
548 if (newCaret == -1) {
Doug Felt0c702b82010-05-14 10:55:42 -0700549 newCaret = getOffsetBeforeAfter(otherRunIndex, otherRunStart,
550 otherRunLimit, otherRunIsRtl,
Doug Felte8e45f22010-03-29 14:58:40 -0700551 advance ? otherRunStart : otherRunLimit, advance);
552 if (newCaret == (advance ? otherRunLimit : otherRunStart)) {
553 // Crossed and ended up at a new boundary,
554 // repeat a second and final time.
555 runIndex = otherRunIndex;
556 runLevel = otherRunLevel;
557 continue;
558 }
559 break;
560 }
561
562 // The new caret is at a boundary.
563 if (otherRunLevel < runLevel) {
564 // The strong character is in the other run.
565 newCaret = advance ? otherRunStart : otherRunLimit;
566 }
567 break;
568 }
569
570 if (newCaret == -1) {
571 // We're walking off the end of the line. The paragraph
572 // level is always equal to or lower than any internal level, so
573 // the boundaries get the strong caret.
Doug Felt0c702b82010-05-14 10:55:42 -0700574 newCaret = advance ? mLen + 1 : -1;
Doug Felte8e45f22010-03-29 14:58:40 -0700575 break;
576 }
577
578 // Else we've arrived at the end of the line. That's a strong position.
579 // We might have arrived here by crossing over a run with no internal
580 // breaks and dropping out of the above loop before advancing one final
581 // time, so reset the caret.
582 // Note, we use '<=' below to handle a situation where the only run
583 // on the line is a counter-directional run. If we're not advancing,
584 // we can end up at the 'lineEnd' position but the caret we want is at
585 // the lineStart.
586 if (newCaret <= lineEnd) {
587 newCaret = advance ? lineEnd : lineStart;
588 }
589 break;
590 }
591
592 return newCaret;
593 }
594
595 /**
596 * Returns the next valid offset within this directional run, skipping
597 * conjuncts and zero-width characters. This should not be called to walk
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -0700598 * off the end of the line, since the returned values might not be valid
Doug Felt0c702b82010-05-14 10:55:42 -0700599 * on neighboring lines. If the returned offset is less than zero or
600 * greater than the line length, the offset should be recomputed on the
601 * preceding or following line, respectively.
Doug Felte8e45f22010-03-29 14:58:40 -0700602 *
603 * @param runIndex the run index
Doug Felt0c702b82010-05-14 10:55:42 -0700604 * @param runStart the start of the run
605 * @param runLimit the limit of the run
606 * @param runIsRtl true if the run is right-to-left
Doug Felte8e45f22010-03-29 14:58:40 -0700607 * @param offset the offset
608 * @param after true if the new offset should logically follow the provided
609 * offset
610 * @return the new offset
611 */
Doug Felt0c702b82010-05-14 10:55:42 -0700612 private int getOffsetBeforeAfter(int runIndex, int runStart, int runLimit,
613 boolean runIsRtl, int offset, boolean after) {
Doug Felte8e45f22010-03-29 14:58:40 -0700614
Doug Felt0c702b82010-05-14 10:55:42 -0700615 if (runIndex < 0 || offset == (after ? mLen : 0)) {
616 // Walking off end of line. Since we don't know
617 // what cursor positions are available on other lines, we can't
618 // return accurate values. These are a guess.
Doug Felte8e45f22010-03-29 14:58:40 -0700619 if (after) {
Doug Felt0c702b82010-05-14 10:55:42 -0700620 return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart;
621 }
622 return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart;
623 }
624
625 TextPaint wp = mWorkPaint;
626 wp.set(mPaint);
627
628 int spanStart = runStart;
629 int spanLimit;
630 if (mSpanned == null) {
631 spanLimit = runLimit;
632 } else {
633 int target = after ? offset + 1 : offset;
634 int limit = mStart + runLimit;
635 while (true) {
636 spanLimit = mSpanned.nextSpanTransition(mStart + spanStart, limit,
637 MetricAffectingSpan.class) - mStart;
638 if (spanLimit >= target) {
639 break;
Doug Felte8e45f22010-03-29 14:58:40 -0700640 }
Doug Felt0c702b82010-05-14 10:55:42 -0700641 spanStart = spanLimit;
642 }
643
644 MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart,
645 mStart + spanLimit, MetricAffectingSpan.class);
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800646 spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
Doug Felt0c702b82010-05-14 10:55:42 -0700647
648 if (spans.length > 0) {
649 ReplacementSpan replacement = null;
650 for (int j = 0; j < spans.length; j++) {
651 MetricAffectingSpan span = spans[j];
652 if (span instanceof ReplacementSpan) {
653 replacement = (ReplacementSpan)span;
654 } else {
655 span.updateMeasureState(wp);
656 }
657 }
658
659 if (replacement != null) {
660 // If we have a replacement span, we're moving either to
661 // the start or end of this span.
662 return after ? spanLimit : spanStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700663 }
664 }
Doug Felte8e45f22010-03-29 14:58:40 -0700665 }
666
Doug Felt0c702b82010-05-14 10:55:42 -0700667 int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
668 if (mCharsValid) {
669 return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -0800670 offset, cursorOpt);
Doug Felt0c702b82010-05-14 10:55:42 -0700671 } else {
672 return wp.getTextRunCursor(mText, mStart + spanStart,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -0800673 mStart + spanLimit, mStart + offset, cursorOpt) - mStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700674 }
Doug Felte8e45f22010-03-29 14:58:40 -0700675 }
676
677 /**
Gilles Debunne0bb00092010-12-02 15:50:26 -0800678 * @param wp
679 */
680 private static void expandMetricsFromPaint(FontMetricsInt fmi, TextPaint wp) {
681 final int previousTop = fmi.top;
682 final int previousAscent = fmi.ascent;
683 final int previousDescent = fmi.descent;
684 final int previousBottom = fmi.bottom;
685 final int previousLeading = fmi.leading;
686
687 wp.getFontMetricsInt(fmi);
688
Fabrice Di Meglio8a5137a2011-09-22 18:49:43 -0700689 updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom,
690 previousLeading);
691 }
692
693 static void updateMetrics(FontMetricsInt fmi, int previousTop, int previousAscent,
694 int previousDescent, int previousBottom, int previousLeading) {
Gilles Debunne0bb00092010-12-02 15:50:26 -0800695 fmi.top = Math.min(fmi.top, previousTop);
696 fmi.ascent = Math.min(fmi.ascent, previousAscent);
697 fmi.descent = Math.max(fmi.descent, previousDescent);
698 fmi.bottom = Math.max(fmi.bottom, previousBottom);
699 fmi.leading = Math.max(fmi.leading, previousLeading);
700 }
701
702 /**
Doug Felte8e45f22010-03-29 14:58:40 -0700703 * Utility function for measuring and rendering text. The text must
704 * not include a tab or emoji.
705 *
706 * @param wp the working paint
707 * @param start the start of the text
Doug Felt0c702b82010-05-14 10:55:42 -0700708 * @param end the end of the text
Doug Felte8e45f22010-03-29 14:58:40 -0700709 * @param runIsRtl true if the run is right-to-left
710 * @param c the canvas, can be null if rendering is not needed
711 * @param x the edge of the run closest to the leading margin
712 * @param top the top of the line
713 * @param y the baseline
714 * @param bottom the bottom of the line
715 * @param fmi receives metrics information, can be null
716 * @param needWidth true if the width of the run is needed
717 * @return the signed width of the run based on the run direction; only
718 * valid if needWidth is true
719 */
Doug Felt0c702b82010-05-14 10:55:42 -0700720 private float handleText(TextPaint wp, int start, int end,
721 int contextStart, int contextEnd, boolean runIsRtl,
722 Canvas c, float x, int top, int y, int bottom,
Doug Felte8e45f22010-03-29 14:58:40 -0700723 FontMetricsInt fmi, boolean needWidth) {
724
Fabrice Di Meglio850dffa2011-08-08 09:45:09 -0700725 // Get metrics first (even for empty strings or "0" width runs)
726 if (fmi != null) {
727 expandMetricsFromPaint(fmi, wp);
728 }
Doug Felte8e45f22010-03-29 14:58:40 -0700729
Doug Felt0c702b82010-05-14 10:55:42 -0700730 int runLen = end - start;
Fabrice Di Meglio850dffa2011-08-08 09:45:09 -0700731 // No need to do anything if the run width is "0"
732 if (runLen == 0) {
733 return 0f;
734 }
735
736 float ret = 0;
737
Doug Felt0c702b82010-05-14 10:55:42 -0700738 int contextLen = contextEnd - contextStart;
Gilles Debunnec9fd9782011-09-09 09:20:12 -0700739 if (needWidth || (c != null && (wp.bgColor != 0 || wp.underlineColor != 0 || runIsRtl))) {
Doug Felte8e45f22010-03-29 14:58:40 -0700740 if (mCharsValid) {
Doug Felt0c702b82010-05-14 10:55:42 -0700741 ret = wp.getTextRunAdvances(mChars, start, runLen,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -0800742 contextStart, contextLen, null, 0);
Doug Felte8e45f22010-03-29 14:58:40 -0700743 } else {
Doug Felt0c702b82010-05-14 10:55:42 -0700744 int delta = mStart;
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -0800745 ret = wp.getTextRunAdvances(mText, delta + start, delta + end,
746 delta + contextStart, delta + contextEnd, null, 0);
Doug Felte8e45f22010-03-29 14:58:40 -0700747 }
748 }
749
Doug Felte8e45f22010-03-29 14:58:40 -0700750 if (c != null) {
751 if (runIsRtl) {
752 x -= ret;
753 }
754
755 if (wp.bgColor != 0) {
Gilles Debunnedd8f5ed2011-08-10 18:25:49 -0700756 int previousColor = wp.getColor();
757 Paint.Style previousStyle = wp.getStyle();
758
Doug Felte8e45f22010-03-29 14:58:40 -0700759 wp.setColor(wp.bgColor);
760 wp.setStyle(Paint.Style.FILL);
Doug Felte8e45f22010-03-29 14:58:40 -0700761 c.drawRect(x, top, x + ret, bottom, wp);
762
Gilles Debunnedd8f5ed2011-08-10 18:25:49 -0700763 wp.setStyle(previousStyle);
764 wp.setColor(previousColor);
765 }
766
Gilles Debunnec9fd9782011-09-09 09:20:12 -0700767 if (wp.underlineColor != 0) {
Gilles Debunnedd8f5ed2011-08-10 18:25:49 -0700768 // kStdUnderline_Offset = 1/9, defined in SkTextFormatParams.h
Luca Zanoline6d36822011-08-30 18:04:34 +0100769 float underlineTop = y + wp.baselineShift + (1.0f / 9.0f) * wp.getTextSize();
Gilles Debunnedd8f5ed2011-08-10 18:25:49 -0700770
771 int previousColor = wp.getColor();
772 Paint.Style previousStyle = wp.getStyle();
Luca Zanoline6d36822011-08-30 18:04:34 +0100773 boolean previousAntiAlias = wp.isAntiAlias();
Gilles Debunnedd8f5ed2011-08-10 18:25:49 -0700774
Gilles Debunnedd8f5ed2011-08-10 18:25:49 -0700775 wp.setStyle(Paint.Style.FILL);
Luca Zanoline6d36822011-08-30 18:04:34 +0100776 wp.setAntiAlias(true);
777
Gilles Debunnec9fd9782011-09-09 09:20:12 -0700778 wp.setColor(wp.underlineColor);
779 c.drawRect(x, underlineTop, x + ret, underlineTop + wp.underlineThickness, wp);
Gilles Debunnedd8f5ed2011-08-10 18:25:49 -0700780
781 wp.setStyle(previousStyle);
782 wp.setColor(previousColor);
Luca Zanoline6d36822011-08-30 18:04:34 +0100783 wp.setAntiAlias(previousAntiAlias);
Doug Felte8e45f22010-03-29 14:58:40 -0700784 }
785
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -0800786 drawTextRun(c, wp, start, end, contextStart, contextEnd, x, y + wp.baselineShift);
Doug Felte8e45f22010-03-29 14:58:40 -0700787 }
788
789 return runIsRtl ? -ret : ret;
790 }
791
792 /**
793 * Utility function for measuring and rendering a replacement.
794 *
Romain Guybc7cdb62011-05-26 18:48:49 -0700795 *
Doug Felte8e45f22010-03-29 14:58:40 -0700796 * @param replacement the replacement
797 * @param wp the work paint
Doug Felte8e45f22010-03-29 14:58:40 -0700798 * @param start the start of the run
799 * @param limit the limit of the run
800 * @param runIsRtl true if the run is right-to-left
801 * @param c the canvas, can be null if not rendering
802 * @param x the edge of the replacement closest to the leading margin
803 * @param top the top of the line
804 * @param y the baseline
805 * @param bottom the bottom of the line
806 * @param fmi receives metrics information, can be null
807 * @param needWidth true if the width of the replacement is needed
Doug Felte8e45f22010-03-29 14:58:40 -0700808 * @return the signed width of the run based on the run direction; only
809 * valid if needWidth is true
810 */
811 private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
Romain Guybc7cdb62011-05-26 18:48:49 -0700812 int start, int limit, boolean runIsRtl, Canvas c,
Doug Felte8e45f22010-03-29 14:58:40 -0700813 float x, int top, int y, int bottom, FontMetricsInt fmi,
Doug Felt0c702b82010-05-14 10:55:42 -0700814 boolean needWidth) {
Doug Felte8e45f22010-03-29 14:58:40 -0700815
816 float ret = 0;
817
Doug Felt0c702b82010-05-14 10:55:42 -0700818 int textStart = mStart + start;
819 int textLimit = mStart + limit;
820
821 if (needWidth || (c != null && runIsRtl)) {
Fabrice Di Meglio8a5137a2011-09-22 18:49:43 -0700822 int previousTop = 0;
823 int previousAscent = 0;
824 int previousDescent = 0;
825 int previousBottom = 0;
826 int previousLeading = 0;
827
828 boolean needUpdateMetrics = (fmi != null);
829
830 if (needUpdateMetrics) {
831 previousTop = fmi.top;
832 previousAscent = fmi.ascent;
833 previousDescent = fmi.descent;
834 previousBottom = fmi.bottom;
835 previousLeading = fmi.leading;
836 }
837
Doug Felt0c702b82010-05-14 10:55:42 -0700838 ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
Fabrice Di Meglio8a5137a2011-09-22 18:49:43 -0700839
840 if (needUpdateMetrics) {
841 updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom,
842 previousLeading);
843 }
Doug Felte8e45f22010-03-29 14:58:40 -0700844 }
845
Doug Felt0c702b82010-05-14 10:55:42 -0700846 if (c != null) {
847 if (runIsRtl) {
848 x -= ret;
Doug Felte8e45f22010-03-29 14:58:40 -0700849 }
Doug Felt0c702b82010-05-14 10:55:42 -0700850 replacement.draw(c, mText, textStart, textLimit,
851 x, top, y, bottom, wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700852 }
853
854 return runIsRtl ? -ret : ret;
855 }
856
857 /**
858 * Utility function for handling a unidirectional run. The run must not
859 * contain tabs or emoji but can contain styles.
860 *
Romain Guybc7cdb62011-05-26 18:48:49 -0700861 *
Doug Felte8e45f22010-03-29 14:58:40 -0700862 * @param start the line-relative start of the run
Doug Felt0c702b82010-05-14 10:55:42 -0700863 * @param measureLimit the offset to measure to, between start and limit inclusive
Doug Felte8e45f22010-03-29 14:58:40 -0700864 * @param limit the limit of the run
865 * @param runIsRtl true if the run is right-to-left
866 * @param c the canvas, can be null
867 * @param x the end of the run closest to the leading margin
868 * @param top the top of the line
869 * @param y the baseline
870 * @param bottom the bottom of the line
871 * @param fmi receives metrics information, can be null
872 * @param needWidth true if the width is required
Doug Felte8e45f22010-03-29 14:58:40 -0700873 * @return the signed width of the run based on the run direction; only
874 * valid if needWidth is true
875 */
Romain Guybc7cdb62011-05-26 18:48:49 -0700876 private float handleRun(int start, int measureLimit,
Doug Felte8e45f22010-03-29 14:58:40 -0700877 int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
Doug Felt0c702b82010-05-14 10:55:42 -0700878 int bottom, FontMetricsInt fmi, boolean needWidth) {
Doug Felte8e45f22010-03-29 14:58:40 -0700879
Gilles Debunnef483e512011-04-28 15:08:54 -0700880 // Case of an empty line, make sure we update fmi according to mPaint
881 if (start == measureLimit) {
882 TextPaint wp = mWorkPaint;
883 wp.set(mPaint);
Fabrice Di Meglio15c097a2011-08-08 14:42:41 -0700884 if (fmi != null) {
885 expandMetricsFromPaint(fmi, wp);
886 }
887 return 0f;
Gilles Debunnef483e512011-04-28 15:08:54 -0700888 }
889
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700890 if (mSpanned == null) {
891 TextPaint wp = mWorkPaint;
892 wp.set(mPaint);
893 final int mlimit = measureLimit;
894 return handleText(wp, start, mlimit, start, limit, runIsRtl, c, x, top,
895 y, bottom, fmi, needWidth || mlimit < measureLimit);
896 }
897
Gilles Debunnec1f44832011-12-08 16:03:00 -0800898 mMetricAffectingSpanSpanSet.init(mSpanned, mStart + start, mStart + limit);
899 mCharacterStyleSpanSet.init(mSpanned, mStart + start, mStart + limit);
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700900
Doug Felte8e45f22010-03-29 14:58:40 -0700901 // Shaping needs to take into account context up to metric boundaries,
902 // but rendering needs to take into account character style boundaries.
Doug Felt0c702b82010-05-14 10:55:42 -0700903 // So we iterate through metric runs to get metric bounds,
904 // then within each metric run iterate through character style runs
905 // for the run bounds.
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700906 final float originalX = x;
Doug Felt0c702b82010-05-14 10:55:42 -0700907 for (int i = start, inext; i < measureLimit; i = inext) {
Doug Felte8e45f22010-03-29 14:58:40 -0700908 TextPaint wp = mWorkPaint;
909 wp.set(mPaint);
910
Gilles Debunnec1f44832011-12-08 16:03:00 -0800911 inext = mMetricAffectingSpanSpanSet.getNextTransition(mStart + i, mStart + limit) -
912 mStart;
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700913 int mlimit = Math.min(inext, measureLimit);
Doug Felte8e45f22010-03-29 14:58:40 -0700914
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700915 ReplacementSpan replacement = null;
Doug Felte8e45f22010-03-29 14:58:40 -0700916
Gilles Debunnec1f44832011-12-08 16:03:00 -0800917 for (int j = 0; j < mMetricAffectingSpanSpanSet.numberOfSpans; j++) {
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700918 // Both intervals [spanStarts..spanEnds] and [mStart + i..mStart + mlimit] are NOT
919 // empty by construction. This special case in getSpans() explains the >= & <= tests
Gilles Debunnec1f44832011-12-08 16:03:00 -0800920 if ((mMetricAffectingSpanSpanSet.spanStarts[j] >= mStart + mlimit) ||
921 (mMetricAffectingSpanSpanSet.spanEnds[j] <= mStart + i)) continue;
922 MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j];
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700923 if (span instanceof ReplacementSpan) {
924 replacement = (ReplacementSpan)span;
925 } else {
926 // We might have a replacement that uses the draw
927 // state, otherwise measure state would suffice.
928 span.updateDrawState(wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700929 }
930 }
931
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700932 if (replacement != null) {
933 x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y,
934 bottom, fmi, needWidth || mlimit < measureLimit);
935 continue;
936 }
937
Raph Levien42ef515d2012-10-22 15:01:17 -0700938 for (int j = i, jnext; j < mlimit; j = jnext) {
939 jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + mlimit) -
940 mStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700941
Raph Levien42ef515d2012-10-22 15:01:17 -0700942 wp.set(mPaint);
943 for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
944 // Intentionally using >= and <= as explained above
945 if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + jnext) ||
946 (mCharacterStyleSpanSet.spanEnds[k] <= mStart + j)) continue;
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700947
Raph Levien42ef515d2012-10-22 15:01:17 -0700948 CharacterStyle span = mCharacterStyleSpanSet.spans[k];
949 span.updateDrawState(wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700950 }
Raph Levien42ef515d2012-10-22 15:01:17 -0700951
952 x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
953 top, y, bottom, fmi, needWidth || jnext < measureLimit);
Doug Felte8e45f22010-03-29 14:58:40 -0700954 }
955 }
956
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700957 return x - originalX;
Doug Felte8e45f22010-03-29 14:58:40 -0700958 }
959
Doug Felte8e45f22010-03-29 14:58:40 -0700960 /**
961 * Render a text run with the set-up paint.
962 *
963 * @param c the canvas
964 * @param wp the paint used to render the text
Doug Felt0c702b82010-05-14 10:55:42 -0700965 * @param start the start of the run
966 * @param end the end of the run
967 * @param contextStart the start of context for the run
968 * @param contextEnd the end of the context for the run
Doug Felte8e45f22010-03-29 14:58:40 -0700969 * @param x the x position of the left edge of the run
970 * @param y the baseline of the run
971 */
Doug Felt0c702b82010-05-14 10:55:42 -0700972 private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -0800973 int contextStart, int contextEnd, float x, int y) {
Doug Felte8e45f22010-03-29 14:58:40 -0700974
Doug Felte8e45f22010-03-29 14:58:40 -0700975 if (mCharsValid) {
Doug Felt0c702b82010-05-14 10:55:42 -0700976 int count = end - start;
977 int contextCount = contextEnd - contextStart;
978 c.drawTextRun(mChars, start, count, contextStart, contextCount,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -0800979 x, y, wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700980 } else {
Doug Felt0c702b82010-05-14 10:55:42 -0700981 int delta = mStart;
982 c.drawTextRun(mText, delta + start, delta + end,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -0800983 delta + contextStart, delta + contextEnd, x, y, wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700984 }
985 }
986
987 /**
988 * Returns the ascent of the text at start. This is used for scaling
989 * emoji.
990 *
991 * @param pos the line-relative position
992 * @return the ascent of the text at start
993 */
994 float ascent(int pos) {
995 if (mSpanned == null) {
996 return mPaint.ascent();
997 }
998
999 pos += mStart;
Gilles Debunne945ee9b2011-09-19 19:18:18 -07001000 MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1, MetricAffectingSpan.class);
Doug Felte8e45f22010-03-29 14:58:40 -07001001 if (spans.length == 0) {
1002 return mPaint.ascent();
1003 }
1004
1005 TextPaint wp = mWorkPaint;
1006 wp.set(mPaint);
1007 for (MetricAffectingSpan span : spans) {
1008 span.updateMeasureState(wp);
1009 }
1010 return wp.ascent();
1011 }
1012
1013 /**
1014 * Returns the next tab position.
1015 *
1016 * @param h the (unsigned) offset from the leading margin
1017 * @return the (unsigned) tab position after this offset
1018 */
1019 float nextTab(float h) {
Doug Feltc982f602010-05-25 11:51:40 -07001020 if (mTabs != null) {
1021 return mTabs.nextTab(h);
Doug Felte8e45f22010-03-29 14:58:40 -07001022 }
Doug Feltc982f602010-05-25 11:51:40 -07001023 return TabStops.nextDefaultStop(h, TAB_INCREMENT);
Doug Felte8e45f22010-03-29 14:58:40 -07001024 }
1025
1026 private static final int TAB_INCREMENT = 20;
1027}