blob: 0e3522e7a0d6444d5876d8ae7c38c26ccdb0a78e [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
19import com.android.internal.util.ArrayUtils;
20
21import android.graphics.Bitmap;
22import android.graphics.Canvas;
23import android.graphics.Paint;
Doug Felte8e45f22010-03-29 14:58:40 -070024import android.graphics.Paint.FontMetricsInt;
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -070025import android.graphics.RectF;
Doug Felte8e45f22010-03-29 14:58:40 -070026import android.text.Layout.Directions;
Doug Feltc982f602010-05-25 11:51:40 -070027import android.text.Layout.TabStops;
Doug Felte8e45f22010-03-29 14:58:40 -070028import android.text.style.CharacterStyle;
29import android.text.style.MetricAffectingSpan;
30import android.text.style.ReplacementSpan;
Doug Felte8e45f22010-03-29 14:58:40 -070031import android.util.Log;
32
33/**
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 {
46 private TextPaint mPaint;
47 private CharSequence mText;
48 private int mStart;
49 private int mLen;
50 private int mDir;
51 private Directions mDirections;
52 private boolean mHasTabs;
Doug Feltc982f602010-05-25 11:51:40 -070053 private TabStops mTabs;
Doug Felte8e45f22010-03-29 14:58:40 -070054 private char[] mChars;
55 private boolean mCharsValid;
56 private Spanned mSpanned;
Gilles Debunne345cb032010-06-16 17:13:23 -070057 private final TextPaint mWorkPaint = new TextPaint();
Doug Felte8e45f22010-03-29 14:58:40 -070058
59 private static TextLine[] cached = new TextLine[3];
60
61 /**
62 * Returns a new TextLine from the shared pool.
63 *
64 * @return an uninitialized TextLine
65 */
66 static TextLine obtain() {
67 TextLine tl;
68 synchronized (cached) {
69 for (int i = cached.length; --i >= 0;) {
70 if (cached[i] != null) {
71 tl = cached[i];
72 cached[i] = null;
73 return tl;
74 }
75 }
76 }
77 tl = new TextLine();
78 Log.e("TLINE", "new: " + tl);
79 return tl;
80 }
81
82 /**
83 * Puts a TextLine back into the shared pool. Do not use this TextLine once
84 * it has been returned.
85 * @param tl the textLine
86 * @return null, as a convenience from clearing references to the provided
87 * TextLine
88 */
89 static TextLine recycle(TextLine tl) {
90 tl.mText = null;
91 tl.mPaint = null;
92 tl.mDirections = null;
93 if (tl.mLen < 250) {
94 synchronized(cached) {
95 for (int i = 0; i < cached.length; ++i) {
96 if (cached[i] == null) {
97 cached[i] = tl;
98 break;
99 }
100 }
101 }
102 }
103 return null;
104 }
105
106 /**
107 * Initializes a TextLine and prepares it for use.
108 *
109 * @param paint the base paint for the line
110 * @param text the text, can be Styled
111 * @param start the start of the line relative to the text
112 * @param limit the limit of the line relative to the text
113 * @param dir the paragraph direction of this line
114 * @param directions the directions information of this line
115 * @param hasTabs true if the line might contain tabs or emoji
Doug Feltc982f602010-05-25 11:51:40 -0700116 * @param tabStops the tabStops. Can be null.
Doug Felte8e45f22010-03-29 14:58:40 -0700117 */
118 void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
Doug Feltc982f602010-05-25 11:51:40 -0700119 Directions directions, boolean hasTabs, TabStops tabStops) {
Doug Felte8e45f22010-03-29 14:58:40 -0700120 mPaint = paint;
121 mText = text;
122 mStart = start;
123 mLen = limit - start;
124 mDir = dir;
125 mDirections = directions;
126 mHasTabs = hasTabs;
127 mSpanned = null;
Doug Felte8e45f22010-03-29 14:58:40 -0700128
129 boolean hasReplacement = false;
130 if (text instanceof Spanned) {
131 mSpanned = (Spanned) text;
132 hasReplacement = mSpanned.getSpans(start, limit,
133 ReplacementSpan.class).length > 0;
134 }
135
136 mCharsValid = hasReplacement || hasTabs ||
137 directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
138
139 if (mCharsValid) {
140 if (mChars == null || mChars.length < mLen) {
141 mChars = new char[ArrayUtils.idealCharArraySize(mLen)];
142 }
143 TextUtils.getChars(text, start, limit, mChars, 0);
Doug Felt0c702b82010-05-14 10:55:42 -0700144 if (hasReplacement) {
145 // Handle these all at once so we don't have to do it as we go.
146 // Replace the first character of each replacement run with the
147 // object-replacement character and the remainder with zero width
148 // non-break space aka BOM. Cursor movement code skips these
149 // zero-width characters.
150 char[] chars = mChars;
151 for (int i = start, inext; i < limit; i = inext) {
152 inext = mSpanned.nextSpanTransition(i, limit,
153 ReplacementSpan.class);
154 if (mSpanned.getSpans(i, inext, ReplacementSpan.class)
155 .length > 0) { // transition into a span
156 chars[i - start] = '\ufffc';
157 for (int j = i - start + 1, e = inext - start; j < e; ++j) {
158 chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip
159 }
160 }
161 }
162 }
Doug Felte8e45f22010-03-29 14:58:40 -0700163 }
Doug Feltc982f602010-05-25 11:51:40 -0700164 mTabs = tabStops;
Doug Felte8e45f22010-03-29 14:58:40 -0700165 }
166
167 /**
168 * Renders the TextLine.
169 *
170 * @param c the canvas to render on
171 * @param x the leading margin position
172 * @param top the top of the line
173 * @param y the baseline
174 * @param bottom the bottom of the line
175 */
176 void draw(Canvas c, float x, int top, int y, int bottom) {
177 if (!mHasTabs) {
178 if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
179 drawRun(c, 0, 0, mLen, false, x, top, y, bottom, false);
180 return;
181 }
182 if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
183 drawRun(c, 0, 0, mLen, true, x, top, y, bottom, false);
184 return;
185 }
186 }
187
188 float h = 0;
189 int[] runs = mDirections.mDirections;
190 RectF emojiRect = null;
191
192 int lastRunIndex = runs.length - 2;
193 for (int i = 0; i < runs.length; i += 2) {
194 int runStart = runs[i];
195 int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
196 if (runLimit > mLen) {
197 runLimit = mLen;
198 }
199 boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
200
201 int segstart = runStart;
202 char[] chars = mChars;
203 for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
204 int codept = 0;
205 Bitmap bm = null;
206
207 if (mHasTabs && j < runLimit) {
208 codept = mChars[j];
209 if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) {
210 codept = Character.codePointAt(mChars, j);
211 if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) {
212 bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
213 } else if (codept > 0xffff) {
214 ++j;
215 continue;
216 }
217 }
218 }
219
220 if (j == runLimit || codept == '\t' || bm != null) {
221 h += drawRun(c, i, segstart, j, runIsRtl, x+h, top, y, bottom,
222 i != lastRunIndex || j != mLen);
223
224 if (codept == '\t') {
225 h = mDir * nextTab(h * mDir);
226 } else if (bm != null) {
227 float bmAscent = ascent(j);
228 float bitmapHeight = bm.getHeight();
229 float scale = -bmAscent / bitmapHeight;
230 float width = bm.getWidth() * scale;
231
232 if (emojiRect == null) {
233 emojiRect = new RectF();
234 }
235 emojiRect.set(x + h, y + bmAscent,
236 x + h + width, y);
237 c.drawBitmap(bm, null, emojiRect, mPaint);
238 h += width;
239 j++;
240 }
241 segstart = j + 1;
242 }
243 }
244 }
245 }
246
247 /**
248 * Returns metrics information for the entire line.
249 *
250 * @param fmi receives font metrics information, can be null
251 * @return the signed width of the line
252 */
253 float metrics(FontMetricsInt fmi) {
254 return measure(mLen, false, fmi);
255 }
256
257 /**
258 * Returns information about a position on the line.
259 *
260 * @param offset the line-relative character offset, between 0 and the
261 * line length, inclusive
262 * @param trailing true to measure the trailing edge of the character
263 * before offset, false to measure the leading edge of the character
264 * at offset.
265 * @param fmi receives metrics information about the requested
266 * character, can be null.
267 * @return the signed offset from the leading margin to the requested
268 * character edge.
269 */
270 float measure(int offset, boolean trailing, FontMetricsInt fmi) {
271 int target = trailing ? offset - 1 : offset;
272 if (target < 0) {
273 return 0;
274 }
275
276 float h = 0;
277
278 if (!mHasTabs) {
279 if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
Doug Felt0c702b82010-05-14 10:55:42 -0700280 return measureRun(0, 0, offset, mLen, false, fmi);
Doug Felte8e45f22010-03-29 14:58:40 -0700281 }
282 if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
Doug Felt0b9d2ca2010-05-06 17:10:54 -0700283 return measureRun(0, 0, offset, mLen, true, fmi);
Doug Felte8e45f22010-03-29 14:58:40 -0700284 }
285 }
286
287 char[] chars = mChars;
288 int[] runs = mDirections.mDirections;
289 for (int i = 0; i < runs.length; i += 2) {
290 int runStart = runs[i];
291 int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
292 if (runLimit > mLen) {
293 runLimit = mLen;
294 }
295 boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
296
297 int segstart = runStart;
298 for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
299 int codept = 0;
300 Bitmap bm = null;
301
302 if (mHasTabs && j < runLimit) {
303 codept = chars[j];
304 if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) {
305 codept = Character.codePointAt(chars, j);
306 if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) {
307 bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
308 } else if (codept > 0xffff) {
309 ++j;
310 continue;
311 }
312 }
313 }
314
315 if (j == runLimit || codept == '\t' || bm != null) {
316 boolean inSegment = target >= segstart && target < j;
317
318 boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
319 if (inSegment && advance) {
320 return h += measureRun(i, segstart, offset, j, runIsRtl, fmi);
321 }
322
323 float w = measureRun(i, segstart, j, j, runIsRtl, fmi);
324 h += advance ? w : -w;
325
326 if (inSegment) {
327 return h += measureRun(i, segstart, offset, j, runIsRtl, null);
328 }
329
330 if (codept == '\t') {
331 if (offset == j) {
332 return h;
333 }
334 h = mDir * nextTab(h * mDir);
335 if (target == j) {
336 return h;
337 }
338 }
339
340 if (bm != null) {
341 float bmAscent = ascent(j);
342 float wid = bm.getWidth() * -bmAscent / bm.getHeight();
343 h += mDir * wid;
344 j++;
345 }
346
347 segstart = j + 1;
348 }
349 }
350 }
351
352 return h;
353 }
354
355 /**
356 * Draws a unidirectional (but possibly multi-styled) run of text.
357 *
358 * @param c the canvas to draw on
359 * @param runIndex the index of this directional run
360 * @param start the line-relative start
361 * @param limit the line-relative limit
362 * @param runIsRtl true if the run is right-to-left
363 * @param x the position of the run that is closest to the leading margin
364 * @param top the top of the line
365 * @param y the baseline
366 * @param bottom the bottom of the line
367 * @param needWidth true if the width value is required.
368 * @return the signed width of the run, based on the paragraph direction.
369 * Only valid if needWidth is true.
370 */
371 private float drawRun(Canvas c, int runIndex, int start,
372 int limit, boolean runIsRtl, float x, int top, int y, int bottom,
373 boolean needWidth) {
374
375 if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
376 float w = -measureRun(runIndex, start, limit, limit, runIsRtl, null);
377 handleRun(runIndex, start, limit, limit, runIsRtl, c, x + w, top,
Doug Felt0c702b82010-05-14 10:55:42 -0700378 y, bottom, null, false);
Doug Felte8e45f22010-03-29 14:58:40 -0700379 return w;
380 }
381
382 return handleRun(runIndex, start, limit, limit, runIsRtl, c, x, top,
Doug Felt0c702b82010-05-14 10:55:42 -0700383 y, bottom, null, needWidth);
Doug Felte8e45f22010-03-29 14:58:40 -0700384 }
385
386 /**
387 * Measures a unidirectional (but possibly multi-styled) run of text.
388 *
389 * @param runIndex the run index
390 * @param start the line-relative start of the run
391 * @param offset the offset to measure to, between start and limit inclusive
392 * @param limit the line-relative limit of the run
393 * @param runIsRtl true if the run is right-to-left
394 * @param fmi receives metrics information about the requested
395 * run, can be null.
396 * @return the signed width from the start of the run to the leading edge
397 * of the character at offset, based on the run (not paragraph) direction
398 */
399 private float measureRun(int runIndex, int start,
400 int offset, int limit, boolean runIsRtl, FontMetricsInt fmi) {
401 return handleRun(runIndex, start, offset, limit, runIsRtl, null,
Doug Felt0c702b82010-05-14 10:55:42 -0700402 0, 0, 0, 0, fmi, true);
Doug Felte8e45f22010-03-29 14:58:40 -0700403 }
404
405 /**
406 * Walk the cursor through this line, skipping conjuncts and
407 * zero-width characters.
408 *
409 * <p>This function cannot properly walk the cursor off the ends of the line
410 * since it does not know about any shaping on the previous/following line
411 * that might affect the cursor position. Callers must either avoid these
412 * situations or handle the result specially.
413 *
Doug Felte8e45f22010-03-29 14:58:40 -0700414 * @param cursor the starting position of the cursor, between 0 and the
415 * length of the line, inclusive
416 * @param toLeft true if the caret is moving to the left.
417 * @return the new offset. If it is less than 0 or greater than the length
418 * of the line, the previous/following line should be examined to get the
419 * actual offset.
420 */
421 int getOffsetToLeftRightOf(int cursor, boolean toLeft) {
422 // 1) The caret marks the leading edge of a character. The character
423 // logically before it might be on a different level, and the active caret
424 // position is on the character at the lower level. If that character
425 // was the previous character, the caret is on its trailing edge.
426 // 2) Take this character/edge and move it in the indicated direction.
427 // This gives you a new character and a new edge.
428 // 3) This position is between two visually adjacent characters. One of
429 // these might be at a lower level. The active position is on the
430 // character at the lower level.
431 // 4) If the active position is on the trailing edge of the character,
432 // the new caret position is the following logical character, else it
433 // is the character.
434
435 int lineStart = 0;
436 int lineEnd = mLen;
437 boolean paraIsRtl = mDir == -1;
438 int[] runs = mDirections.mDirections;
439
440 int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1;
441 boolean trailing = false;
442
443 if (cursor == lineStart) {
444 runIndex = -2;
445 } else if (cursor == lineEnd) {
446 runIndex = runs.length;
447 } else {
448 // First, get information about the run containing the character with
449 // the active caret.
450 for (runIndex = 0; runIndex < runs.length; runIndex += 2) {
451 runStart = lineStart + runs[runIndex];
452 if (cursor >= runStart) {
453 runLimit = runStart + (runs[runIndex+1] & Layout.RUN_LENGTH_MASK);
454 if (runLimit > lineEnd) {
455 runLimit = lineEnd;
456 }
457 if (cursor < runLimit) {
458 runLevel = (runs[runIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
459 Layout.RUN_LEVEL_MASK;
460 if (cursor == runStart) {
461 // The caret is on a run boundary, see if we should
462 // use the position on the trailing edge of the previous
463 // logical character instead.
464 int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit;
465 int pos = cursor - 1;
466 for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) {
467 prevRunStart = lineStart + runs[prevRunIndex];
468 if (pos >= prevRunStart) {
469 prevRunLimit = prevRunStart +
470 (runs[prevRunIndex+1] & Layout.RUN_LENGTH_MASK);
471 if (prevRunLimit > lineEnd) {
472 prevRunLimit = lineEnd;
473 }
474 if (pos < prevRunLimit) {
475 prevRunLevel = (runs[prevRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT)
476 & Layout.RUN_LEVEL_MASK;
477 if (prevRunLevel < runLevel) {
478 // Start from logically previous character.
479 runIndex = prevRunIndex;
480 runLevel = prevRunLevel;
481 runStart = prevRunStart;
482 runLimit = prevRunLimit;
483 trailing = true;
484 break;
485 }
486 }
487 }
488 }
489 }
490 break;
491 }
492 }
493 }
494
495 // caret might be == lineEnd. This is generally a space or paragraph
496 // separator and has an associated run, but might be the end of
497 // text, in which case it doesn't. If that happens, we ran off the
498 // end of the run list, and runIndex == runs.length. In this case,
499 // we are at a run boundary so we skip the below test.
500 if (runIndex != runs.length) {
501 boolean runIsRtl = (runLevel & 0x1) != 0;
502 boolean advance = toLeft == runIsRtl;
503 if (cursor != (advance ? runLimit : runStart) || advance != trailing) {
504 // Moving within or into the run, so we can move logically.
Doug Felt0c702b82010-05-14 10:55:42 -0700505 newCaret = getOffsetBeforeAfter(runIndex, runStart, runLimit,
506 runIsRtl, cursor, advance);
Doug Felte8e45f22010-03-29 14:58:40 -0700507 // If the new position is internal to the run, we're at the strong
508 // position already so we're finished.
509 if (newCaret != (advance ? runLimit : runStart)) {
510 return newCaret;
511 }
512 }
513 }
514 }
515
516 // If newCaret is -1, we're starting at a run boundary and crossing
517 // into another run. Otherwise we've arrived at a run boundary, and
518 // need to figure out which character to attach to. Note we might
519 // need to run this twice, if we cross a run boundary and end up at
520 // another run boundary.
521 while (true) {
522 boolean advance = toLeft == paraIsRtl;
523 int otherRunIndex = runIndex + (advance ? 2 : -2);
524 if (otherRunIndex >= 0 && otherRunIndex < runs.length) {
525 int otherRunStart = lineStart + runs[otherRunIndex];
526 int otherRunLimit = otherRunStart +
527 (runs[otherRunIndex+1] & Layout.RUN_LENGTH_MASK);
528 if (otherRunLimit > lineEnd) {
529 otherRunLimit = lineEnd;
530 }
531 int otherRunLevel = (runs[otherRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
532 Layout.RUN_LEVEL_MASK;
533 boolean otherRunIsRtl = (otherRunLevel & 1) != 0;
534
535 advance = toLeft == otherRunIsRtl;
536 if (newCaret == -1) {
Doug Felt0c702b82010-05-14 10:55:42 -0700537 newCaret = getOffsetBeforeAfter(otherRunIndex, otherRunStart,
538 otherRunLimit, otherRunIsRtl,
Doug Felte8e45f22010-03-29 14:58:40 -0700539 advance ? otherRunStart : otherRunLimit, advance);
540 if (newCaret == (advance ? otherRunLimit : otherRunStart)) {
541 // Crossed and ended up at a new boundary,
542 // repeat a second and final time.
543 runIndex = otherRunIndex;
544 runLevel = otherRunLevel;
545 continue;
546 }
547 break;
548 }
549
550 // The new caret is at a boundary.
551 if (otherRunLevel < runLevel) {
552 // The strong character is in the other run.
553 newCaret = advance ? otherRunStart : otherRunLimit;
554 }
555 break;
556 }
557
558 if (newCaret == -1) {
559 // We're walking off the end of the line. The paragraph
560 // level is always equal to or lower than any internal level, so
561 // the boundaries get the strong caret.
Doug Felt0c702b82010-05-14 10:55:42 -0700562 newCaret = advance ? mLen + 1 : -1;
Doug Felte8e45f22010-03-29 14:58:40 -0700563 break;
564 }
565
566 // Else we've arrived at the end of the line. That's a strong position.
567 // We might have arrived here by crossing over a run with no internal
568 // breaks and dropping out of the above loop before advancing one final
569 // time, so reset the caret.
570 // Note, we use '<=' below to handle a situation where the only run
571 // on the line is a counter-directional run. If we're not advancing,
572 // we can end up at the 'lineEnd' position but the caret we want is at
573 // the lineStart.
574 if (newCaret <= lineEnd) {
575 newCaret = advance ? lineEnd : lineStart;
576 }
577 break;
578 }
579
580 return newCaret;
581 }
582
583 /**
584 * Returns the next valid offset within this directional run, skipping
585 * conjuncts and zero-width characters. This should not be called to walk
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -0700586 * off the end of the line, since the returned values might not be valid
Doug Felt0c702b82010-05-14 10:55:42 -0700587 * on neighboring lines. If the returned offset is less than zero or
588 * greater than the line length, the offset should be recomputed on the
589 * preceding or following line, respectively.
Doug Felte8e45f22010-03-29 14:58:40 -0700590 *
591 * @param runIndex the run index
Doug Felt0c702b82010-05-14 10:55:42 -0700592 * @param runStart the start of the run
593 * @param runLimit the limit of the run
594 * @param runIsRtl true if the run is right-to-left
Doug Felte8e45f22010-03-29 14:58:40 -0700595 * @param offset the offset
596 * @param after true if the new offset should logically follow the provided
597 * offset
598 * @return the new offset
599 */
Doug Felt0c702b82010-05-14 10:55:42 -0700600 private int getOffsetBeforeAfter(int runIndex, int runStart, int runLimit,
601 boolean runIsRtl, int offset, boolean after) {
Doug Felte8e45f22010-03-29 14:58:40 -0700602
Doug Felt0c702b82010-05-14 10:55:42 -0700603 if (runIndex < 0 || offset == (after ? mLen : 0)) {
604 // Walking off end of line. Since we don't know
605 // what cursor positions are available on other lines, we can't
606 // return accurate values. These are a guess.
Doug Felte8e45f22010-03-29 14:58:40 -0700607 if (after) {
Doug Felt0c702b82010-05-14 10:55:42 -0700608 return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart;
609 }
610 return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart;
611 }
612
613 TextPaint wp = mWorkPaint;
614 wp.set(mPaint);
615
616 int spanStart = runStart;
617 int spanLimit;
618 if (mSpanned == null) {
619 spanLimit = runLimit;
620 } else {
621 int target = after ? offset + 1 : offset;
622 int limit = mStart + runLimit;
623 while (true) {
624 spanLimit = mSpanned.nextSpanTransition(mStart + spanStart, limit,
625 MetricAffectingSpan.class) - mStart;
626 if (spanLimit >= target) {
627 break;
Doug Felte8e45f22010-03-29 14:58:40 -0700628 }
Doug Felt0c702b82010-05-14 10:55:42 -0700629 spanStart = spanLimit;
630 }
631
632 MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart,
633 mStart + spanLimit, MetricAffectingSpan.class);
634
635 if (spans.length > 0) {
636 ReplacementSpan replacement = null;
637 for (int j = 0; j < spans.length; j++) {
638 MetricAffectingSpan span = spans[j];
639 if (span instanceof ReplacementSpan) {
640 replacement = (ReplacementSpan)span;
641 } else {
642 span.updateMeasureState(wp);
643 }
644 }
645
646 if (replacement != null) {
647 // If we have a replacement span, we're moving either to
648 // the start or end of this span.
649 return after ? spanLimit : spanStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700650 }
651 }
Doug Felte8e45f22010-03-29 14:58:40 -0700652 }
653
Doug Felt0c702b82010-05-14 10:55:42 -0700654 int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
655 int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
656 if (mCharsValid) {
657 return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart,
658 flags, offset, cursorOpt);
659 } else {
660 return wp.getTextRunCursor(mText, mStart + spanStart,
Gilles Debunne345cb032010-06-16 17:13:23 -0700661 mStart + spanLimit, flags, mStart + offset, cursorOpt) - mStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700662 }
Doug Felte8e45f22010-03-29 14:58:40 -0700663 }
664
665 /**
666 * Utility function for measuring and rendering text. The text must
667 * not include a tab or emoji.
668 *
669 * @param wp the working paint
670 * @param start the start of the text
Doug Felt0c702b82010-05-14 10:55:42 -0700671 * @param end the end of the text
Doug Felte8e45f22010-03-29 14:58:40 -0700672 * @param runIsRtl true if the run is right-to-left
673 * @param c the canvas, can be null if rendering is not needed
674 * @param x the edge of the run closest to the leading margin
675 * @param top the top of the line
676 * @param y the baseline
677 * @param bottom the bottom of the line
678 * @param fmi receives metrics information, can be null
679 * @param needWidth true if the width of the run is needed
680 * @return the signed width of the run based on the run direction; only
681 * valid if needWidth is true
682 */
Doug Felt0c702b82010-05-14 10:55:42 -0700683 private float handleText(TextPaint wp, int start, int end,
684 int contextStart, int contextEnd, boolean runIsRtl,
685 Canvas c, float x, int top, int y, int bottom,
Doug Felte8e45f22010-03-29 14:58:40 -0700686 FontMetricsInt fmi, boolean needWidth) {
687
688 float ret = 0;
689
Doug Felt0c702b82010-05-14 10:55:42 -0700690 int runLen = end - start;
691 int contextLen = contextEnd - contextStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700692 if (needWidth || (c != null && (wp.bgColor != 0 || runIsRtl))) {
Doug Felt0c702b82010-05-14 10:55:42 -0700693 int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
Doug Felte8e45f22010-03-29 14:58:40 -0700694 if (mCharsValid) {
Doug Felt0c702b82010-05-14 10:55:42 -0700695 ret = wp.getTextRunAdvances(mChars, start, runLen,
696 contextStart, contextLen, flags, null, 0);
Doug Felte8e45f22010-03-29 14:58:40 -0700697 } else {
Doug Felt0c702b82010-05-14 10:55:42 -0700698 int delta = mStart;
699 ret = wp.getTextRunAdvances(mText, delta + start,
700 delta + end, delta + contextStart, delta + contextEnd,
701 flags, null, 0);
Doug Felte8e45f22010-03-29 14:58:40 -0700702 }
703 }
704
705 if (fmi != null) {
706 wp.getFontMetricsInt(fmi);
707 }
708
709 if (c != null) {
710 if (runIsRtl) {
711 x -= ret;
712 }
713
714 if (wp.bgColor != 0) {
715 int color = wp.getColor();
716 Paint.Style s = wp.getStyle();
717 wp.setColor(wp.bgColor);
718 wp.setStyle(Paint.Style.FILL);
719
720 c.drawRect(x, top, x + ret, bottom, wp);
721
722 wp.setStyle(s);
723 wp.setColor(color);
724 }
725
Doug Felt0c702b82010-05-14 10:55:42 -0700726 drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
727 x, y + wp.baselineShift);
Doug Felte8e45f22010-03-29 14:58:40 -0700728 }
729
730 return runIsRtl ? -ret : ret;
731 }
732
733 /**
734 * Utility function for measuring and rendering a replacement.
735 *
736 * @param replacement the replacement
737 * @param wp the work paint
738 * @param runIndex the run index
739 * @param start the start of the run
740 * @param limit the limit of the run
741 * @param runIsRtl true if the run is right-to-left
742 * @param c the canvas, can be null if not rendering
743 * @param x the edge of the replacement closest to the leading margin
744 * @param top the top of the line
745 * @param y the baseline
746 * @param bottom the bottom of the line
747 * @param fmi receives metrics information, can be null
748 * @param needWidth true if the width of the replacement is needed
Doug Felte8e45f22010-03-29 14:58:40 -0700749 * @return the signed width of the run based on the run direction; only
750 * valid if needWidth is true
751 */
752 private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
753 int runIndex, int start, int limit, boolean runIsRtl, Canvas c,
754 float x, int top, int y, int bottom, FontMetricsInt fmi,
Doug Felt0c702b82010-05-14 10:55:42 -0700755 boolean needWidth) {
Doug Felte8e45f22010-03-29 14:58:40 -0700756
757 float ret = 0;
758
Doug Felt0c702b82010-05-14 10:55:42 -0700759 int textStart = mStart + start;
760 int textLimit = mStart + limit;
761
762 if (needWidth || (c != null && runIsRtl)) {
763 ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
Doug Felte8e45f22010-03-29 14:58:40 -0700764 }
765
Doug Felt0c702b82010-05-14 10:55:42 -0700766 if (c != null) {
767 if (runIsRtl) {
768 x -= ret;
Doug Felte8e45f22010-03-29 14:58:40 -0700769 }
Doug Felt0c702b82010-05-14 10:55:42 -0700770 replacement.draw(c, mText, textStart, textLimit,
771 x, top, y, bottom, wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700772 }
773
774 return runIsRtl ? -ret : ret;
775 }
776
777 /**
778 * Utility function for handling a unidirectional run. The run must not
779 * contain tabs or emoji but can contain styles.
780 *
Doug Felte8e45f22010-03-29 14:58:40 -0700781 * @param runIndex the run index
782 * @param start the line-relative start of the run
Doug Felt0c702b82010-05-14 10:55:42 -0700783 * @param measureLimit the offset to measure to, between start and limit inclusive
Doug Felte8e45f22010-03-29 14:58:40 -0700784 * @param limit the limit of the run
785 * @param runIsRtl true if the run is right-to-left
786 * @param c the canvas, can be null
787 * @param x the end of the run closest to the leading margin
788 * @param top the top of the line
789 * @param y the baseline
790 * @param bottom the bottom of the line
791 * @param fmi receives metrics information, can be null
792 * @param needWidth true if the width is required
Doug Felte8e45f22010-03-29 14:58:40 -0700793 * @return the signed width of the run based on the run direction; only
794 * valid if needWidth is true
795 */
Doug Felt0c702b82010-05-14 10:55:42 -0700796 private float handleRun(int runIndex, int start, int measureLimit,
Doug Felte8e45f22010-03-29 14:58:40 -0700797 int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
Doug Felt0c702b82010-05-14 10:55:42 -0700798 int bottom, FontMetricsInt fmi, boolean needWidth) {
Doug Felte8e45f22010-03-29 14:58:40 -0700799
800 // Shaping needs to take into account context up to metric boundaries,
801 // but rendering needs to take into account character style boundaries.
Doug Felt0c702b82010-05-14 10:55:42 -0700802 // So we iterate through metric runs to get metric bounds,
803 // then within each metric run iterate through character style runs
804 // for the run bounds.
Doug Felte8e45f22010-03-29 14:58:40 -0700805 float ox = x;
Doug Felt0c702b82010-05-14 10:55:42 -0700806 for (int i = start, inext; i < measureLimit; i = inext) {
Doug Felte8e45f22010-03-29 14:58:40 -0700807 TextPaint wp = mWorkPaint;
808 wp.set(mPaint);
809
Doug Felt0c702b82010-05-14 10:55:42 -0700810 int mlimit;
Doug Felte8e45f22010-03-29 14:58:40 -0700811 if (mSpanned == null) {
812 inext = limit;
Doug Felt0c702b82010-05-14 10:55:42 -0700813 mlimit = measureLimit;
Doug Felte8e45f22010-03-29 14:58:40 -0700814 } else {
815 inext = mSpanned.nextSpanTransition(mStart + i, mStart + limit,
816 MetricAffectingSpan.class) - mStart;
817
Doug Felt0c702b82010-05-14 10:55:42 -0700818 mlimit = inext < measureLimit ? inext : measureLimit;
Doug Felte8e45f22010-03-29 14:58:40 -0700819 MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + i,
Doug Felt0c702b82010-05-14 10:55:42 -0700820 mStart + mlimit, MetricAffectingSpan.class);
Doug Felte8e45f22010-03-29 14:58:40 -0700821
822 if (spans.length > 0) {
823 ReplacementSpan replacement = null;
824 for (int j = 0; j < spans.length; j++) {
825 MetricAffectingSpan span = spans[j];
826 if (span instanceof ReplacementSpan) {
827 replacement = (ReplacementSpan)span;
828 } else {
Doug Felt0c702b82010-05-14 10:55:42 -0700829 // We might have a replacement that uses the draw
830 // state, otherwise measure state would suffice.
831 span.updateDrawState(wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700832 }
833 }
834
835 if (replacement != null) {
836 x += handleReplacement(replacement, wp, runIndex, i,
Doug Felt0c702b82010-05-14 10:55:42 -0700837 mlimit, runIsRtl, c, x, top, y, bottom, fmi,
838 needWidth || mlimit < measureLimit);
Doug Felte8e45f22010-03-29 14:58:40 -0700839 continue;
840 }
841 }
842 }
843
Doug Felt0c702b82010-05-14 10:55:42 -0700844 if (mSpanned == null || c == null) {
845 x += handleText(wp, i, mlimit, i, inext, runIsRtl, c, x, top,
846 y, bottom, fmi, needWidth || mlimit < measureLimit);
847 } else {
848 for (int j = i, jnext; j < mlimit; j = jnext) {
849 jnext = mSpanned.nextSpanTransition(mStart + j,
850 mStart + mlimit, CharacterStyle.class) - mStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700851
Doug Felt0c702b82010-05-14 10:55:42 -0700852 CharacterStyle[] spans = mSpanned.getSpans(mStart + j,
853 mStart + jnext, CharacterStyle.class);
Doug Felte8e45f22010-03-29 14:58:40 -0700854
Doug Felt0c702b82010-05-14 10:55:42 -0700855 wp.set(mPaint);
856 for (int k = 0; k < spans.length; k++) {
857 CharacterStyle span = spans[k];
858 span.updateDrawState(wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700859 }
Doug Felt0c702b82010-05-14 10:55:42 -0700860
861 x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
862 top, y, bottom, fmi, needWidth || jnext < measureLimit);
Doug Felte8e45f22010-03-29 14:58:40 -0700863 }
864 }
865 }
866
867 return x - ox;
868 }
869
Doug Felte8e45f22010-03-29 14:58:40 -0700870 /**
871 * Render a text run with the set-up paint.
872 *
873 * @param c the canvas
874 * @param wp the paint used to render the text
Doug Felt0c702b82010-05-14 10:55:42 -0700875 * @param start the start of the run
876 * @param end the end of the run
877 * @param contextStart the start of context for the run
878 * @param contextEnd the end of the context for the run
Doug Felte8e45f22010-03-29 14:58:40 -0700879 * @param runIsRtl true if the run is right-to-left
880 * @param x the x position of the left edge of the run
881 * @param y the baseline of the run
882 */
Doug Felt0c702b82010-05-14 10:55:42 -0700883 private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
884 int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
Doug Felte8e45f22010-03-29 14:58:40 -0700885
Doug Feltf47d7402010-04-21 16:01:52 -0700886 int flags = runIsRtl ? Canvas.DIRECTION_RTL : Canvas.DIRECTION_LTR;
Doug Felte8e45f22010-03-29 14:58:40 -0700887 if (mCharsValid) {
Doug Felt0c702b82010-05-14 10:55:42 -0700888 int count = end - start;
889 int contextCount = contextEnd - contextStart;
890 c.drawTextRun(mChars, start, count, contextStart, contextCount,
891 x, y, flags, wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700892 } else {
Doug Felt0c702b82010-05-14 10:55:42 -0700893 int delta = mStart;
894 c.drawTextRun(mText, delta + start, delta + end,
895 delta + contextStart, delta + contextEnd, x, y, flags, wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700896 }
897 }
898
899 /**
900 * Returns the ascent of the text at start. This is used for scaling
901 * emoji.
902 *
903 * @param pos the line-relative position
904 * @return the ascent of the text at start
905 */
906 float ascent(int pos) {
907 if (mSpanned == null) {
908 return mPaint.ascent();
909 }
910
911 pos += mStart;
912 MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1,
913 MetricAffectingSpan.class);
914 if (spans.length == 0) {
915 return mPaint.ascent();
916 }
917
918 TextPaint wp = mWorkPaint;
919 wp.set(mPaint);
920 for (MetricAffectingSpan span : spans) {
921 span.updateMeasureState(wp);
922 }
923 return wp.ascent();
924 }
925
926 /**
927 * Returns the next tab position.
928 *
929 * @param h the (unsigned) offset from the leading margin
930 * @return the (unsigned) tab position after this offset
931 */
932 float nextTab(float h) {
Doug Feltc982f602010-05-25 11:51:40 -0700933 if (mTabs != null) {
934 return mTabs.nextTab(h);
Doug Felte8e45f22010-03-29 14:58:40 -0700935 }
Doug Feltc982f602010-05-25 11:51:40 -0700936 return TabStops.nextDefaultStop(h, TAB_INCREMENT);
Doug Felte8e45f22010-03-29 14:58:40 -0700937 }
938
939 private static final int TAB_INCREMENT = 20;
940}