blob: 0f8097a948a61364be0d30106fc141808686c134 [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;
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800130 ReplacementSpan[] spans = mSpanned.getSpans(start, limit, ReplacementSpan.class);
131 spans = TextUtils.removeEmptySpans(spans, mSpanned, ReplacementSpan.class);
132 hasReplacement = spans.length > 0;
Doug Felte8e45f22010-03-29 14:58:40 -0700133 }
134
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800135 mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
Doug Felte8e45f22010-03-29 14:58:40 -0700136
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) {
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800150 inext = mSpanned.nextSpanTransition(i, limit, ReplacementSpan.class);
151 ReplacementSpan[] spans = mSpanned.getSpans(i, inext, ReplacementSpan.class);
152 spans = TextUtils.removeEmptySpans(spans, mSpanned, ReplacementSpan.class);
153 if (spans.length > 0) {
154 // transition into a span
Doug Felt0c702b82010-05-14 10:55:42 -0700155 chars[i - start] = '\ufffc';
156 for (int j = i - start + 1, e = inext - start; j < e; ++j) {
157 chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip
158 }
159 }
160 }
161 }
Doug Felte8e45f22010-03-29 14:58:40 -0700162 }
Doug Feltc982f602010-05-25 11:51:40 -0700163 mTabs = tabStops;
Doug Felte8e45f22010-03-29 14:58:40 -0700164 }
165
166 /**
167 * Renders the TextLine.
168 *
169 * @param c the canvas to render on
170 * @param x the leading margin position
171 * @param top the top of the line
172 * @param y the baseline
173 * @param bottom the bottom of the line
174 */
175 void draw(Canvas c, float x, int top, int y, int bottom) {
176 if (!mHasTabs) {
177 if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
178 drawRun(c, 0, 0, mLen, false, x, top, y, bottom, false);
179 return;
180 }
181 if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
182 drawRun(c, 0, 0, mLen, true, x, top, y, bottom, false);
183 return;
184 }
185 }
186
187 float h = 0;
188 int[] runs = mDirections.mDirections;
189 RectF emojiRect = null;
190
191 int lastRunIndex = runs.length - 2;
192 for (int i = 0; i < runs.length; i += 2) {
193 int runStart = runs[i];
194 int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
195 if (runLimit > mLen) {
196 runLimit = mLen;
197 }
198 boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
199
200 int segstart = runStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700201 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);
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800632 spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
Doug Felt0c702b82010-05-14 10:55:42 -0700633
634 if (spans.length > 0) {
635 ReplacementSpan replacement = null;
636 for (int j = 0; j < spans.length; j++) {
637 MetricAffectingSpan span = spans[j];
638 if (span instanceof ReplacementSpan) {
639 replacement = (ReplacementSpan)span;
640 } else {
641 span.updateMeasureState(wp);
642 }
643 }
644
645 if (replacement != null) {
646 // If we have a replacement span, we're moving either to
647 // the start or end of this span.
648 return after ? spanLimit : spanStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700649 }
650 }
Doug Felte8e45f22010-03-29 14:58:40 -0700651 }
652
Doug Felt0c702b82010-05-14 10:55:42 -0700653 int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
654 int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
655 if (mCharsValid) {
656 return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart,
657 flags, offset, cursorOpt);
658 } else {
659 return wp.getTextRunCursor(mText, mStart + spanStart,
Gilles Debunne345cb032010-06-16 17:13:23 -0700660 mStart + spanLimit, flags, mStart + offset, cursorOpt) - mStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700661 }
Doug Felte8e45f22010-03-29 14:58:40 -0700662 }
663
664 /**
Gilles Debunne0bb00092010-12-02 15:50:26 -0800665 * @param wp
666 */
667 private static void expandMetricsFromPaint(FontMetricsInt fmi, TextPaint wp) {
668 final int previousTop = fmi.top;
669 final int previousAscent = fmi.ascent;
670 final int previousDescent = fmi.descent;
671 final int previousBottom = fmi.bottom;
672 final int previousLeading = fmi.leading;
673
674 wp.getFontMetricsInt(fmi);
675
676 fmi.top = Math.min(fmi.top, previousTop);
677 fmi.ascent = Math.min(fmi.ascent, previousAscent);
678 fmi.descent = Math.max(fmi.descent, previousDescent);
679 fmi.bottom = Math.max(fmi.bottom, previousBottom);
680 fmi.leading = Math.max(fmi.leading, previousLeading);
681 }
682
683 /**
Doug Felte8e45f22010-03-29 14:58:40 -0700684 * Utility function for measuring and rendering text. The text must
685 * not include a tab or emoji.
686 *
687 * @param wp the working paint
688 * @param start the start of the text
Doug Felt0c702b82010-05-14 10:55:42 -0700689 * @param end the end of the text
Doug Felte8e45f22010-03-29 14:58:40 -0700690 * @param runIsRtl true if the run is right-to-left
691 * @param c the canvas, can be null if rendering is not needed
692 * @param x the edge of the run closest to the leading margin
693 * @param top the top of the line
694 * @param y the baseline
695 * @param bottom the bottom of the line
696 * @param fmi receives metrics information, can be null
697 * @param needWidth true if the width of the run is needed
698 * @return the signed width of the run based on the run direction; only
699 * valid if needWidth is true
700 */
Doug Felt0c702b82010-05-14 10:55:42 -0700701 private float handleText(TextPaint wp, int start, int end,
702 int contextStart, int contextEnd, boolean runIsRtl,
703 Canvas c, float x, int top, int y, int bottom,
Doug Felte8e45f22010-03-29 14:58:40 -0700704 FontMetricsInt fmi, boolean needWidth) {
705
706 float ret = 0;
707
Doug Felt0c702b82010-05-14 10:55:42 -0700708 int runLen = end - start;
709 int contextLen = contextEnd - contextStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700710 if (needWidth || (c != null && (wp.bgColor != 0 || runIsRtl))) {
Doug Felt0c702b82010-05-14 10:55:42 -0700711 int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
Doug Felte8e45f22010-03-29 14:58:40 -0700712 if (mCharsValid) {
Doug Felt0c702b82010-05-14 10:55:42 -0700713 ret = wp.getTextRunAdvances(mChars, start, runLen,
714 contextStart, contextLen, flags, null, 0);
Doug Felte8e45f22010-03-29 14:58:40 -0700715 } else {
Doug Felt0c702b82010-05-14 10:55:42 -0700716 int delta = mStart;
717 ret = wp.getTextRunAdvances(mText, delta + start,
718 delta + end, delta + contextStart, delta + contextEnd,
719 flags, null, 0);
Doug Felte8e45f22010-03-29 14:58:40 -0700720 }
721 }
722
723 if (fmi != null) {
Gilles Debunne0bb00092010-12-02 15:50:26 -0800724 expandMetricsFromPaint(fmi, wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700725 }
726
727 if (c != null) {
728 if (runIsRtl) {
729 x -= ret;
730 }
731
732 if (wp.bgColor != 0) {
733 int color = wp.getColor();
734 Paint.Style s = wp.getStyle();
735 wp.setColor(wp.bgColor);
736 wp.setStyle(Paint.Style.FILL);
737
738 c.drawRect(x, top, x + ret, bottom, wp);
739
740 wp.setStyle(s);
741 wp.setColor(color);
742 }
743
Doug Felt0c702b82010-05-14 10:55:42 -0700744 drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
745 x, y + wp.baselineShift);
Doug Felte8e45f22010-03-29 14:58:40 -0700746 }
747
748 return runIsRtl ? -ret : ret;
749 }
750
751 /**
752 * Utility function for measuring and rendering a replacement.
753 *
754 * @param replacement the replacement
755 * @param wp the work paint
756 * @param runIndex the run index
757 * @param start the start of the run
758 * @param limit the limit of the run
759 * @param runIsRtl true if the run is right-to-left
760 * @param c the canvas, can be null if not rendering
761 * @param x the edge of the replacement closest to the leading margin
762 * @param top the top of the line
763 * @param y the baseline
764 * @param bottom the bottom of the line
765 * @param fmi receives metrics information, can be null
766 * @param needWidth true if the width of the replacement is needed
Doug Felte8e45f22010-03-29 14:58:40 -0700767 * @return the signed width of the run based on the run direction; only
768 * valid if needWidth is true
769 */
770 private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
771 int runIndex, int start, int limit, boolean runIsRtl, Canvas c,
772 float x, int top, int y, int bottom, FontMetricsInt fmi,
Doug Felt0c702b82010-05-14 10:55:42 -0700773 boolean needWidth) {
Doug Felte8e45f22010-03-29 14:58:40 -0700774
775 float ret = 0;
776
Doug Felt0c702b82010-05-14 10:55:42 -0700777 int textStart = mStart + start;
778 int textLimit = mStart + limit;
779
780 if (needWidth || (c != null && runIsRtl)) {
781 ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
Doug Felte8e45f22010-03-29 14:58:40 -0700782 }
783
Doug Felt0c702b82010-05-14 10:55:42 -0700784 if (c != null) {
785 if (runIsRtl) {
786 x -= ret;
Doug Felte8e45f22010-03-29 14:58:40 -0700787 }
Doug Felt0c702b82010-05-14 10:55:42 -0700788 replacement.draw(c, mText, textStart, textLimit,
789 x, top, y, bottom, wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700790 }
791
792 return runIsRtl ? -ret : ret;
793 }
794
795 /**
796 * Utility function for handling a unidirectional run. The run must not
797 * contain tabs or emoji but can contain styles.
798 *
Doug Felte8e45f22010-03-29 14:58:40 -0700799 * @param runIndex the run index
800 * @param start the line-relative start of the run
Doug Felt0c702b82010-05-14 10:55:42 -0700801 * @param measureLimit the offset to measure to, between start and limit inclusive
Doug Felte8e45f22010-03-29 14:58:40 -0700802 * @param limit the limit of the run
803 * @param runIsRtl true if the run is right-to-left
804 * @param c the canvas, can be null
805 * @param x the end of the run closest to the leading margin
806 * @param top the top of the line
807 * @param y the baseline
808 * @param bottom the bottom of the line
809 * @param fmi receives metrics information, can be null
810 * @param needWidth true if the width is required
Doug Felte8e45f22010-03-29 14:58:40 -0700811 * @return the signed width of the run based on the run direction; only
812 * valid if needWidth is true
813 */
Doug Felt0c702b82010-05-14 10:55:42 -0700814 private float handleRun(int runIndex, int start, int measureLimit,
Doug Felte8e45f22010-03-29 14:58:40 -0700815 int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
Doug Felt0c702b82010-05-14 10:55:42 -0700816 int bottom, FontMetricsInt fmi, boolean needWidth) {
Doug Felte8e45f22010-03-29 14:58:40 -0700817
Gilles Debunnef483e512011-04-28 15:08:54 -0700818 // Case of an empty line, make sure we update fmi according to mPaint
819 if (start == measureLimit) {
820 TextPaint wp = mWorkPaint;
821 wp.set(mPaint);
822 return handleText(wp, 0, 0, 0, 0, runIsRtl, c, x, top, y, bottom, fmi, needWidth);
823 }
824
Doug Felte8e45f22010-03-29 14:58:40 -0700825 // Shaping needs to take into account context up to metric boundaries,
826 // but rendering needs to take into account character style boundaries.
Doug Felt0c702b82010-05-14 10:55:42 -0700827 // So we iterate through metric runs to get metric bounds,
828 // then within each metric run iterate through character style runs
829 // for the run bounds.
Doug Felte8e45f22010-03-29 14:58:40 -0700830 float ox = x;
Doug Felt0c702b82010-05-14 10:55:42 -0700831 for (int i = start, inext; i < measureLimit; i = inext) {
Doug Felte8e45f22010-03-29 14:58:40 -0700832 TextPaint wp = mWorkPaint;
833 wp.set(mPaint);
834
Doug Felt0c702b82010-05-14 10:55:42 -0700835 int mlimit;
Doug Felte8e45f22010-03-29 14:58:40 -0700836 if (mSpanned == null) {
837 inext = limit;
Doug Felt0c702b82010-05-14 10:55:42 -0700838 mlimit = measureLimit;
Doug Felte8e45f22010-03-29 14:58:40 -0700839 } else {
840 inext = mSpanned.nextSpanTransition(mStart + i, mStart + limit,
841 MetricAffectingSpan.class) - mStart;
842
Doug Felt0c702b82010-05-14 10:55:42 -0700843 mlimit = inext < measureLimit ? inext : measureLimit;
Doug Felte8e45f22010-03-29 14:58:40 -0700844 MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + i,
Doug Felt0c702b82010-05-14 10:55:42 -0700845 mStart + mlimit, MetricAffectingSpan.class);
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800846 spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
Doug Felte8e45f22010-03-29 14:58:40 -0700847
848 if (spans.length > 0) {
849 ReplacementSpan replacement = null;
850 for (int j = 0; j < spans.length; j++) {
851 MetricAffectingSpan span = spans[j];
852 if (span instanceof ReplacementSpan) {
853 replacement = (ReplacementSpan)span;
854 } else {
Doug Felt0c702b82010-05-14 10:55:42 -0700855 // We might have a replacement that uses the draw
856 // state, otherwise measure state would suffice.
857 span.updateDrawState(wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700858 }
859 }
860
861 if (replacement != null) {
862 x += handleReplacement(replacement, wp, runIndex, i,
Doug Felt0c702b82010-05-14 10:55:42 -0700863 mlimit, runIsRtl, c, x, top, y, bottom, fmi,
864 needWidth || mlimit < measureLimit);
Doug Felte8e45f22010-03-29 14:58:40 -0700865 continue;
866 }
867 }
868 }
869
Doug Felt0c702b82010-05-14 10:55:42 -0700870 if (mSpanned == null || c == null) {
871 x += handleText(wp, i, mlimit, i, inext, runIsRtl, c, x, top,
872 y, bottom, fmi, needWidth || mlimit < measureLimit);
873 } else {
874 for (int j = i, jnext; j < mlimit; j = jnext) {
875 jnext = mSpanned.nextSpanTransition(mStart + j,
876 mStart + mlimit, CharacterStyle.class) - mStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700877
Doug Felt0c702b82010-05-14 10:55:42 -0700878 CharacterStyle[] spans = mSpanned.getSpans(mStart + j,
879 mStart + jnext, CharacterStyle.class);
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800880 spans = TextUtils.removeEmptySpans(spans, mSpanned, CharacterStyle.class);
Doug Felte8e45f22010-03-29 14:58:40 -0700881
Doug Felt0c702b82010-05-14 10:55:42 -0700882 wp.set(mPaint);
883 for (int k = 0; k < spans.length; k++) {
884 CharacterStyle span = spans[k];
885 span.updateDrawState(wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700886 }
Doug Felt0c702b82010-05-14 10:55:42 -0700887
888 x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
889 top, y, bottom, fmi, needWidth || jnext < measureLimit);
Doug Felte8e45f22010-03-29 14:58:40 -0700890 }
891 }
892 }
893
894 return x - ox;
895 }
896
Doug Felte8e45f22010-03-29 14:58:40 -0700897 /**
898 * Render a text run with the set-up paint.
899 *
900 * @param c the canvas
901 * @param wp the paint used to render the text
Doug Felt0c702b82010-05-14 10:55:42 -0700902 * @param start the start of the run
903 * @param end the end of the run
904 * @param contextStart the start of context for the run
905 * @param contextEnd the end of the context for the run
Doug Felte8e45f22010-03-29 14:58:40 -0700906 * @param runIsRtl true if the run is right-to-left
907 * @param x the x position of the left edge of the run
908 * @param y the baseline of the run
909 */
Doug Felt0c702b82010-05-14 10:55:42 -0700910 private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
911 int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
Doug Felte8e45f22010-03-29 14:58:40 -0700912
Doug Feltf47d7402010-04-21 16:01:52 -0700913 int flags = runIsRtl ? Canvas.DIRECTION_RTL : Canvas.DIRECTION_LTR;
Doug Felte8e45f22010-03-29 14:58:40 -0700914 if (mCharsValid) {
Doug Felt0c702b82010-05-14 10:55:42 -0700915 int count = end - start;
916 int contextCount = contextEnd - contextStart;
917 c.drawTextRun(mChars, start, count, contextStart, contextCount,
918 x, y, flags, wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700919 } else {
Doug Felt0c702b82010-05-14 10:55:42 -0700920 int delta = mStart;
921 c.drawTextRun(mText, delta + start, delta + end,
922 delta + contextStart, delta + contextEnd, x, y, flags, wp);
Doug Felte8e45f22010-03-29 14:58:40 -0700923 }
924 }
925
926 /**
927 * Returns the ascent of the text at start. This is used for scaling
928 * emoji.
929 *
930 * @param pos the line-relative position
931 * @return the ascent of the text at start
932 */
933 float ascent(int pos) {
934 if (mSpanned == null) {
935 return mPaint.ascent();
936 }
937
938 pos += mStart;
939 MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1,
940 MetricAffectingSpan.class);
941 if (spans.length == 0) {
942 return mPaint.ascent();
943 }
944
945 TextPaint wp = mWorkPaint;
946 wp.set(mPaint);
947 for (MetricAffectingSpan span : spans) {
948 span.updateMeasureState(wp);
949 }
950 return wp.ascent();
951 }
952
953 /**
954 * Returns the next tab position.
955 *
956 * @param h the (unsigned) offset from the leading margin
957 * @return the (unsigned) tab position after this offset
958 */
959 float nextTab(float h) {
Doug Feltc982f602010-05-25 11:51:40 -0700960 if (mTabs != null) {
961 return mTabs.nextTab(h);
Doug Felte8e45f22010-03-29 14:58:40 -0700962 }
Doug Feltc982f602010-05-25 11:51:40 -0700963 return TabStops.nextDefaultStop(h, TAB_INCREMENT);
Doug Felte8e45f22010-03-29 14:58:40 -0700964 }
965
966 private static final int TAB_INCREMENT = 20;
967}