blob: 90279d1f63c98e5873d257ac9c82e07305ee4586 [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();
Chet Haase673e42f2010-08-25 16:32:37 -070078 Log.v("TLINE", "new: " + tl);
Doug Felte8e45f22010-03-29 14:58:40 -070079 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;
Gilles Debunnef902d7b2011-01-25 09:09:46 -080093 synchronized(cached) {
94 for (int i = 0; i < cached.length; ++i) {
95 if (cached[i] == null) {
96 cached[i] = tl;
97 break;
Doug Felte8e45f22010-03-29 14:58:40 -070098 }
99 }
100 }
101 return null;
102 }
103
104 /**
105 * Initializes a TextLine and prepares it for use.
106 *
107 * @param paint the base paint for the line
108 * @param text the text, can be Styled
109 * @param start the start of the line relative to the text
110 * @param limit the limit of the line relative to the text
111 * @param dir the paragraph direction of this line
112 * @param directions the directions information of this line
113 * @param hasTabs true if the line might contain tabs or emoji
Doug Feltc982f602010-05-25 11:51:40 -0700114 * @param tabStops the tabStops. Can be null.
Doug Felte8e45f22010-03-29 14:58:40 -0700115 */
116 void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
Doug Feltc982f602010-05-25 11:51:40 -0700117 Directions directions, boolean hasTabs, TabStops tabStops) {
Doug Felte8e45f22010-03-29 14:58:40 -0700118 mPaint = paint;
119 mText = text;
120 mStart = start;
121 mLen = limit - start;
122 mDir = dir;
123 mDirections = directions;
124 mHasTabs = hasTabs;
125 mSpanned = null;
Doug Felte8e45f22010-03-29 14:58:40 -0700126
127 boolean hasReplacement = false;
128 if (text instanceof Spanned) {
129 mSpanned = (Spanned) text;
130 hasReplacement = mSpanned.getSpans(start, limit,
131 ReplacementSpan.class).length > 0;
132 }
133
134 mCharsValid = hasReplacement || hasTabs ||
135 directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
136
137 if (mCharsValid) {
138 if (mChars == null || mChars.length < mLen) {
139 mChars = new char[ArrayUtils.idealCharArraySize(mLen)];
140 }
141 TextUtils.getChars(text, start, limit, mChars, 0);
Doug Felt0c702b82010-05-14 10:55:42 -0700142 if (hasReplacement) {
143 // Handle these all at once so we don't have to do it as we go.
144 // Replace the first character of each replacement run with the
145 // object-replacement character and the remainder with zero width
146 // non-break space aka BOM. Cursor movement code skips these
147 // zero-width characters.
148 char[] chars = mChars;
149 for (int i = start, inext; i < limit; i = inext) {
150 inext = mSpanned.nextSpanTransition(i, limit,
151 ReplacementSpan.class);
152 if (mSpanned.getSpans(i, inext, ReplacementSpan.class)
153 .length > 0) { // transition into a span
154 chars[i - start] = '\ufffc';
155 for (int j = i - start + 1, e = inext - start; j < e; ++j) {
156 chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip
157 }
158 }
159 }
160 }
Doug Felte8e45f22010-03-29 14:58:40 -0700161 }
Doug Feltc982f602010-05-25 11:51:40 -0700162 mTabs = tabStops;
Doug Felte8e45f22010-03-29 14:58:40 -0700163 }
164
165 /**
166 * Renders the TextLine.
167 *
168 * @param c the canvas to render on
169 * @param x the leading margin position
170 * @param top the top of the line
171 * @param y the baseline
172 * @param bottom the bottom of the line
173 */
174 void draw(Canvas c, float x, int top, int y, int bottom) {
175 if (!mHasTabs) {
176 if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
177 drawRun(c, 0, 0, mLen, false, x, top, y, bottom, false);
178 return;
179 }
180 if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
181 drawRun(c, 0, 0, mLen, true, x, top, y, bottom, false);
182 return;
183 }
184 }
185
186 float h = 0;
187 int[] runs = mDirections.mDirections;
188 RectF emojiRect = null;
189
190 int lastRunIndex = runs.length - 2;
191 for (int i = 0; i < runs.length; i += 2) {
192 int runStart = runs[i];
193 int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
194 if (runLimit > mLen) {
195 runLimit = mLen;
196 }
197 boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
198
199 int segstart = runStart;
200 char[] chars = mChars;
201 for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
202 int codept = 0;
203 Bitmap bm = null;
204
205 if (mHasTabs && j < runLimit) {
206 codept = mChars[j];
207 if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) {
208 codept = Character.codePointAt(mChars, j);
209 if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) {
210 bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
211 } else if (codept > 0xffff) {
212 ++j;
213 continue;
214 }
215 }
216 }
217
218 if (j == runLimit || codept == '\t' || bm != null) {
219 h += drawRun(c, i, segstart, j, runIsRtl, x+h, top, y, bottom,
220 i != lastRunIndex || j != mLen);
221
222 if (codept == '\t') {
223 h = mDir * nextTab(h * mDir);
224 } else if (bm != null) {
225 float bmAscent = ascent(j);
226 float bitmapHeight = bm.getHeight();
227 float scale = -bmAscent / bitmapHeight;
228 float width = bm.getWidth() * scale;
229
230 if (emojiRect == null) {
231 emojiRect = new RectF();
232 }
233 emojiRect.set(x + h, y + bmAscent,
234 x + h + width, y);
235 c.drawBitmap(bm, null, emojiRect, mPaint);
236 h += width;
237 j++;
238 }
239 segstart = j + 1;
240 }
241 }
242 }
243 }
244
245 /**
246 * Returns metrics information for the entire line.
247 *
248 * @param fmi receives font metrics information, can be null
249 * @return the signed width of the line
250 */
251 float metrics(FontMetricsInt fmi) {
252 return measure(mLen, false, fmi);
253 }
254
255 /**
256 * Returns information about a position on the line.
257 *
258 * @param offset the line-relative character offset, between 0 and the
259 * line length, inclusive
260 * @param trailing true to measure the trailing edge of the character
261 * before offset, false to measure the leading edge of the character
262 * at offset.
263 * @param fmi receives metrics information about the requested
264 * character, can be null.
265 * @return the signed offset from the leading margin to the requested
266 * character edge.
267 */
268 float measure(int offset, boolean trailing, FontMetricsInt fmi) {
269 int target = trailing ? offset - 1 : offset;
270 if (target < 0) {
271 return 0;
272 }
273
274 float h = 0;
275
276 if (!mHasTabs) {
277 if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
Doug Felt0c702b82010-05-14 10:55:42 -0700278 return measureRun(0, 0, offset, mLen, false, fmi);
Doug Felte8e45f22010-03-29 14:58:40 -0700279 }
280 if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
Doug Felt0b9d2ca2010-05-06 17:10:54 -0700281 return measureRun(0, 0, offset, mLen, true, fmi);
Doug Felte8e45f22010-03-29 14:58:40 -0700282 }
283 }
284
285 char[] chars = mChars;
286 int[] runs = mDirections.mDirections;
287 for (int i = 0; i < runs.length; i += 2) {
288 int runStart = runs[i];
289 int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
290 if (runLimit > mLen) {
291 runLimit = mLen;
292 }
293 boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
294
295 int segstart = runStart;
296 for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
297 int codept = 0;
298 Bitmap bm = null;
299
300 if (mHasTabs && j < runLimit) {
301 codept = chars[j];
302 if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) {
303 codept = Character.codePointAt(chars, j);
304 if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) {
305 bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
306 } else if (codept > 0xffff) {
307 ++j;
308 continue;
309 }
310 }
311 }
312
313 if (j == runLimit || codept == '\t' || bm != null) {
314 boolean inSegment = target >= segstart && target < j;
315
316 boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
317 if (inSegment && advance) {
318 return h += measureRun(i, segstart, offset, j, runIsRtl, fmi);
319 }
320
321 float w = measureRun(i, segstart, j, j, runIsRtl, fmi);
322 h += advance ? w : -w;
323
324 if (inSegment) {
325 return h += measureRun(i, segstart, offset, j, runIsRtl, null);
326 }
327
328 if (codept == '\t') {
329 if (offset == j) {
330 return h;
331 }
332 h = mDir * nextTab(h * mDir);
333 if (target == j) {
334 return h;
335 }
336 }
337
338 if (bm != null) {
339 float bmAscent = ascent(j);
340 float wid = bm.getWidth() * -bmAscent / bm.getHeight();
341 h += mDir * wid;
342 j++;
343 }
344
345 segstart = j + 1;
346 }
347 }
348 }
349
350 return h;
351 }
352
353 /**
354 * Draws a unidirectional (but possibly multi-styled) run of text.
355 *
356 * @param c the canvas to draw on
357 * @param runIndex the index of this directional run
358 * @param start the line-relative start
359 * @param limit the line-relative limit
360 * @param runIsRtl true if the run is right-to-left
361 * @param x the position of the run that is closest to the leading margin
362 * @param top the top of the line
363 * @param y the baseline
364 * @param bottom the bottom of the line
365 * @param needWidth true if the width value is required.
366 * @return the signed width of the run, based on the paragraph direction.
367 * Only valid if needWidth is true.
368 */
369 private float drawRun(Canvas c, int runIndex, int start,
370 int limit, boolean runIsRtl, float x, int top, int y, int bottom,
371 boolean needWidth) {
372
373 if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
374 float w = -measureRun(runIndex, start, limit, limit, runIsRtl, null);
375 handleRun(runIndex, start, limit, limit, runIsRtl, c, x + w, top,
Doug Felt0c702b82010-05-14 10:55:42 -0700376 y, bottom, null, false);
Doug Felte8e45f22010-03-29 14:58:40 -0700377 return w;
378 }
379
380 return handleRun(runIndex, start, limit, limit, runIsRtl, c, x, top,
Doug Felt0c702b82010-05-14 10:55:42 -0700381 y, bottom, null, needWidth);
Doug Felte8e45f22010-03-29 14:58:40 -0700382 }
383
384 /**
385 * Measures a unidirectional (but possibly multi-styled) run of text.
386 *
387 * @param runIndex the run index
388 * @param start the line-relative start of the run
389 * @param offset the offset to measure to, between start and limit inclusive
390 * @param limit the line-relative limit of the run
391 * @param runIsRtl true if the run is right-to-left
392 * @param fmi receives metrics information about the requested
393 * run, can be null.
394 * @return the signed width from the start of the run to the leading edge
395 * of the character at offset, based on the run (not paragraph) direction
396 */
397 private float measureRun(int runIndex, int start,
398 int offset, int limit, boolean runIsRtl, FontMetricsInt fmi) {
399 return handleRun(runIndex, start, offset, limit, runIsRtl, null,
Doug Felt0c702b82010-05-14 10:55:42 -0700400 0, 0, 0, 0, fmi, true);
Doug Felte8e45f22010-03-29 14:58:40 -0700401 }
402
403 /**
404 * Walk the cursor through this line, skipping conjuncts and
405 * zero-width characters.
406 *
407 * <p>This function cannot properly walk the cursor off the ends of the line
408 * since it does not know about any shaping on the previous/following line
409 * that might affect the cursor position. Callers must either avoid these
410 * situations or handle the result specially.
411 *
Doug Felte8e45f22010-03-29 14:58:40 -0700412 * @param cursor the starting position of the cursor, between 0 and the
413 * length of the line, inclusive
414 * @param toLeft true if the caret is moving to the left.
415 * @return the new offset. If it is less than 0 or greater than the length
416 * of the line, the previous/following line should be examined to get the
417 * actual offset.
418 */
419 int getOffsetToLeftRightOf(int cursor, boolean toLeft) {
420 // 1) The caret marks the leading edge of a character. The character
421 // logically before it might be on a different level, and the active caret
422 // position is on the character at the lower level. If that character
423 // was the previous character, the caret is on its trailing edge.
424 // 2) Take this character/edge and move it in the indicated direction.
425 // This gives you a new character and a new edge.
426 // 3) This position is between two visually adjacent characters. One of
427 // these might be at a lower level. The active position is on the
428 // character at the lower level.
429 // 4) If the active position is on the trailing edge of the character,
430 // the new caret position is the following logical character, else it
431 // is the character.
432
433 int lineStart = 0;
434 int lineEnd = mLen;
435 boolean paraIsRtl = mDir == -1;
436 int[] runs = mDirections.mDirections;
437
438 int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1;
439 boolean trailing = false;
440
441 if (cursor == lineStart) {
442 runIndex = -2;
443 } else if (cursor == lineEnd) {
444 runIndex = runs.length;
445 } else {
446 // First, get information about the run containing the character with
447 // the active caret.
448 for (runIndex = 0; runIndex < runs.length; runIndex += 2) {
449 runStart = lineStart + runs[runIndex];
450 if (cursor >= runStart) {
451 runLimit = runStart + (runs[runIndex+1] & Layout.RUN_LENGTH_MASK);
452 if (runLimit > lineEnd) {
453 runLimit = lineEnd;
454 }
455 if (cursor < runLimit) {
456 runLevel = (runs[runIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
457 Layout.RUN_LEVEL_MASK;
458 if (cursor == runStart) {
459 // The caret is on a run boundary, see if we should
460 // use the position on the trailing edge of the previous
461 // logical character instead.
462 int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit;
463 int pos = cursor - 1;
464 for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) {
465 prevRunStart = lineStart + runs[prevRunIndex];
466 if (pos >= prevRunStart) {
467 prevRunLimit = prevRunStart +
468 (runs[prevRunIndex+1] & Layout.RUN_LENGTH_MASK);
469 if (prevRunLimit > lineEnd) {
470 prevRunLimit = lineEnd;
471 }
472 if (pos < prevRunLimit) {
473 prevRunLevel = (runs[prevRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT)
474 & Layout.RUN_LEVEL_MASK;
475 if (prevRunLevel < runLevel) {
476 // Start from logically previous character.
477 runIndex = prevRunIndex;
478 runLevel = prevRunLevel;
479 runStart = prevRunStart;
480 runLimit = prevRunLimit;
481 trailing = true;
482 break;
483 }
484 }
485 }
486 }
487 }
488 break;
489 }
490 }
491 }
492
493 // caret might be == lineEnd. This is generally a space or paragraph
494 // separator and has an associated run, but might be the end of
495 // text, in which case it doesn't. If that happens, we ran off the
496 // end of the run list, and runIndex == runs.length. In this case,
497 // we are at a run boundary so we skip the below test.
498 if (runIndex != runs.length) {
499 boolean runIsRtl = (runLevel & 0x1) != 0;
500 boolean advance = toLeft == runIsRtl;
501 if (cursor != (advance ? runLimit : runStart) || advance != trailing) {
502 // Moving within or into the run, so we can move logically.
Doug Felt0c702b82010-05-14 10:55:42 -0700503 newCaret = getOffsetBeforeAfter(runIndex, runStart, runLimit,
504 runIsRtl, cursor, advance);
Doug Felte8e45f22010-03-29 14:58:40 -0700505 // If the new position is internal to the run, we're at the strong
506 // position already so we're finished.
507 if (newCaret != (advance ? runLimit : runStart)) {
508 return newCaret;
509 }
510 }
511 }
512 }
513
514 // If newCaret is -1, we're starting at a run boundary and crossing
515 // into another run. Otherwise we've arrived at a run boundary, and
516 // need to figure out which character to attach to. Note we might
517 // need to run this twice, if we cross a run boundary and end up at
518 // another run boundary.
519 while (true) {
520 boolean advance = toLeft == paraIsRtl;
521 int otherRunIndex = runIndex + (advance ? 2 : -2);
522 if (otherRunIndex >= 0 && otherRunIndex < runs.length) {
523 int otherRunStart = lineStart + runs[otherRunIndex];
524 int otherRunLimit = otherRunStart +
525 (runs[otherRunIndex+1] & Layout.RUN_LENGTH_MASK);
526 if (otherRunLimit > lineEnd) {
527 otherRunLimit = lineEnd;
528 }
529 int otherRunLevel = (runs[otherRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
530 Layout.RUN_LEVEL_MASK;
531 boolean otherRunIsRtl = (otherRunLevel & 1) != 0;
532
533 advance = toLeft == otherRunIsRtl;
534 if (newCaret == -1) {
Doug Felt0c702b82010-05-14 10:55:42 -0700535 newCaret = getOffsetBeforeAfter(otherRunIndex, otherRunStart,
536 otherRunLimit, otherRunIsRtl,
Doug Felte8e45f22010-03-29 14:58:40 -0700537 advance ? otherRunStart : otherRunLimit, advance);
538 if (newCaret == (advance ? otherRunLimit : otherRunStart)) {
539 // Crossed and ended up at a new boundary,
540 // repeat a second and final time.
541 runIndex = otherRunIndex;
542 runLevel = otherRunLevel;
543 continue;
544 }
545 break;
546 }
547
548 // The new caret is at a boundary.
549 if (otherRunLevel < runLevel) {
550 // The strong character is in the other run.
551 newCaret = advance ? otherRunStart : otherRunLimit;
552 }
553 break;
554 }
555
556 if (newCaret == -1) {
557 // We're walking off the end of the line. The paragraph
558 // level is always equal to or lower than any internal level, so
559 // the boundaries get the strong caret.
Doug Felt0c702b82010-05-14 10:55:42 -0700560 newCaret = advance ? mLen + 1 : -1;
Doug Felte8e45f22010-03-29 14:58:40 -0700561 break;
562 }
563
564 // Else we've arrived at the end of the line. That's a strong position.
565 // We might have arrived here by crossing over a run with no internal
566 // breaks and dropping out of the above loop before advancing one final
567 // time, so reset the caret.
568 // Note, we use '<=' below to handle a situation where the only run
569 // on the line is a counter-directional run. If we're not advancing,
570 // we can end up at the 'lineEnd' position but the caret we want is at
571 // the lineStart.
572 if (newCaret <= lineEnd) {
573 newCaret = advance ? lineEnd : lineStart;
574 }
575 break;
576 }
577
578 return newCaret;
579 }
580
581 /**
582 * Returns the next valid offset within this directional run, skipping
583 * conjuncts and zero-width characters. This should not be called to walk
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -0700584 * off the end of the line, since the returned values might not be valid
Doug Felt0c702b82010-05-14 10:55:42 -0700585 * on neighboring lines. If the returned offset is less than zero or
586 * greater than the line length, the offset should be recomputed on the
587 * preceding or following line, respectively.
Doug Felte8e45f22010-03-29 14:58:40 -0700588 *
589 * @param runIndex the run index
Doug Felt0c702b82010-05-14 10:55:42 -0700590 * @param runStart the start of the run
591 * @param runLimit the limit of the run
592 * @param runIsRtl true if the run is right-to-left
Doug Felte8e45f22010-03-29 14:58:40 -0700593 * @param offset the offset
594 * @param after true if the new offset should logically follow the provided
595 * offset
596 * @return the new offset
597 */
Doug Felt0c702b82010-05-14 10:55:42 -0700598 private int getOffsetBeforeAfter(int runIndex, int runStart, int runLimit,
599 boolean runIsRtl, int offset, boolean after) {
Doug Felte8e45f22010-03-29 14:58:40 -0700600
Doug Felt0c702b82010-05-14 10:55:42 -0700601 if (runIndex < 0 || offset == (after ? mLen : 0)) {
602 // Walking off end of line. Since we don't know
603 // what cursor positions are available on other lines, we can't
604 // return accurate values. These are a guess.
Doug Felte8e45f22010-03-29 14:58:40 -0700605 if (after) {
Doug Felt0c702b82010-05-14 10:55:42 -0700606 return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart;
607 }
608 return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart;
609 }
610
611 TextPaint wp = mWorkPaint;
612 wp.set(mPaint);
613
614 int spanStart = runStart;
615 int spanLimit;
616 if (mSpanned == null) {
617 spanLimit = runLimit;
618 } else {
619 int target = after ? offset + 1 : offset;
620 int limit = mStart + runLimit;
621 while (true) {
622 spanLimit = mSpanned.nextSpanTransition(mStart + spanStart, limit,
623 MetricAffectingSpan.class) - mStart;
624 if (spanLimit >= target) {
625 break;
Doug Felte8e45f22010-03-29 14:58:40 -0700626 }
Doug Felt0c702b82010-05-14 10:55:42 -0700627 spanStart = spanLimit;
628 }
629
630 MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart,
631 mStart + spanLimit, MetricAffectingSpan.class);
632
633 if (spans.length > 0) {
634 ReplacementSpan replacement = null;
635 for (int j = 0; j < spans.length; j++) {
636 MetricAffectingSpan span = spans[j];
637 if (span instanceof ReplacementSpan) {
638 replacement = (ReplacementSpan)span;
639 } else {
640 span.updateMeasureState(wp);
641 }
642 }
643
644 if (replacement != null) {
645 // If we have a replacement span, we're moving either to
646 // the start or end of this span.
647 return after ? spanLimit : spanStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700648 }
649 }
Doug Felte8e45f22010-03-29 14:58:40 -0700650 }
651
Doug Felt0c702b82010-05-14 10:55:42 -0700652 int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
653 int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
654 if (mCharsValid) {
655 return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart,
656 flags, offset, cursorOpt);
657 } else {
658 return wp.getTextRunCursor(mText, mStart + spanStart,
Gilles Debunne345cb032010-06-16 17:13:23 -0700659 mStart + spanLimit, flags, mStart + offset, cursorOpt) - mStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700660 }
Doug Felte8e45f22010-03-29 14:58:40 -0700661 }
662
663 /**
Gilles Debunne0bb00092010-12-02 15:50:26 -0800664 * @param wp
665 */
666 private static void expandMetricsFromPaint(FontMetricsInt fmi, TextPaint wp) {
667 final int previousTop = fmi.top;
668 final int previousAscent = fmi.ascent;
669 final int previousDescent = fmi.descent;
670 final int previousBottom = fmi.bottom;
671 final int previousLeading = fmi.leading;
672
673 wp.getFontMetricsInt(fmi);
674
675 fmi.top = Math.min(fmi.top, previousTop);
676 fmi.ascent = Math.min(fmi.ascent, previousAscent);
677 fmi.descent = Math.max(fmi.descent, previousDescent);
678 fmi.bottom = Math.max(fmi.bottom, previousBottom);
679 fmi.leading = Math.max(fmi.leading, previousLeading);
680 }
681
682 /**
Doug Felte8e45f22010-03-29 14:58:40 -0700683 * Utility function for measuring and rendering text. The text must
684 * not include a tab or emoji.
685 *
686 * @param wp the working paint
687 * @param start the start of the text
Doug Felt0c702b82010-05-14 10:55:42 -0700688 * @param end the end of the text
Doug Felte8e45f22010-03-29 14:58:40 -0700689 * @param runIsRtl true if the run is right-to-left
690 * @param c the canvas, can be null if rendering is not needed
691 * @param x the edge of the run closest to the leading margin
692 * @param top the top of the line
693 * @param y the baseline
694 * @param bottom the bottom of the line
695 * @param fmi receives metrics information, can be null
696 * @param needWidth true if the width of the run is needed
697 * @return the signed width of the run based on the run direction; only
698 * valid if needWidth is true
699 */
Doug Felt0c702b82010-05-14 10:55:42 -0700700 private float handleText(TextPaint wp, int start, int end,
701 int contextStart, int contextEnd, boolean runIsRtl,
702 Canvas c, float x, int top, int y, int bottom,
Doug Felte8e45f22010-03-29 14:58:40 -0700703 FontMetricsInt fmi, boolean needWidth) {
704
705 float ret = 0;
706
Doug Felt0c702b82010-05-14 10:55:42 -0700707 int runLen = end - start;
708 int contextLen = contextEnd - contextStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700709 if (needWidth || (c != null && (wp.bgColor != 0 || runIsRtl))) {
Doug Felt0c702b82010-05-14 10:55:42 -0700710 int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
Doug Felte8e45f22010-03-29 14:58:40 -0700711 if (mCharsValid) {
Doug Felt0c702b82010-05-14 10:55:42 -0700712 ret = wp.getTextRunAdvances(mChars, start, runLen,
713 contextStart, contextLen, flags, null, 0);
Doug Felte8e45f22010-03-29 14:58:40 -0700714 } else {
Doug Felt0c702b82010-05-14 10:55:42 -0700715 int delta = mStart;
716 ret = wp.getTextRunAdvances(mText, delta + start,
717 delta + end, delta + contextStart, delta + contextEnd,
718 flags, null, 0);
Doug Felte8e45f22010-03-29 14:58:40 -0700719 }
720 }
721
722 if (fmi != null) {
Gilles Debunne0bb00092010-12-02 15:50:26 -0800723 expandMetricsFromPaint(fmi, wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700724 }
725
726 if (c != null) {
727 if (runIsRtl) {
728 x -= ret;
729 }
730
731 if (wp.bgColor != 0) {
732 int color = wp.getColor();
733 Paint.Style s = wp.getStyle();
734 wp.setColor(wp.bgColor);
735 wp.setStyle(Paint.Style.FILL);
736
737 c.drawRect(x, top, x + ret, bottom, wp);
738
739 wp.setStyle(s);
740 wp.setColor(color);
741 }
742
Doug Felt0c702b82010-05-14 10:55:42 -0700743 drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
744 x, y + wp.baselineShift);
Doug Felte8e45f22010-03-29 14:58:40 -0700745 }
746
747 return runIsRtl ? -ret : ret;
748 }
749
750 /**
751 * Utility function for measuring and rendering a replacement.
752 *
753 * @param replacement the replacement
754 * @param wp the work paint
755 * @param runIndex the run index
756 * @param start the start of the run
757 * @param limit the limit of the run
758 * @param runIsRtl true if the run is right-to-left
759 * @param c the canvas, can be null if not rendering
760 * @param x the edge of the replacement closest to the leading margin
761 * @param top the top of the line
762 * @param y the baseline
763 * @param bottom the bottom of the line
764 * @param fmi receives metrics information, can be null
765 * @param needWidth true if the width of the replacement is needed
Doug Felte8e45f22010-03-29 14:58:40 -0700766 * @return the signed width of the run based on the run direction; only
767 * valid if needWidth is true
768 */
769 private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
770 int runIndex, int start, int limit, boolean runIsRtl, Canvas c,
771 float x, int top, int y, int bottom, FontMetricsInt fmi,
Doug Felt0c702b82010-05-14 10:55:42 -0700772 boolean needWidth) {
Doug Felte8e45f22010-03-29 14:58:40 -0700773
774 float ret = 0;
775
Doug Felt0c702b82010-05-14 10:55:42 -0700776 int textStart = mStart + start;
777 int textLimit = mStart + limit;
778
779 if (needWidth || (c != null && runIsRtl)) {
780 ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
Doug Felte8e45f22010-03-29 14:58:40 -0700781 }
782
Doug Felt0c702b82010-05-14 10:55:42 -0700783 if (c != null) {
784 if (runIsRtl) {
785 x -= ret;
Doug Felte8e45f22010-03-29 14:58:40 -0700786 }
Doug Felt0c702b82010-05-14 10:55:42 -0700787 replacement.draw(c, mText, textStart, textLimit,
788 x, top, y, bottom, wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700789 }
790
791 return runIsRtl ? -ret : ret;
792 }
793
794 /**
795 * Utility function for handling a unidirectional run. The run must not
796 * contain tabs or emoji but can contain styles.
797 *
Doug Felte8e45f22010-03-29 14:58:40 -0700798 * @param runIndex the run index
799 * @param start the line-relative start of the run
Doug Felt0c702b82010-05-14 10:55:42 -0700800 * @param measureLimit the offset to measure to, between start and limit inclusive
Doug Felte8e45f22010-03-29 14:58:40 -0700801 * @param limit the limit of the run
802 * @param runIsRtl true if the run is right-to-left
803 * @param c the canvas, can be null
804 * @param x the end of the run closest to the leading margin
805 * @param top the top of the line
806 * @param y the baseline
807 * @param bottom the bottom of the line
808 * @param fmi receives metrics information, can be null
809 * @param needWidth true if the width is required
Doug Felte8e45f22010-03-29 14:58:40 -0700810 * @return the signed width of the run based on the run direction; only
811 * valid if needWidth is true
812 */
Doug Felt0c702b82010-05-14 10:55:42 -0700813 private float handleRun(int runIndex, int start, int measureLimit,
Doug Felte8e45f22010-03-29 14:58:40 -0700814 int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
Doug Felt0c702b82010-05-14 10:55:42 -0700815 int bottom, FontMetricsInt fmi, boolean needWidth) {
Doug Felte8e45f22010-03-29 14:58:40 -0700816
817 // Shaping needs to take into account context up to metric boundaries,
818 // but rendering needs to take into account character style boundaries.
Doug Felt0c702b82010-05-14 10:55:42 -0700819 // So we iterate through metric runs to get metric bounds,
820 // then within each metric run iterate through character style runs
821 // for the run bounds.
Doug Felte8e45f22010-03-29 14:58:40 -0700822 float ox = x;
Doug Felt0c702b82010-05-14 10:55:42 -0700823 for (int i = start, inext; i < measureLimit; i = inext) {
Doug Felte8e45f22010-03-29 14:58:40 -0700824 TextPaint wp = mWorkPaint;
825 wp.set(mPaint);
826
Doug Felt0c702b82010-05-14 10:55:42 -0700827 int mlimit;
Doug Felte8e45f22010-03-29 14:58:40 -0700828 if (mSpanned == null) {
829 inext = limit;
Doug Felt0c702b82010-05-14 10:55:42 -0700830 mlimit = measureLimit;
Doug Felte8e45f22010-03-29 14:58:40 -0700831 } else {
832 inext = mSpanned.nextSpanTransition(mStart + i, mStart + limit,
833 MetricAffectingSpan.class) - mStart;
834
Doug Felt0c702b82010-05-14 10:55:42 -0700835 mlimit = inext < measureLimit ? inext : measureLimit;
Doug Felte8e45f22010-03-29 14:58:40 -0700836 MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + i,
Doug Felt0c702b82010-05-14 10:55:42 -0700837 mStart + mlimit, MetricAffectingSpan.class);
Doug Felte8e45f22010-03-29 14:58:40 -0700838
839 if (spans.length > 0) {
840 ReplacementSpan replacement = null;
841 for (int j = 0; j < spans.length; j++) {
842 MetricAffectingSpan span = spans[j];
843 if (span instanceof ReplacementSpan) {
844 replacement = (ReplacementSpan)span;
845 } else {
Doug Felt0c702b82010-05-14 10:55:42 -0700846 // We might have a replacement that uses the draw
847 // state, otherwise measure state would suffice.
848 span.updateDrawState(wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700849 }
850 }
851
852 if (replacement != null) {
853 x += handleReplacement(replacement, wp, runIndex, i,
Doug Felt0c702b82010-05-14 10:55:42 -0700854 mlimit, runIsRtl, c, x, top, y, bottom, fmi,
855 needWidth || mlimit < measureLimit);
Doug Felte8e45f22010-03-29 14:58:40 -0700856 continue;
857 }
858 }
859 }
860
Doug Felt0c702b82010-05-14 10:55:42 -0700861 if (mSpanned == null || c == null) {
862 x += handleText(wp, i, mlimit, i, inext, runIsRtl, c, x, top,
863 y, bottom, fmi, needWidth || mlimit < measureLimit);
864 } else {
865 for (int j = i, jnext; j < mlimit; j = jnext) {
866 jnext = mSpanned.nextSpanTransition(mStart + j,
867 mStart + mlimit, CharacterStyle.class) - mStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700868
Doug Felt0c702b82010-05-14 10:55:42 -0700869 CharacterStyle[] spans = mSpanned.getSpans(mStart + j,
870 mStart + jnext, CharacterStyle.class);
Doug Felte8e45f22010-03-29 14:58:40 -0700871
Doug Felt0c702b82010-05-14 10:55:42 -0700872 wp.set(mPaint);
873 for (int k = 0; k < spans.length; k++) {
874 CharacterStyle span = spans[k];
875 span.updateDrawState(wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700876 }
Doug Felt0c702b82010-05-14 10:55:42 -0700877
878 x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
879 top, y, bottom, fmi, needWidth || jnext < measureLimit);
Doug Felte8e45f22010-03-29 14:58:40 -0700880 }
881 }
882 }
883
884 return x - ox;
885 }
886
Doug Felte8e45f22010-03-29 14:58:40 -0700887 /**
888 * Render a text run with the set-up paint.
889 *
890 * @param c the canvas
891 * @param wp the paint used to render the text
Doug Felt0c702b82010-05-14 10:55:42 -0700892 * @param start the start of the run
893 * @param end the end of the run
894 * @param contextStart the start of context for the run
895 * @param contextEnd the end of the context for the run
Doug Felte8e45f22010-03-29 14:58:40 -0700896 * @param runIsRtl true if the run is right-to-left
897 * @param x the x position of the left edge of the run
898 * @param y the baseline of the run
899 */
Doug Felt0c702b82010-05-14 10:55:42 -0700900 private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
901 int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
Doug Felte8e45f22010-03-29 14:58:40 -0700902
Doug Feltf47d7402010-04-21 16:01:52 -0700903 int flags = runIsRtl ? Canvas.DIRECTION_RTL : Canvas.DIRECTION_LTR;
Doug Felte8e45f22010-03-29 14:58:40 -0700904 if (mCharsValid) {
Doug Felt0c702b82010-05-14 10:55:42 -0700905 int count = end - start;
906 int contextCount = contextEnd - contextStart;
907 c.drawTextRun(mChars, start, count, contextStart, contextCount,
908 x, y, flags, wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700909 } else {
Doug Felt0c702b82010-05-14 10:55:42 -0700910 int delta = mStart;
911 c.drawTextRun(mText, delta + start, delta + end,
912 delta + contextStart, delta + contextEnd, x, y, flags, wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700913 }
914 }
915
916 /**
917 * Returns the ascent of the text at start. This is used for scaling
918 * emoji.
919 *
920 * @param pos the line-relative position
921 * @return the ascent of the text at start
922 */
923 float ascent(int pos) {
924 if (mSpanned == null) {
925 return mPaint.ascent();
926 }
927
928 pos += mStart;
929 MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1,
930 MetricAffectingSpan.class);
931 if (spans.length == 0) {
932 return mPaint.ascent();
933 }
934
935 TextPaint wp = mWorkPaint;
936 wp.set(mPaint);
937 for (MetricAffectingSpan span : spans) {
938 span.updateMeasureState(wp);
939 }
940 return wp.ascent();
941 }
942
943 /**
944 * Returns the next tab position.
945 *
946 * @param h the (unsigned) offset from the leading margin
947 * @return the (unsigned) tab position after this offset
948 */
949 float nextTab(float h) {
Doug Feltc982f602010-05-25 11:51:40 -0700950 if (mTabs != null) {
951 return mTabs.nextTab(h);
Doug Felte8e45f22010-03-29 14:58:40 -0700952 }
Doug Feltc982f602010-05-25 11:51:40 -0700953 return TabStops.nextDefaultStop(h, TAB_INCREMENT);
Doug Felte8e45f22010-03-29 14:58:40 -0700954 }
955
956 private static final int TAB_INCREMENT = 20;
957}