blob: 1e8a2f7676de28346c2294cb668a7f1963ba5bf5 [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
Gilles Debunne945ee9b2011-09-19 19:18:18 -070033import java.lang.reflect.Array;
34
Doug Felte8e45f22010-03-29 14:58:40 -070035/**
36 * Represents a line of styled text, for measuring in visual order and
37 * for rendering.
38 *
39 * <p>Get a new instance using obtain(), and when finished with it, return it
40 * to the pool using recycle().
41 *
42 * <p>Call set to prepare the instance for use, then either draw, measure,
43 * metrics, or caretToLeftRightOf.
44 *
45 * @hide
46 */
47class TextLine {
Romain Guybc7cdb62011-05-26 18:48:49 -070048 private static final boolean DEBUG = false;
49
Doug Felte8e45f22010-03-29 14:58:40 -070050 private TextPaint mPaint;
51 private CharSequence mText;
52 private int mStart;
53 private int mLen;
54 private int mDir;
55 private Directions mDirections;
56 private boolean mHasTabs;
Doug Feltc982f602010-05-25 11:51:40 -070057 private TabStops mTabs;
Doug Felte8e45f22010-03-29 14:58:40 -070058 private char[] mChars;
59 private boolean mCharsValid;
60 private Spanned mSpanned;
Gilles Debunne345cb032010-06-16 17:13:23 -070061 private final TextPaint mWorkPaint = new TextPaint();
Gilles Debunnec1f44832011-12-08 16:03:00 -080062 private final SpanSet<MetricAffectingSpan> mMetricAffectingSpanSpanSet =
63 new SpanSet<MetricAffectingSpan>(MetricAffectingSpan.class);
64 private final SpanSet<CharacterStyle> mCharacterStyleSpanSet =
65 new SpanSet<CharacterStyle>(CharacterStyle.class);
66 private final SpanSet<ReplacementSpan> mReplacementSpanSpanSet =
67 new SpanSet<ReplacementSpan>(ReplacementSpan.class);
Doug Felte8e45f22010-03-29 14:58:40 -070068
Romain Guybc7cdb62011-05-26 18:48:49 -070069 private static final TextLine[] sCached = new TextLine[3];
Doug Felte8e45f22010-03-29 14:58:40 -070070
71 /**
72 * Returns a new TextLine from the shared pool.
73 *
74 * @return an uninitialized TextLine
75 */
76 static TextLine obtain() {
77 TextLine tl;
Romain Guybc7cdb62011-05-26 18:48:49 -070078 synchronized (sCached) {
79 for (int i = sCached.length; --i >= 0;) {
80 if (sCached[i] != null) {
81 tl = sCached[i];
82 sCached[i] = null;
Doug Felte8e45f22010-03-29 14:58:40 -070083 return tl;
84 }
85 }
86 }
87 tl = new TextLine();
Romain Guybc7cdb62011-05-26 18:48:49 -070088 if (DEBUG) {
89 Log.v("TLINE", "new: " + tl);
90 }
Doug Felte8e45f22010-03-29 14:58:40 -070091 return tl;
92 }
93
94 /**
95 * Puts a TextLine back into the shared pool. Do not use this TextLine once
96 * it has been returned.
97 * @param tl the textLine
98 * @return null, as a convenience from clearing references to the provided
99 * TextLine
100 */
101 static TextLine recycle(TextLine tl) {
102 tl.mText = null;
103 tl.mPaint = null;
104 tl.mDirections = null;
Gilles Debunnec3fb7a12011-12-12 14:03:40 -0800105
106 tl.mMetricAffectingSpanSpanSet.recycle();
107 tl.mCharacterStyleSpanSet.recycle();
108 tl.mReplacementSpanSpanSet.recycle();
109
Romain Guybc7cdb62011-05-26 18:48:49 -0700110 synchronized(sCached) {
111 for (int i = 0; i < sCached.length; ++i) {
112 if (sCached[i] == null) {
113 sCached[i] = tl;
Gilles Debunnef902d7b2011-01-25 09:09:46 -0800114 break;
Doug Felte8e45f22010-03-29 14:58:40 -0700115 }
116 }
117 }
118 return null;
119 }
120
121 /**
122 * Initializes a TextLine and prepares it for use.
123 *
124 * @param paint the base paint for the line
125 * @param text the text, can be Styled
126 * @param start the start of the line relative to the text
127 * @param limit the limit of the line relative to the text
128 * @param dir the paragraph direction of this line
129 * @param directions the directions information of this line
130 * @param hasTabs true if the line might contain tabs or emoji
Doug Feltc982f602010-05-25 11:51:40 -0700131 * @param tabStops the tabStops. Can be null.
Doug Felte8e45f22010-03-29 14:58:40 -0700132 */
133 void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
Doug Feltc982f602010-05-25 11:51:40 -0700134 Directions directions, boolean hasTabs, TabStops tabStops) {
Doug Felte8e45f22010-03-29 14:58:40 -0700135 mPaint = paint;
136 mText = text;
137 mStart = start;
138 mLen = limit - start;
139 mDir = dir;
140 mDirections = directions;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700141 if (mDirections == null) {
142 throw new IllegalArgumentException("Directions cannot be null");
143 }
Doug Felte8e45f22010-03-29 14:58:40 -0700144 mHasTabs = hasTabs;
145 mSpanned = null;
Doug Felte8e45f22010-03-29 14:58:40 -0700146
147 boolean hasReplacement = false;
148 if (text instanceof Spanned) {
149 mSpanned = (Spanned) text;
Gilles Debunnec1f44832011-12-08 16:03:00 -0800150 mReplacementSpanSpanSet.init(mSpanned, start, limit);
151 hasReplacement = mReplacementSpanSpanSet.numberOfSpans > 0;
Doug Felte8e45f22010-03-29 14:58:40 -0700152 }
153
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800154 mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
Doug Felte8e45f22010-03-29 14:58:40 -0700155
156 if (mCharsValid) {
157 if (mChars == null || mChars.length < mLen) {
158 mChars = new char[ArrayUtils.idealCharArraySize(mLen)];
159 }
160 TextUtils.getChars(text, start, limit, mChars, 0);
Doug Felt0c702b82010-05-14 10:55:42 -0700161 if (hasReplacement) {
162 // Handle these all at once so we don't have to do it as we go.
163 // Replace the first character of each replacement run with the
164 // object-replacement character and the remainder with zero width
165 // non-break space aka BOM. Cursor movement code skips these
166 // zero-width characters.
167 char[] chars = mChars;
168 for (int i = start, inext; i < limit; i = inext) {
Gilles Debunnec1f44832011-12-08 16:03:00 -0800169 inext = mReplacementSpanSpanSet.getNextTransition(i, limit);
170 if (mReplacementSpanSpanSet.hasSpansIntersecting(i, inext)) {
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800171 // transition into a span
Doug Felt0c702b82010-05-14 10:55:42 -0700172 chars[i - start] = '\ufffc';
173 for (int j = i - start + 1, e = inext - start; j < e; ++j) {
174 chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip
175 }
176 }
177 }
178 }
Doug Felte8e45f22010-03-29 14:58:40 -0700179 }
Doug Feltc982f602010-05-25 11:51:40 -0700180 mTabs = tabStops;
Doug Felte8e45f22010-03-29 14:58:40 -0700181 }
182
183 /**
184 * Renders the TextLine.
185 *
186 * @param c the canvas to render on
187 * @param x the leading margin position
188 * @param top the top of the line
189 * @param y the baseline
190 * @param bottom the bottom of the line
191 */
192 void draw(Canvas c, float x, int top, int y, int bottom) {
193 if (!mHasTabs) {
194 if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
Romain Guybc7cdb62011-05-26 18:48:49 -0700195 drawRun(c, 0, mLen, false, x, top, y, bottom, false);
Doug Felte8e45f22010-03-29 14:58:40 -0700196 return;
197 }
198 if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
Romain Guybc7cdb62011-05-26 18:48:49 -0700199 drawRun(c, 0, mLen, true, x, top, y, bottom, false);
Doug Felte8e45f22010-03-29 14:58:40 -0700200 return;
201 }
202 }
203
204 float h = 0;
205 int[] runs = mDirections.mDirections;
206 RectF emojiRect = null;
207
208 int lastRunIndex = runs.length - 2;
209 for (int i = 0; i < runs.length; i += 2) {
210 int runStart = runs[i];
211 int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
212 if (runLimit > mLen) {
213 runLimit = mLen;
214 }
215 boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
216
217 int segstart = runStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700218 for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
219 int codept = 0;
220 Bitmap bm = null;
221
222 if (mHasTabs && j < runLimit) {
223 codept = mChars[j];
224 if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) {
225 codept = Character.codePointAt(mChars, j);
226 if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) {
227 bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
228 } else if (codept > 0xffff) {
229 ++j;
230 continue;
231 }
232 }
233 }
234
235 if (j == runLimit || codept == '\t' || bm != null) {
Romain Guybc7cdb62011-05-26 18:48:49 -0700236 h += drawRun(c, segstart, j, runIsRtl, x+h, top, y, bottom,
Doug Felte8e45f22010-03-29 14:58:40 -0700237 i != lastRunIndex || j != mLen);
238
239 if (codept == '\t') {
240 h = mDir * nextTab(h * mDir);
241 } else if (bm != null) {
242 float bmAscent = ascent(j);
243 float bitmapHeight = bm.getHeight();
244 float scale = -bmAscent / bitmapHeight;
245 float width = bm.getWidth() * scale;
246
247 if (emojiRect == null) {
248 emojiRect = new RectF();
249 }
250 emojiRect.set(x + h, y + bmAscent,
251 x + h + width, y);
252 c.drawBitmap(bm, null, emojiRect, mPaint);
253 h += width;
254 j++;
255 }
256 segstart = j + 1;
257 }
258 }
259 }
260 }
261
262 /**
263 * Returns metrics information for the entire line.
264 *
265 * @param fmi receives font metrics information, can be null
266 * @return the signed width of the line
267 */
268 float metrics(FontMetricsInt fmi) {
269 return measure(mLen, false, fmi);
270 }
271
272 /**
273 * Returns information about a position on the line.
274 *
275 * @param offset the line-relative character offset, between 0 and the
276 * line length, inclusive
277 * @param trailing true to measure the trailing edge of the character
278 * before offset, false to measure the leading edge of the character
279 * at offset.
280 * @param fmi receives metrics information about the requested
281 * character, can be null.
282 * @return the signed offset from the leading margin to the requested
283 * character edge.
284 */
285 float measure(int offset, boolean trailing, FontMetricsInt fmi) {
286 int target = trailing ? offset - 1 : offset;
287 if (target < 0) {
288 return 0;
289 }
290
291 float h = 0;
292
293 if (!mHasTabs) {
294 if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
Romain Guybc7cdb62011-05-26 18:48:49 -0700295 return measureRun(0, offset, mLen, false, fmi);
Doug Felte8e45f22010-03-29 14:58:40 -0700296 }
297 if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
Romain Guybc7cdb62011-05-26 18:48:49 -0700298 return measureRun(0, offset, mLen, true, fmi);
Doug Felte8e45f22010-03-29 14:58:40 -0700299 }
300 }
301
302 char[] chars = mChars;
303 int[] runs = mDirections.mDirections;
304 for (int i = 0; i < runs.length; i += 2) {
305 int runStart = runs[i];
306 int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
307 if (runLimit > mLen) {
308 runLimit = mLen;
309 }
310 boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
311
312 int segstart = runStart;
313 for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
314 int codept = 0;
315 Bitmap bm = null;
316
317 if (mHasTabs && j < runLimit) {
318 codept = chars[j];
319 if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) {
320 codept = Character.codePointAt(chars, j);
321 if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) {
322 bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
323 } else if (codept > 0xffff) {
324 ++j;
325 continue;
326 }
327 }
328 }
329
330 if (j == runLimit || codept == '\t' || bm != null) {
331 boolean inSegment = target >= segstart && target < j;
332
333 boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
334 if (inSegment && advance) {
Romain Guybc7cdb62011-05-26 18:48:49 -0700335 return h += measureRun(segstart, offset, j, runIsRtl, fmi);
Doug Felte8e45f22010-03-29 14:58:40 -0700336 }
337
Romain Guybc7cdb62011-05-26 18:48:49 -0700338 float w = measureRun(segstart, j, j, runIsRtl, fmi);
Doug Felte8e45f22010-03-29 14:58:40 -0700339 h += advance ? w : -w;
340
341 if (inSegment) {
Romain Guybc7cdb62011-05-26 18:48:49 -0700342 return h += measureRun(segstart, offset, j, runIsRtl, null);
Doug Felte8e45f22010-03-29 14:58:40 -0700343 }
344
345 if (codept == '\t') {
346 if (offset == j) {
347 return h;
348 }
349 h = mDir * nextTab(h * mDir);
350 if (target == j) {
351 return h;
352 }
353 }
354
355 if (bm != null) {
356 float bmAscent = ascent(j);
357 float wid = bm.getWidth() * -bmAscent / bm.getHeight();
358 h += mDir * wid;
359 j++;
360 }
361
362 segstart = j + 1;
363 }
364 }
365 }
366
367 return h;
368 }
369
370 /**
371 * Draws a unidirectional (but possibly multi-styled) run of text.
372 *
Romain Guybc7cdb62011-05-26 18:48:49 -0700373 *
Doug Felte8e45f22010-03-29 14:58:40 -0700374 * @param c the canvas to draw on
Doug Felte8e45f22010-03-29 14:58:40 -0700375 * @param start the line-relative start
376 * @param limit the line-relative limit
377 * @param runIsRtl true if the run is right-to-left
378 * @param x the position of the run that is closest to the leading margin
379 * @param top the top of the line
380 * @param y the baseline
381 * @param bottom the bottom of the line
382 * @param needWidth true if the width value is required.
383 * @return the signed width of the run, based on the paragraph direction.
384 * Only valid if needWidth is true.
385 */
Romain Guybc7cdb62011-05-26 18:48:49 -0700386 private float drawRun(Canvas c, int start,
Doug Felte8e45f22010-03-29 14:58:40 -0700387 int limit, boolean runIsRtl, float x, int top, int y, int bottom,
388 boolean needWidth) {
389
390 if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
Romain Guybc7cdb62011-05-26 18:48:49 -0700391 float w = -measureRun(start, limit, limit, runIsRtl, null);
392 handleRun(start, limit, limit, runIsRtl, c, x + w, top,
Doug Felt0c702b82010-05-14 10:55:42 -0700393 y, bottom, null, false);
Doug Felte8e45f22010-03-29 14:58:40 -0700394 return w;
395 }
396
Romain Guybc7cdb62011-05-26 18:48:49 -0700397 return handleRun(start, limit, limit, runIsRtl, c, x, top,
Doug Felt0c702b82010-05-14 10:55:42 -0700398 y, bottom, null, needWidth);
Doug Felte8e45f22010-03-29 14:58:40 -0700399 }
400
401 /**
402 * Measures a unidirectional (but possibly multi-styled) run of text.
403 *
Romain Guybc7cdb62011-05-26 18:48:49 -0700404 *
Doug Felte8e45f22010-03-29 14:58:40 -0700405 * @param start the line-relative start of the run
406 * @param offset the offset to measure to, between start and limit inclusive
407 * @param limit the line-relative limit of the run
408 * @param runIsRtl true if the run is right-to-left
409 * @param fmi receives metrics information about the requested
410 * run, can be null.
411 * @return the signed width from the start of the run to the leading edge
412 * of the character at offset, based on the run (not paragraph) direction
413 */
Romain Guybc7cdb62011-05-26 18:48:49 -0700414 private float measureRun(int start, int offset, int limit, boolean runIsRtl,
415 FontMetricsInt fmi) {
416 return handleRun(start, offset, limit, runIsRtl, null, 0, 0, 0, 0, fmi, true);
Doug Felte8e45f22010-03-29 14:58:40 -0700417 }
418
419 /**
420 * Walk the cursor through this line, skipping conjuncts and
421 * zero-width characters.
422 *
423 * <p>This function cannot properly walk the cursor off the ends of the line
424 * since it does not know about any shaping on the previous/following line
425 * that might affect the cursor position. Callers must either avoid these
426 * situations or handle the result specially.
427 *
Doug Felte8e45f22010-03-29 14:58:40 -0700428 * @param cursor the starting position of the cursor, between 0 and the
429 * length of the line, inclusive
430 * @param toLeft true if the caret is moving to the left.
431 * @return the new offset. If it is less than 0 or greater than the length
432 * of the line, the previous/following line should be examined to get the
433 * actual offset.
434 */
435 int getOffsetToLeftRightOf(int cursor, boolean toLeft) {
436 // 1) The caret marks the leading edge of a character. The character
437 // logically before it might be on a different level, and the active caret
438 // position is on the character at the lower level. If that character
439 // was the previous character, the caret is on its trailing edge.
440 // 2) Take this character/edge and move it in the indicated direction.
441 // This gives you a new character and a new edge.
442 // 3) This position is between two visually adjacent characters. One of
443 // these might be at a lower level. The active position is on the
444 // character at the lower level.
445 // 4) If the active position is on the trailing edge of the character,
446 // the new caret position is the following logical character, else it
447 // is the character.
448
449 int lineStart = 0;
450 int lineEnd = mLen;
451 boolean paraIsRtl = mDir == -1;
452 int[] runs = mDirections.mDirections;
453
454 int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1;
455 boolean trailing = false;
456
457 if (cursor == lineStart) {
458 runIndex = -2;
459 } else if (cursor == lineEnd) {
460 runIndex = runs.length;
461 } else {
462 // First, get information about the run containing the character with
463 // the active caret.
464 for (runIndex = 0; runIndex < runs.length; runIndex += 2) {
465 runStart = lineStart + runs[runIndex];
466 if (cursor >= runStart) {
467 runLimit = runStart + (runs[runIndex+1] & Layout.RUN_LENGTH_MASK);
468 if (runLimit > lineEnd) {
469 runLimit = lineEnd;
470 }
471 if (cursor < runLimit) {
472 runLevel = (runs[runIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
473 Layout.RUN_LEVEL_MASK;
474 if (cursor == runStart) {
475 // The caret is on a run boundary, see if we should
476 // use the position on the trailing edge of the previous
477 // logical character instead.
478 int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit;
479 int pos = cursor - 1;
480 for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) {
481 prevRunStart = lineStart + runs[prevRunIndex];
482 if (pos >= prevRunStart) {
483 prevRunLimit = prevRunStart +
484 (runs[prevRunIndex+1] & Layout.RUN_LENGTH_MASK);
485 if (prevRunLimit > lineEnd) {
486 prevRunLimit = lineEnd;
487 }
488 if (pos < prevRunLimit) {
489 prevRunLevel = (runs[prevRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT)
490 & Layout.RUN_LEVEL_MASK;
491 if (prevRunLevel < runLevel) {
492 // Start from logically previous character.
493 runIndex = prevRunIndex;
494 runLevel = prevRunLevel;
495 runStart = prevRunStart;
496 runLimit = prevRunLimit;
497 trailing = true;
498 break;
499 }
500 }
501 }
502 }
503 }
504 break;
505 }
506 }
507 }
508
509 // caret might be == lineEnd. This is generally a space or paragraph
510 // separator and has an associated run, but might be the end of
511 // text, in which case it doesn't. If that happens, we ran off the
512 // end of the run list, and runIndex == runs.length. In this case,
513 // we are at a run boundary so we skip the below test.
514 if (runIndex != runs.length) {
515 boolean runIsRtl = (runLevel & 0x1) != 0;
516 boolean advance = toLeft == runIsRtl;
517 if (cursor != (advance ? runLimit : runStart) || advance != trailing) {
518 // Moving within or into the run, so we can move logically.
Doug Felt0c702b82010-05-14 10:55:42 -0700519 newCaret = getOffsetBeforeAfter(runIndex, runStart, runLimit,
520 runIsRtl, cursor, advance);
Doug Felte8e45f22010-03-29 14:58:40 -0700521 // If the new position is internal to the run, we're at the strong
522 // position already so we're finished.
523 if (newCaret != (advance ? runLimit : runStart)) {
524 return newCaret;
525 }
526 }
527 }
528 }
529
530 // If newCaret is -1, we're starting at a run boundary and crossing
531 // into another run. Otherwise we've arrived at a run boundary, and
532 // need to figure out which character to attach to. Note we might
533 // need to run this twice, if we cross a run boundary and end up at
534 // another run boundary.
535 while (true) {
536 boolean advance = toLeft == paraIsRtl;
537 int otherRunIndex = runIndex + (advance ? 2 : -2);
538 if (otherRunIndex >= 0 && otherRunIndex < runs.length) {
539 int otherRunStart = lineStart + runs[otherRunIndex];
540 int otherRunLimit = otherRunStart +
541 (runs[otherRunIndex+1] & Layout.RUN_LENGTH_MASK);
542 if (otherRunLimit > lineEnd) {
543 otherRunLimit = lineEnd;
544 }
545 int otherRunLevel = (runs[otherRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
546 Layout.RUN_LEVEL_MASK;
547 boolean otherRunIsRtl = (otherRunLevel & 1) != 0;
548
549 advance = toLeft == otherRunIsRtl;
550 if (newCaret == -1) {
Doug Felt0c702b82010-05-14 10:55:42 -0700551 newCaret = getOffsetBeforeAfter(otherRunIndex, otherRunStart,
552 otherRunLimit, otherRunIsRtl,
Doug Felte8e45f22010-03-29 14:58:40 -0700553 advance ? otherRunStart : otherRunLimit, advance);
554 if (newCaret == (advance ? otherRunLimit : otherRunStart)) {
555 // Crossed and ended up at a new boundary,
556 // repeat a second and final time.
557 runIndex = otherRunIndex;
558 runLevel = otherRunLevel;
559 continue;
560 }
561 break;
562 }
563
564 // The new caret is at a boundary.
565 if (otherRunLevel < runLevel) {
566 // The strong character is in the other run.
567 newCaret = advance ? otherRunStart : otherRunLimit;
568 }
569 break;
570 }
571
572 if (newCaret == -1) {
573 // We're walking off the end of the line. The paragraph
574 // level is always equal to or lower than any internal level, so
575 // the boundaries get the strong caret.
Doug Felt0c702b82010-05-14 10:55:42 -0700576 newCaret = advance ? mLen + 1 : -1;
Doug Felte8e45f22010-03-29 14:58:40 -0700577 break;
578 }
579
580 // Else we've arrived at the end of the line. That's a strong position.
581 // We might have arrived here by crossing over a run with no internal
582 // breaks and dropping out of the above loop before advancing one final
583 // time, so reset the caret.
584 // Note, we use '<=' below to handle a situation where the only run
585 // on the line is a counter-directional run. If we're not advancing,
586 // we can end up at the 'lineEnd' position but the caret we want is at
587 // the lineStart.
588 if (newCaret <= lineEnd) {
589 newCaret = advance ? lineEnd : lineStart;
590 }
591 break;
592 }
593
594 return newCaret;
595 }
596
597 /**
598 * Returns the next valid offset within this directional run, skipping
599 * conjuncts and zero-width characters. This should not be called to walk
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -0700600 * off the end of the line, since the returned values might not be valid
Doug Felt0c702b82010-05-14 10:55:42 -0700601 * on neighboring lines. If the returned offset is less than zero or
602 * greater than the line length, the offset should be recomputed on the
603 * preceding or following line, respectively.
Doug Felte8e45f22010-03-29 14:58:40 -0700604 *
605 * @param runIndex the run index
Doug Felt0c702b82010-05-14 10:55:42 -0700606 * @param runStart the start of the run
607 * @param runLimit the limit of the run
608 * @param runIsRtl true if the run is right-to-left
Doug Felte8e45f22010-03-29 14:58:40 -0700609 * @param offset the offset
610 * @param after true if the new offset should logically follow the provided
611 * offset
612 * @return the new offset
613 */
Doug Felt0c702b82010-05-14 10:55:42 -0700614 private int getOffsetBeforeAfter(int runIndex, int runStart, int runLimit,
615 boolean runIsRtl, int offset, boolean after) {
Doug Felte8e45f22010-03-29 14:58:40 -0700616
Doug Felt0c702b82010-05-14 10:55:42 -0700617 if (runIndex < 0 || offset == (after ? mLen : 0)) {
618 // Walking off end of line. Since we don't know
619 // what cursor positions are available on other lines, we can't
620 // return accurate values. These are a guess.
Doug Felte8e45f22010-03-29 14:58:40 -0700621 if (after) {
Doug Felt0c702b82010-05-14 10:55:42 -0700622 return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart;
623 }
624 return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart;
625 }
626
627 TextPaint wp = mWorkPaint;
628 wp.set(mPaint);
629
630 int spanStart = runStart;
631 int spanLimit;
632 if (mSpanned == null) {
633 spanLimit = runLimit;
634 } else {
635 int target = after ? offset + 1 : offset;
636 int limit = mStart + runLimit;
637 while (true) {
638 spanLimit = mSpanned.nextSpanTransition(mStart + spanStart, limit,
639 MetricAffectingSpan.class) - mStart;
640 if (spanLimit >= target) {
641 break;
Doug Felte8e45f22010-03-29 14:58:40 -0700642 }
Doug Felt0c702b82010-05-14 10:55:42 -0700643 spanStart = spanLimit;
644 }
645
646 MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart,
647 mStart + spanLimit, MetricAffectingSpan.class);
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800648 spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
Doug Felt0c702b82010-05-14 10:55:42 -0700649
650 if (spans.length > 0) {
651 ReplacementSpan replacement = null;
652 for (int j = 0; j < spans.length; j++) {
653 MetricAffectingSpan span = spans[j];
654 if (span instanceof ReplacementSpan) {
655 replacement = (ReplacementSpan)span;
656 } else {
657 span.updateMeasureState(wp);
658 }
659 }
660
661 if (replacement != null) {
662 // If we have a replacement span, we're moving either to
663 // the start or end of this span.
664 return after ? spanLimit : spanStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700665 }
666 }
Doug Felte8e45f22010-03-29 14:58:40 -0700667 }
668
Doug Felt0c702b82010-05-14 10:55:42 -0700669 int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
670 int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
671 if (mCharsValid) {
672 return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart,
673 flags, offset, cursorOpt);
674 } else {
675 return wp.getTextRunCursor(mText, mStart + spanStart,
Gilles Debunne345cb032010-06-16 17:13:23 -0700676 mStart + spanLimit, flags, mStart + offset, cursorOpt) - mStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700677 }
Doug Felte8e45f22010-03-29 14:58:40 -0700678 }
679
680 /**
Gilles Debunne0bb00092010-12-02 15:50:26 -0800681 * @param wp
682 */
683 private static void expandMetricsFromPaint(FontMetricsInt fmi, TextPaint wp) {
684 final int previousTop = fmi.top;
685 final int previousAscent = fmi.ascent;
686 final int previousDescent = fmi.descent;
687 final int previousBottom = fmi.bottom;
688 final int previousLeading = fmi.leading;
689
690 wp.getFontMetricsInt(fmi);
691
Fabrice Di Meglio8a5137a2011-09-22 18:49:43 -0700692 updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom,
693 previousLeading);
694 }
695
696 static void updateMetrics(FontMetricsInt fmi, int previousTop, int previousAscent,
697 int previousDescent, int previousBottom, int previousLeading) {
Gilles Debunne0bb00092010-12-02 15:50:26 -0800698 fmi.top = Math.min(fmi.top, previousTop);
699 fmi.ascent = Math.min(fmi.ascent, previousAscent);
700 fmi.descent = Math.max(fmi.descent, previousDescent);
701 fmi.bottom = Math.max(fmi.bottom, previousBottom);
702 fmi.leading = Math.max(fmi.leading, previousLeading);
703 }
704
705 /**
Doug Felte8e45f22010-03-29 14:58:40 -0700706 * Utility function for measuring and rendering text. The text must
707 * not include a tab or emoji.
708 *
709 * @param wp the working paint
710 * @param start the start of the text
Doug Felt0c702b82010-05-14 10:55:42 -0700711 * @param end the end of the text
Doug Felte8e45f22010-03-29 14:58:40 -0700712 * @param runIsRtl true if the run is right-to-left
713 * @param c the canvas, can be null if rendering is not needed
714 * @param x the edge of the run closest to the leading margin
715 * @param top the top of the line
716 * @param y the baseline
717 * @param bottom the bottom of the line
718 * @param fmi receives metrics information, can be null
719 * @param needWidth true if the width of the run is needed
720 * @return the signed width of the run based on the run direction; only
721 * valid if needWidth is true
722 */
Doug Felt0c702b82010-05-14 10:55:42 -0700723 private float handleText(TextPaint wp, int start, int end,
724 int contextStart, int contextEnd, boolean runIsRtl,
725 Canvas c, float x, int top, int y, int bottom,
Doug Felte8e45f22010-03-29 14:58:40 -0700726 FontMetricsInt fmi, boolean needWidth) {
727
Fabrice Di Meglio850dffa2011-08-08 09:45:09 -0700728 // Get metrics first (even for empty strings or "0" width runs)
729 if (fmi != null) {
730 expandMetricsFromPaint(fmi, wp);
731 }
Doug Felte8e45f22010-03-29 14:58:40 -0700732
Doug Felt0c702b82010-05-14 10:55:42 -0700733 int runLen = end - start;
Fabrice Di Meglio850dffa2011-08-08 09:45:09 -0700734 // No need to do anything if the run width is "0"
735 if (runLen == 0) {
736 return 0f;
737 }
738
739 float ret = 0;
740
Doug Felt0c702b82010-05-14 10:55:42 -0700741 int contextLen = contextEnd - contextStart;
Gilles Debunnec9fd9782011-09-09 09:20:12 -0700742 if (needWidth || (c != null && (wp.bgColor != 0 || wp.underlineColor != 0 || runIsRtl))) {
Doug Felt0c702b82010-05-14 10:55:42 -0700743 int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
Doug Felte8e45f22010-03-29 14:58:40 -0700744 if (mCharsValid) {
Doug Felt0c702b82010-05-14 10:55:42 -0700745 ret = wp.getTextRunAdvances(mChars, start, runLen,
746 contextStart, contextLen, flags, null, 0);
Doug Felte8e45f22010-03-29 14:58:40 -0700747 } else {
Doug Felt0c702b82010-05-14 10:55:42 -0700748 int delta = mStart;
749 ret = wp.getTextRunAdvances(mText, delta + start,
750 delta + end, delta + contextStart, delta + contextEnd,
751 flags, null, 0);
Doug Felte8e45f22010-03-29 14:58:40 -0700752 }
753 }
754
Doug Felte8e45f22010-03-29 14:58:40 -0700755 if (c != null) {
756 if (runIsRtl) {
757 x -= ret;
758 }
759
760 if (wp.bgColor != 0) {
Gilles Debunnedd8f5ed2011-08-10 18:25:49 -0700761 int previousColor = wp.getColor();
762 Paint.Style previousStyle = wp.getStyle();
763
Doug Felte8e45f22010-03-29 14:58:40 -0700764 wp.setColor(wp.bgColor);
765 wp.setStyle(Paint.Style.FILL);
Doug Felte8e45f22010-03-29 14:58:40 -0700766 c.drawRect(x, top, x + ret, bottom, wp);
767
Gilles Debunnedd8f5ed2011-08-10 18:25:49 -0700768 wp.setStyle(previousStyle);
769 wp.setColor(previousColor);
770 }
771
Gilles Debunnec9fd9782011-09-09 09:20:12 -0700772 if (wp.underlineColor != 0) {
Gilles Debunnedd8f5ed2011-08-10 18:25:49 -0700773 // kStdUnderline_Offset = 1/9, defined in SkTextFormatParams.h
Luca Zanoline6d36822011-08-30 18:04:34 +0100774 float underlineTop = y + wp.baselineShift + (1.0f / 9.0f) * wp.getTextSize();
Gilles Debunnedd8f5ed2011-08-10 18:25:49 -0700775
776 int previousColor = wp.getColor();
777 Paint.Style previousStyle = wp.getStyle();
Luca Zanoline6d36822011-08-30 18:04:34 +0100778 boolean previousAntiAlias = wp.isAntiAlias();
Gilles Debunnedd8f5ed2011-08-10 18:25:49 -0700779
Gilles Debunnedd8f5ed2011-08-10 18:25:49 -0700780 wp.setStyle(Paint.Style.FILL);
Luca Zanoline6d36822011-08-30 18:04:34 +0100781 wp.setAntiAlias(true);
782
Gilles Debunnec9fd9782011-09-09 09:20:12 -0700783 wp.setColor(wp.underlineColor);
784 c.drawRect(x, underlineTop, x + ret, underlineTop + wp.underlineThickness, wp);
Gilles Debunnedd8f5ed2011-08-10 18:25:49 -0700785
786 wp.setStyle(previousStyle);
787 wp.setColor(previousColor);
Luca Zanoline6d36822011-08-30 18:04:34 +0100788 wp.setAntiAlias(previousAntiAlias);
Doug Felte8e45f22010-03-29 14:58:40 -0700789 }
790
Doug Felt0c702b82010-05-14 10:55:42 -0700791 drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
792 x, y + wp.baselineShift);
Doug Felte8e45f22010-03-29 14:58:40 -0700793 }
794
795 return runIsRtl ? -ret : ret;
796 }
797
798 /**
799 * Utility function for measuring and rendering a replacement.
800 *
Romain Guybc7cdb62011-05-26 18:48:49 -0700801 *
Doug Felte8e45f22010-03-29 14:58:40 -0700802 * @param replacement the replacement
803 * @param wp the work paint
Doug Felte8e45f22010-03-29 14:58:40 -0700804 * @param start the start of the run
805 * @param limit the limit of the run
806 * @param runIsRtl true if the run is right-to-left
807 * @param c the canvas, can be null if not rendering
808 * @param x the edge of the replacement closest to the leading margin
809 * @param top the top of the line
810 * @param y the baseline
811 * @param bottom the bottom of the line
812 * @param fmi receives metrics information, can be null
813 * @param needWidth true if the width of the replacement is needed
Doug Felte8e45f22010-03-29 14:58:40 -0700814 * @return the signed width of the run based on the run direction; only
815 * valid if needWidth is true
816 */
817 private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
Romain Guybc7cdb62011-05-26 18:48:49 -0700818 int start, int limit, boolean runIsRtl, Canvas c,
Doug Felte8e45f22010-03-29 14:58:40 -0700819 float x, int top, int y, int bottom, FontMetricsInt fmi,
Doug Felt0c702b82010-05-14 10:55:42 -0700820 boolean needWidth) {
Doug Felte8e45f22010-03-29 14:58:40 -0700821
822 float ret = 0;
823
Doug Felt0c702b82010-05-14 10:55:42 -0700824 int textStart = mStart + start;
825 int textLimit = mStart + limit;
826
827 if (needWidth || (c != null && runIsRtl)) {
Fabrice Di Meglio8a5137a2011-09-22 18:49:43 -0700828 int previousTop = 0;
829 int previousAscent = 0;
830 int previousDescent = 0;
831 int previousBottom = 0;
832 int previousLeading = 0;
833
834 boolean needUpdateMetrics = (fmi != null);
835
836 if (needUpdateMetrics) {
837 previousTop = fmi.top;
838 previousAscent = fmi.ascent;
839 previousDescent = fmi.descent;
840 previousBottom = fmi.bottom;
841 previousLeading = fmi.leading;
842 }
843
Doug Felt0c702b82010-05-14 10:55:42 -0700844 ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
Fabrice Di Meglio8a5137a2011-09-22 18:49:43 -0700845
846 if (needUpdateMetrics) {
847 updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom,
848 previousLeading);
849 }
Doug Felte8e45f22010-03-29 14:58:40 -0700850 }
851
Doug Felt0c702b82010-05-14 10:55:42 -0700852 if (c != null) {
853 if (runIsRtl) {
854 x -= ret;
Doug Felte8e45f22010-03-29 14:58:40 -0700855 }
Doug Felt0c702b82010-05-14 10:55:42 -0700856 replacement.draw(c, mText, textStart, textLimit,
857 x, top, y, bottom, wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700858 }
859
860 return runIsRtl ? -ret : ret;
861 }
862
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700863 private static class SpanSet<E> {
Gilles Debunnec1f44832011-12-08 16:03:00 -0800864 int numberOfSpans;
865 E[] spans;
866 int[] spanStarts;
867 int[] spanEnds;
868 int[] spanFlags;
869 final Class<? extends E> classType;
870
871 SpanSet(Class<? extends E> type) {
872 classType = type;
873 numberOfSpans = 0;
874 }
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700875
876 @SuppressWarnings("unchecked")
Gilles Debunnec1f44832011-12-08 16:03:00 -0800877 public void init(Spanned spanned, int start, int limit) {
878 final E[] allSpans = spanned.getSpans(start, limit, classType);
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700879 final int length = allSpans.length;
Gilles Debunnec1f44832011-12-08 16:03:00 -0800880
881 if (length > 0 && (spans == null || spans.length < length)) {
882 // These arrays may end up being too large because of empty spans
883 spans = (E[]) Array.newInstance(classType, length);
884 spanStarts = new int[length];
885 spanEnds = new int[length];
886 spanFlags = new int[length];
887 }
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700888
Gilles Debunnefc997b42011-12-13 12:37:00 -0800889 numberOfSpans = 0;
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700890 for (int i = 0; i < length; i++) {
891 final E span = allSpans[i];
892
893 final int spanStart = spanned.getSpanStart(span);
894 final int spanEnd = spanned.getSpanEnd(span);
895 if (spanStart == spanEnd) continue;
896
897 final int spanFlag = spanned.getSpanFlags(span);
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700898
Gilles Debunnefc997b42011-12-13 12:37:00 -0800899 spans[numberOfSpans] = span;
900 spanStarts[numberOfSpans] = spanStart;
901 spanEnds[numberOfSpans] = spanEnd;
902 spanFlags[numberOfSpans] = spanFlag;
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700903
Gilles Debunnefc997b42011-12-13 12:37:00 -0800904 numberOfSpans++;
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700905 }
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700906 }
907
Gilles Debunne8a439ac72011-10-26 16:41:28 -0700908 public boolean hasSpansIntersecting(int start, int end) {
909 for (int i = 0; i < numberOfSpans; i++) {
910 // equal test is valid since both intervals are not empty by construction
911 if (spanStarts[i] >= end || spanEnds[i] <= start) continue;
912 return true;
913 }
914 return false;
915 }
916
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700917 int getNextTransition(int start, int limit) {
918 for (int i = 0; i < numberOfSpans; i++) {
919 final int spanStart = spanStarts[i];
920 final int spanEnd = spanEnds[i];
921 if (spanStart > start && spanStart < limit) limit = spanStart;
922 if (spanEnd > start && spanEnd < limit) limit = spanEnd;
923 }
924 return limit;
925 }
Gilles Debunnec3fb7a12011-12-12 14:03:40 -0800926
927 public void recycle() {
Gilles Debunnefc997b42011-12-13 12:37:00 -0800928 // The spans array is guaranteed to be not null when numberOfSpans is > 0
Gilles Debunnec3fb7a12011-12-12 14:03:40 -0800929 for (int i = 0; i < numberOfSpans; i++) {
930 spans[i] = null; // prevent a leak: no reference kept when TextLine is recycled
931 }
932 }
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700933 }
934
Doug Felte8e45f22010-03-29 14:58:40 -0700935 /**
936 * Utility function for handling a unidirectional run. The run must not
937 * contain tabs or emoji but can contain styles.
938 *
Romain Guybc7cdb62011-05-26 18:48:49 -0700939 *
Doug Felte8e45f22010-03-29 14:58:40 -0700940 * @param start the line-relative start of the run
Doug Felt0c702b82010-05-14 10:55:42 -0700941 * @param measureLimit the offset to measure to, between start and limit inclusive
Doug Felte8e45f22010-03-29 14:58:40 -0700942 * @param limit the limit of the run
943 * @param runIsRtl true if the run is right-to-left
944 * @param c the canvas, can be null
945 * @param x the end of the run closest to the leading margin
946 * @param top the top of the line
947 * @param y the baseline
948 * @param bottom the bottom of the line
949 * @param fmi receives metrics information, can be null
950 * @param needWidth true if the width is required
Doug Felte8e45f22010-03-29 14:58:40 -0700951 * @return the signed width of the run based on the run direction; only
952 * valid if needWidth is true
953 */
Romain Guybc7cdb62011-05-26 18:48:49 -0700954 private float handleRun(int start, int measureLimit,
Doug Felte8e45f22010-03-29 14:58:40 -0700955 int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
Doug Felt0c702b82010-05-14 10:55:42 -0700956 int bottom, FontMetricsInt fmi, boolean needWidth) {
Doug Felte8e45f22010-03-29 14:58:40 -0700957
Gilles Debunnef483e512011-04-28 15:08:54 -0700958 // Case of an empty line, make sure we update fmi according to mPaint
959 if (start == measureLimit) {
960 TextPaint wp = mWorkPaint;
961 wp.set(mPaint);
Fabrice Di Meglio15c097a2011-08-08 14:42:41 -0700962 if (fmi != null) {
963 expandMetricsFromPaint(fmi, wp);
964 }
965 return 0f;
Gilles Debunnef483e512011-04-28 15:08:54 -0700966 }
967
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700968 if (mSpanned == null) {
969 TextPaint wp = mWorkPaint;
970 wp.set(mPaint);
971 final int mlimit = measureLimit;
972 return handleText(wp, start, mlimit, start, limit, runIsRtl, c, x, top,
973 y, bottom, fmi, needWidth || mlimit < measureLimit);
974 }
975
Gilles Debunnec1f44832011-12-08 16:03:00 -0800976 mMetricAffectingSpanSpanSet.init(mSpanned, mStart + start, mStart + limit);
977 mCharacterStyleSpanSet.init(mSpanned, mStart + start, mStart + limit);
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700978
Doug Felte8e45f22010-03-29 14:58:40 -0700979 // Shaping needs to take into account context up to metric boundaries,
980 // but rendering needs to take into account character style boundaries.
Doug Felt0c702b82010-05-14 10:55:42 -0700981 // So we iterate through metric runs to get metric bounds,
982 // then within each metric run iterate through character style runs
983 // for the run bounds.
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700984 final float originalX = x;
Doug Felt0c702b82010-05-14 10:55:42 -0700985 for (int i = start, inext; i < measureLimit; i = inext) {
Doug Felte8e45f22010-03-29 14:58:40 -0700986 TextPaint wp = mWorkPaint;
987 wp.set(mPaint);
988
Gilles Debunnec1f44832011-12-08 16:03:00 -0800989 inext = mMetricAffectingSpanSpanSet.getNextTransition(mStart + i, mStart + limit) -
990 mStart;
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700991 int mlimit = Math.min(inext, measureLimit);
Doug Felte8e45f22010-03-29 14:58:40 -0700992
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700993 ReplacementSpan replacement = null;
Doug Felte8e45f22010-03-29 14:58:40 -0700994
Gilles Debunnec1f44832011-12-08 16:03:00 -0800995 for (int j = 0; j < mMetricAffectingSpanSpanSet.numberOfSpans; j++) {
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700996 // Both intervals [spanStarts..spanEnds] and [mStart + i..mStart + mlimit] are NOT
997 // empty by construction. This special case in getSpans() explains the >= & <= tests
Gilles Debunnec1f44832011-12-08 16:03:00 -0800998 if ((mMetricAffectingSpanSpanSet.spanStarts[j] >= mStart + mlimit) ||
999 (mMetricAffectingSpanSpanSet.spanEnds[j] <= mStart + i)) continue;
1000 MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j];
Gilles Debunne945ee9b2011-09-19 19:18:18 -07001001 if (span instanceof ReplacementSpan) {
1002 replacement = (ReplacementSpan)span;
1003 } else {
1004 // We might have a replacement that uses the draw
1005 // state, otherwise measure state would suffice.
1006 span.updateDrawState(wp);
Doug Felte8e45f22010-03-29 14:58:40 -07001007 }
1008 }
1009
Gilles Debunne945ee9b2011-09-19 19:18:18 -07001010 if (replacement != null) {
1011 x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y,
1012 bottom, fmi, needWidth || mlimit < measureLimit);
1013 continue;
1014 }
1015
1016 if (c == null) {
Doug Felt0c702b82010-05-14 10:55:42 -07001017 x += handleText(wp, i, mlimit, i, inext, runIsRtl, c, x, top,
1018 y, bottom, fmi, needWidth || mlimit < measureLimit);
1019 } else {
1020 for (int j = i, jnext; j < mlimit; j = jnext) {
Gilles Debunnec1f44832011-12-08 16:03:00 -08001021 jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + mlimit) -
Gilles Debunne945ee9b2011-09-19 19:18:18 -07001022 mStart;
Doug Felte8e45f22010-03-29 14:58:40 -07001023
Doug Felt0c702b82010-05-14 10:55:42 -07001024 wp.set(mPaint);
Gilles Debunnec1f44832011-12-08 16:03:00 -08001025 for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
Gilles Debunne945ee9b2011-09-19 19:18:18 -07001026 // Intentionally using >= and <= as explained above
Gilles Debunnec1f44832011-12-08 16:03:00 -08001027 if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + jnext) ||
1028 (mCharacterStyleSpanSet.spanEnds[k] <= mStart + j)) continue;
Gilles Debunne945ee9b2011-09-19 19:18:18 -07001029
Gilles Debunnec1f44832011-12-08 16:03:00 -08001030 CharacterStyle span = mCharacterStyleSpanSet.spans[k];
Doug Felt0c702b82010-05-14 10:55:42 -07001031 span.updateDrawState(wp);
Doug Felte8e45f22010-03-29 14:58:40 -07001032 }
Doug Felt0c702b82010-05-14 10:55:42 -07001033
1034 x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
1035 top, y, bottom, fmi, needWidth || jnext < measureLimit);
Doug Felte8e45f22010-03-29 14:58:40 -07001036 }
1037 }
1038 }
1039
Gilles Debunne945ee9b2011-09-19 19:18:18 -07001040 return x - originalX;
Doug Felte8e45f22010-03-29 14:58:40 -07001041 }
1042
Doug Felte8e45f22010-03-29 14:58:40 -07001043 /**
1044 * Render a text run with the set-up paint.
1045 *
1046 * @param c the canvas
1047 * @param wp the paint used to render the text
Doug Felt0c702b82010-05-14 10:55:42 -07001048 * @param start the start of the run
1049 * @param end the end of the run
1050 * @param contextStart the start of context for the run
1051 * @param contextEnd the end of the context for the run
Doug Felte8e45f22010-03-29 14:58:40 -07001052 * @param runIsRtl true if the run is right-to-left
1053 * @param x the x position of the left edge of the run
1054 * @param y the baseline of the run
1055 */
Doug Felt0c702b82010-05-14 10:55:42 -07001056 private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
1057 int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
Doug Felte8e45f22010-03-29 14:58:40 -07001058
Doug Feltf47d7402010-04-21 16:01:52 -07001059 int flags = runIsRtl ? Canvas.DIRECTION_RTL : Canvas.DIRECTION_LTR;
Doug Felte8e45f22010-03-29 14:58:40 -07001060 if (mCharsValid) {
Doug Felt0c702b82010-05-14 10:55:42 -07001061 int count = end - start;
1062 int contextCount = contextEnd - contextStart;
1063 c.drawTextRun(mChars, start, count, contextStart, contextCount,
1064 x, y, flags, wp);
Doug Felte8e45f22010-03-29 14:58:40 -07001065 } else {
Doug Felt0c702b82010-05-14 10:55:42 -07001066 int delta = mStart;
1067 c.drawTextRun(mText, delta + start, delta + end,
1068 delta + contextStart, delta + contextEnd, x, y, flags, wp);
Doug Felte8e45f22010-03-29 14:58:40 -07001069 }
1070 }
1071
1072 /**
1073 * Returns the ascent of the text at start. This is used for scaling
1074 * emoji.
1075 *
1076 * @param pos the line-relative position
1077 * @return the ascent of the text at start
1078 */
1079 float ascent(int pos) {
1080 if (mSpanned == null) {
1081 return mPaint.ascent();
1082 }
1083
1084 pos += mStart;
Gilles Debunne945ee9b2011-09-19 19:18:18 -07001085 MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1, MetricAffectingSpan.class);
Doug Felte8e45f22010-03-29 14:58:40 -07001086 if (spans.length == 0) {
1087 return mPaint.ascent();
1088 }
1089
1090 TextPaint wp = mWorkPaint;
1091 wp.set(mPaint);
1092 for (MetricAffectingSpan span : spans) {
1093 span.updateMeasureState(wp);
1094 }
1095 return wp.ascent();
1096 }
1097
1098 /**
1099 * Returns the next tab position.
1100 *
1101 * @param h the (unsigned) offset from the leading margin
1102 * @return the (unsigned) tab position after this offset
1103 */
1104 float nextTab(float h) {
Doug Feltc982f602010-05-25 11:51:40 -07001105 if (mTabs != null) {
1106 return mTabs.nextTab(h);
Doug Felte8e45f22010-03-29 14:58:40 -07001107 }
Doug Feltc982f602010-05-25 11:51:40 -07001108 return TabStops.nextDefaultStop(h, TAB_INCREMENT);
Doug Felte8e45f22010-03-29 14:58:40 -07001109 }
1110
1111 private static final int TAB_INCREMENT = 20;
1112}