TextLine.java revision c982f60e982c1d2df9f115ed9a5c3ef3643d0892
1/*
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;
24import android.graphics.RectF;
25import android.graphics.Paint.FontMetricsInt;
26import android.icu.text.ArabicShaping;
27import android.text.Layout.Directions;
28import android.text.Layout.TabStops;
29import android.text.style.CharacterStyle;
30import android.text.style.MetricAffectingSpan;
31import android.text.style.ReplacementSpan;
32import android.util.Log;
33
34/**
35 * Represents a line of styled text, for measuring in visual order and
36 * for rendering.
37 *
38 * <p>Get a new instance using obtain(), and when finished with it, return it
39 * to the pool using recycle().
40 *
41 * <p>Call set to prepare the instance for use, then either draw, measure,
42 * metrics, or caretToLeftRightOf.
43 *
44 * @hide
45 */
46class TextLine {
47    private TextPaint mPaint;
48    private CharSequence mText;
49    private int mStart;
50    private int mLen;
51    private int mDir;
52    private Directions mDirections;
53    private boolean mHasTabs;
54    private TabStops mTabs;
55
56    private char[] mChars;
57    private boolean mCharsValid;
58    private Spanned mSpanned;
59    private TextPaint mWorkPaint = new TextPaint();
60    private int mPreppedIndex;
61    private int mPreppedLimit;
62
63    private static TextLine[] cached = new TextLine[3];
64
65    /**
66     * Returns a new TextLine from the shared pool.
67     *
68     * @return an uninitialized TextLine
69     */
70    static TextLine obtain() {
71        TextLine tl;
72        synchronized (cached) {
73            for (int i = cached.length; --i >= 0;) {
74                if (cached[i] != null) {
75                    tl = cached[i];
76                    cached[i] = null;
77                    return tl;
78                }
79            }
80        }
81        tl = new TextLine();
82        Log.e("TLINE", "new: " + tl);
83        return tl;
84    }
85
86    /**
87     * Puts a TextLine back into the shared pool. Do not use this TextLine once
88     * it has been returned.
89     * @param tl the textLine
90     * @return null, as a convenience from clearing references to the provided
91     * TextLine
92     */
93    static TextLine recycle(TextLine tl) {
94        tl.mText = null;
95        tl.mPaint = null;
96        tl.mDirections = null;
97        if (tl.mLen < 250) {
98            synchronized(cached) {
99                for (int i = 0; i < cached.length; ++i) {
100                    if (cached[i] == null) {
101                        cached[i] = tl;
102                        break;
103                    }
104                }
105            }
106        }
107        return null;
108    }
109
110    /**
111     * Initializes a TextLine and prepares it for use.
112     *
113     * @param paint the base paint for the line
114     * @param text the text, can be Styled
115     * @param start the start of the line relative to the text
116     * @param limit the limit of the line relative to the text
117     * @param dir the paragraph direction of this line
118     * @param directions the directions information of this line
119     * @param hasTabs true if the line might contain tabs or emoji
120     * @param tabStops the tabStops. Can be null.
121     */
122    void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
123            Directions directions, boolean hasTabs, TabStops tabStops) {
124        mPaint = paint;
125        mText = text;
126        mStart = start;
127        mLen = limit - start;
128        mDir = dir;
129        mDirections = directions;
130        mHasTabs = hasTabs;
131        mSpanned = null;
132        mPreppedIndex = 0;
133        mPreppedLimit = 0;
134
135        boolean hasReplacement = false;
136        if (text instanceof Spanned) {
137            mSpanned = (Spanned) text;
138            hasReplacement = mSpanned.getSpans(start, limit,
139                    ReplacementSpan.class).length > 0;
140        }
141
142        mCharsValid = hasReplacement || hasTabs ||
143            directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
144
145        if (mCharsValid) {
146            if (mChars == null || mChars.length < mLen) {
147                mChars = new char[ArrayUtils.idealCharArraySize(mLen)];
148            }
149            TextUtils.getChars(text, start, limit, mChars, 0);
150        }
151        mTabs = tabStops;
152    }
153
154    /**
155     * Renders the TextLine.
156     *
157     * @param c the canvas to render on
158     * @param x the leading margin position
159     * @param top the top of the line
160     * @param y the baseline
161     * @param bottom the bottom of the line
162     */
163    void draw(Canvas c, float x, int top, int y, int bottom) {
164        if (!mHasTabs) {
165            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
166                drawRun(c, 0, 0, mLen, false, x, top, y, bottom, false);
167                return;
168            }
169            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
170                drawRun(c, 0, 0, mLen, true, x, top, y, bottom, false);
171                return;
172            }
173        }
174
175        float h = 0;
176        int[] runs = mDirections.mDirections;
177        RectF emojiRect = null;
178
179        int lastRunIndex = runs.length - 2;
180        for (int i = 0; i < runs.length; i += 2) {
181            int runStart = runs[i];
182            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
183            if (runLimit > mLen) {
184                runLimit = mLen;
185            }
186            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
187
188            int segstart = runStart;
189            char[] chars = mChars;
190            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
191                int codept = 0;
192                Bitmap bm = null;
193
194                if (mHasTabs && j < runLimit) {
195                    codept = mChars[j];
196                    if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) {
197                        codept = Character.codePointAt(mChars, j);
198                        if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) {
199                            bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
200                        } else if (codept > 0xffff) {
201                            ++j;
202                            continue;
203                        }
204                    }
205                }
206
207                if (j == runLimit || codept == '\t' || bm != null) {
208                    h += drawRun(c, i, segstart, j, runIsRtl, x+h, top, y, bottom,
209                            i != lastRunIndex || j != mLen);
210
211                    if (codept == '\t') {
212                        h = mDir * nextTab(h * mDir);
213                    } else if (bm != null) {
214                        float bmAscent = ascent(j);
215                        float bitmapHeight = bm.getHeight();
216                        float scale = -bmAscent / bitmapHeight;
217                        float width = bm.getWidth() * scale;
218
219                        if (emojiRect == null) {
220                            emojiRect = new RectF();
221                        }
222                        emojiRect.set(x + h, y + bmAscent,
223                                x + h + width, y);
224                        c.drawBitmap(bm, null, emojiRect, mPaint);
225                        h += width;
226                        j++;
227                    }
228                    segstart = j + 1;
229                }
230            }
231        }
232    }
233
234    /**
235     * Returns metrics information for the entire line.
236     *
237     * @param fmi receives font metrics information, can be null
238     * @return the signed width of the line
239     */
240    float metrics(FontMetricsInt fmi) {
241        return measure(mLen, false, fmi);
242    }
243
244    /**
245     * Returns information about a position on the line.
246     *
247     * @param offset the line-relative character offset, between 0 and the
248     * line length, inclusive
249     * @param trailing true to measure the trailing edge of the character
250     * before offset, false to measure the leading edge of the character
251     * at offset.
252     * @param fmi receives metrics information about the requested
253     * character, can be null.
254     * @return the signed offset from the leading margin to the requested
255     * character edge.
256     */
257    float measure(int offset, boolean trailing, FontMetricsInt fmi) {
258        int target = trailing ? offset - 1 : offset;
259        if (target < 0) {
260            return 0;
261        }
262
263        float h = 0;
264
265        if (!mHasTabs) {
266            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
267                return measureRun( 0, 0, offset, mLen, false, fmi);
268            }
269            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
270                return measureRun(0, 0, offset, mLen, true, fmi);
271            }
272        }
273
274        char[] chars = mChars;
275        int[] runs = mDirections.mDirections;
276        for (int i = 0; i < runs.length; i += 2) {
277            int runStart = runs[i];
278            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
279            if (runLimit > mLen) {
280                runLimit = mLen;
281            }
282            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
283
284            int segstart = runStart;
285            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
286                int codept = 0;
287                Bitmap bm = null;
288
289                if (mHasTabs && j < runLimit) {
290                    codept = chars[j];
291                    if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) {
292                        codept = Character.codePointAt(chars, j);
293                        if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) {
294                            bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
295                        } else if (codept > 0xffff) {
296                            ++j;
297                            continue;
298                        }
299                    }
300                }
301
302                if (j == runLimit || codept == '\t' || bm != null) {
303                    boolean inSegment = target >= segstart && target < j;
304
305                    boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
306                    if (inSegment && advance) {
307                        return h += measureRun(i, segstart, offset, j, runIsRtl, fmi);
308                    }
309
310                    float w = measureRun(i, segstart, j, j, runIsRtl, fmi);
311                    h += advance ? w : -w;
312
313                    if (inSegment) {
314                        return h += measureRun(i, segstart, offset, j, runIsRtl, null);
315                    }
316
317                    if (codept == '\t') {
318                        if (offset == j) {
319                            return h;
320                        }
321                        h = mDir * nextTab(h * mDir);
322                        if (target == j) {
323                            return h;
324                        }
325                    }
326
327                    if (bm != null) {
328                        float bmAscent = ascent(j);
329                        float wid = bm.getWidth() * -bmAscent / bm.getHeight();
330                        h += mDir * wid;
331                        j++;
332                    }
333
334                    segstart = j + 1;
335                }
336            }
337        }
338
339        return h;
340    }
341
342    /**
343     * Draws a unidirectional (but possibly multi-styled) run of text.
344     *
345     * @param c the canvas to draw on
346     * @param runIndex the index of this directional run
347     * @param start the line-relative start
348     * @param limit the line-relative limit
349     * @param runIsRtl true if the run is right-to-left
350     * @param x the position of the run that is closest to the leading margin
351     * @param top the top of the line
352     * @param y the baseline
353     * @param bottom the bottom of the line
354     * @param needWidth true if the width value is required.
355     * @return the signed width of the run, based on the paragraph direction.
356     * Only valid if needWidth is true.
357     */
358    private float drawRun(Canvas c, int runIndex, int start,
359            int limit, boolean runIsRtl, float x, int top, int y, int bottom,
360            boolean needWidth) {
361
362        if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
363            float w = -measureRun(runIndex, start, limit, limit, runIsRtl, null);
364            handleRun(runIndex, start, limit, limit, runIsRtl, c, x + w, top,
365                    y, bottom, null, false, PREP_NONE);
366            return w;
367        }
368
369        return handleRun(runIndex, start, limit, limit, runIsRtl, c, x, top,
370                y, bottom, null, needWidth, PREP_NEEDED);
371    }
372
373    /**
374     * Measures a unidirectional (but possibly multi-styled) run of text.
375     *
376     * @param runIndex the run index
377     * @param start the line-relative start of the run
378     * @param offset the offset to measure to, between start and limit inclusive
379     * @param limit the line-relative limit of the run
380     * @param runIsRtl true if the run is right-to-left
381     * @param fmi receives metrics information about the requested
382     * run, can be null.
383     * @return the signed width from the start of the run to the leading edge
384     * of the character at offset, based on the run (not paragraph) direction
385     */
386    private float measureRun(int runIndex, int start,
387            int offset, int limit, boolean runIsRtl, FontMetricsInt fmi) {
388        return handleRun(runIndex, start, offset, limit, runIsRtl, null,
389                0, 0, 0, 0, fmi, true, PREP_NEEDED);
390    }
391
392    /**
393     * Prepares a run for measurement or rendering.  This ensures that any
394     * required shaping of the text in the run has been performed so that
395     * measurements reflect the shaped text.
396     *
397     * @param runIndex the run index
398     * @param start the line-relative start of the run
399     * @param limit the line-relative limit of the run
400     * @param runIsRtl true if the run is right-to-left
401     */
402    private void prepRun(int runIndex, int start, int limit,
403            boolean runIsRtl) {
404        handleRun(runIndex, start, limit, limit, runIsRtl, null, 0, 0, 0,
405                0, null, false, PREP_ONLY);
406    }
407
408    /**
409     * Walk the cursor through this line, skipping conjuncts and
410     * zero-width characters.
411     *
412     * <p>This function cannot properly walk the cursor off the ends of the line
413     * since it does not know about any shaping on the previous/following line
414     * that might affect the cursor position. Callers must either avoid these
415     * situations or handle the result specially.
416     *
417     * <p>The paint is required because the region around the cursor might not
418     * have been formatted yet, and the valid positions can depend on the glyphs
419     * used to render the text, which in turn depends on the paint.
420     *
421     * @param paint the base paint of the line
422     * @param cursor the starting position of the cursor, between 0 and the
423     * length of the line, inclusive
424     * @param toLeft true if the caret is moving to the left.
425     * @return the new offset.  If it is less than 0 or greater than the length
426     * of the line, the previous/following line should be examined to get the
427     * actual offset.
428     */
429    int getOffsetToLeftRightOf(int cursor, boolean toLeft) {
430        // 1) The caret marks the leading edge of a character. The character
431        // logically before it might be on a different level, and the active caret
432        // position is on the character at the lower level. If that character
433        // was the previous character, the caret is on its trailing edge.
434        // 2) Take this character/edge and move it in the indicated direction.
435        // This gives you a new character and a new edge.
436        // 3) This position is between two visually adjacent characters.  One of
437        // these might be at a lower level.  The active position is on the
438        // character at the lower level.
439        // 4) If the active position is on the trailing edge of the character,
440        // the new caret position is the following logical character, else it
441        // is the character.
442
443        int lineStart = 0;
444        int lineEnd = mLen;
445        boolean paraIsRtl = mDir == -1;
446        int[] runs = mDirections.mDirections;
447
448        int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1;
449        boolean trailing = false;
450
451        if (cursor == lineStart) {
452            runIndex = -2;
453        } else if (cursor == lineEnd) {
454            runIndex = runs.length;
455        } else {
456          // First, get information about the run containing the character with
457          // the active caret.
458          for (runIndex = 0; runIndex < runs.length; runIndex += 2) {
459            runStart = lineStart + runs[runIndex];
460            if (cursor >= runStart) {
461              runLimit = runStart + (runs[runIndex+1] & Layout.RUN_LENGTH_MASK);
462              if (runLimit > lineEnd) {
463                  runLimit = lineEnd;
464              }
465              if (cursor < runLimit) {
466                runLevel = (runs[runIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
467                    Layout.RUN_LEVEL_MASK;
468                if (cursor == runStart) {
469                  // The caret is on a run boundary, see if we should
470                  // use the position on the trailing edge of the previous
471                  // logical character instead.
472                  int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit;
473                  int pos = cursor - 1;
474                  for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) {
475                    prevRunStart = lineStart + runs[prevRunIndex];
476                    if (pos >= prevRunStart) {
477                      prevRunLimit = prevRunStart +
478                          (runs[prevRunIndex+1] & Layout.RUN_LENGTH_MASK);
479                      if (prevRunLimit > lineEnd) {
480                          prevRunLimit = lineEnd;
481                      }
482                      if (pos < prevRunLimit) {
483                        prevRunLevel = (runs[prevRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT)
484                            & Layout.RUN_LEVEL_MASK;
485                        if (prevRunLevel < runLevel) {
486                          // Start from logically previous character.
487                          runIndex = prevRunIndex;
488                          runLevel = prevRunLevel;
489                          runStart = prevRunStart;
490                          runLimit = prevRunLimit;
491                          trailing = true;
492                          break;
493                        }
494                      }
495                    }
496                  }
497                }
498                break;
499              }
500            }
501          }
502
503          // caret might be == lineEnd.  This is generally a space or paragraph
504          // separator and has an associated run, but might be the end of
505          // text, in which case it doesn't.  If that happens, we ran off the
506          // end of the run list, and runIndex == runs.length.  In this case,
507          // we are at a run boundary so we skip the below test.
508          if (runIndex != runs.length) {
509              boolean runIsRtl = (runLevel & 0x1) != 0;
510              boolean advance = toLeft == runIsRtl;
511              if (cursor != (advance ? runLimit : runStart) || advance != trailing) {
512                  // Moving within or into the run, so we can move logically.
513                  prepRun(runIndex, runStart, runLimit, runIsRtl);
514                  newCaret = getOffsetBeforeAfter(runIndex, cursor, advance);
515                  // If the new position is internal to the run, we're at the strong
516                  // position already so we're finished.
517                  if (newCaret != (advance ? runLimit : runStart)) {
518                      return newCaret;
519                  }
520              }
521          }
522        }
523
524        // If newCaret is -1, we're starting at a run boundary and crossing
525        // into another run. Otherwise we've arrived at a run boundary, and
526        // need to figure out which character to attach to.  Note we might
527        // need to run this twice, if we cross a run boundary and end up at
528        // another run boundary.
529        while (true) {
530          boolean advance = toLeft == paraIsRtl;
531          int otherRunIndex = runIndex + (advance ? 2 : -2);
532          if (otherRunIndex >= 0 && otherRunIndex < runs.length) {
533            int otherRunStart = lineStart + runs[otherRunIndex];
534            int otherRunLimit = otherRunStart +
535            (runs[otherRunIndex+1] & Layout.RUN_LENGTH_MASK);
536            if (otherRunLimit > lineEnd) {
537                otherRunLimit = lineEnd;
538            }
539            int otherRunLevel = (runs[otherRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
540                Layout.RUN_LEVEL_MASK;
541            boolean otherRunIsRtl = (otherRunLevel & 1) != 0;
542
543            advance = toLeft == otherRunIsRtl;
544            if (newCaret == -1) {
545                prepRun(otherRunIndex, otherRunStart, otherRunLimit,
546                        otherRunIsRtl);
547                newCaret = getOffsetBeforeAfter(otherRunIndex,
548                        advance ? otherRunStart : otherRunLimit, advance);
549                if (newCaret == (advance ? otherRunLimit : otherRunStart)) {
550                    // Crossed and ended up at a new boundary,
551                    // repeat a second and final time.
552                    runIndex = otherRunIndex;
553                    runLevel = otherRunLevel;
554                    continue;
555                }
556                break;
557            }
558
559            // The new caret is at a boundary.
560            if (otherRunLevel < runLevel) {
561              // The strong character is in the other run.
562              newCaret = advance ? otherRunStart : otherRunLimit;
563            }
564            break;
565          }
566
567          if (newCaret == -1) {
568              // We're walking off the end of the line.  The paragraph
569              // level is always equal to or lower than any internal level, so
570              // the boundaries get the strong caret.
571              newCaret = getOffsetBeforeAfter(-1, cursor, advance);
572              break;
573          }
574
575          // Else we've arrived at the end of the line.  That's a strong position.
576          // We might have arrived here by crossing over a run with no internal
577          // breaks and dropping out of the above loop before advancing one final
578          // time, so reset the caret.
579          // Note, we use '<=' below to handle a situation where the only run
580          // on the line is a counter-directional run.  If we're not advancing,
581          // we can end up at the 'lineEnd' position but the caret we want is at
582          // the lineStart.
583          if (newCaret <= lineEnd) {
584              newCaret = advance ? lineEnd : lineStart;
585          }
586          break;
587        }
588
589        return newCaret;
590    }
591
592    /**
593     * Returns the next valid offset within this directional run, skipping
594     * conjuncts and zero-width characters.  This should not be called to walk
595     * off the end of the run.
596     *
597     * @param runIndex the run index
598     * @param offset the offset
599     * @param after true if the new offset should logically follow the provided
600     * offset
601     * @return the new offset
602     */
603    private int getOffsetBeforeAfter(int runIndex, int offset, boolean after) {
604        // XXX note currently there is no special handling of zero-width
605        // combining marks, since the only analysis involves mock shaping.
606
607        boolean offEnd = offset == (after ? mLen : 0);
608        if (runIndex >= 0 && !offEnd && mCharsValid) {
609            char[] chars = mChars;
610            if (after) {
611                int cp = Character.codePointAt(chars, offset, mLen);
612                if (cp >= 0x10000) {
613                    ++offset;
614                }
615                while (++offset < mLen && chars[offset] == '\ufeff'){}
616            } else {
617                while (--offset >= 0 && chars[offset] == '\ufeff'){}
618                int cp = Character.codePointBefore(chars, offset + 1);
619                if (cp >= 0x10000) {
620                    --offset;
621                }
622            }
623            return offset;
624        }
625
626        if (after) {
627            return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart;
628        }
629        return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart;
630    }
631
632    /**
633     * Utility function for measuring and rendering text.  The text must
634     * not include a tab or emoji.
635     *
636     * @param wp the working paint
637     * @param start the start of the text
638     * @param limit the limit of the text
639     * @param runIsRtl true if the run is right-to-left
640     * @param c the canvas, can be null if rendering is not needed
641     * @param x the edge of the run closest to the leading margin
642     * @param top the top of the line
643     * @param y the baseline
644     * @param bottom the bottom of the line
645     * @param fmi receives metrics information, can be null
646     * @param needWidth true if the width of the run is needed
647     * @return the signed width of the run based on the run direction; only
648     * valid if needWidth is true
649     */
650    private float handleText(TextPaint wp, int start, int limit,
651            boolean runIsRtl, Canvas c, float x, int top, int y, int bottom,
652            FontMetricsInt fmi, boolean needWidth) {
653
654        float ret = 0;
655
656        int runLen = limit - start;
657        if (needWidth || (c != null && (wp.bgColor != 0 || runIsRtl))) {
658            if (mCharsValid) {
659                ret = wp.measureText(mChars, start, runLen);
660            } else {
661                ret = wp.measureText(mText, mStart + start,
662                        mStart + start + runLen);
663            }
664        }
665
666        if (fmi != null) {
667            wp.getFontMetricsInt(fmi);
668        }
669
670        if (c != null) {
671            if (runIsRtl) {
672                x -= ret;
673            }
674
675            if (wp.bgColor != 0) {
676                int color = wp.getColor();
677                Paint.Style s = wp.getStyle();
678                wp.setColor(wp.bgColor);
679                wp.setStyle(Paint.Style.FILL);
680
681                c.drawRect(x, top, x + ret, bottom, wp);
682
683                wp.setStyle(s);
684                wp.setColor(color);
685            }
686
687            drawTextRun(c, wp, start, limit, runIsRtl, x, y + wp.baselineShift);
688        }
689
690        return runIsRtl ? -ret : ret;
691    }
692
693    /**
694     * Utility function for measuring and rendering a replacement.
695     *
696     * @param replacement the replacement
697     * @param wp the work paint
698     * @param runIndex the run index
699     * @param start the start of the run
700     * @param limit the limit of the run
701     * @param runIsRtl true if the run is right-to-left
702     * @param c the canvas, can be null if not rendering
703     * @param x the edge of the replacement closest to the leading margin
704     * @param top the top of the line
705     * @param y the baseline
706     * @param bottom the bottom of the line
707     * @param fmi receives metrics information, can be null
708     * @param needWidth true if the width of the replacement is needed
709     * @param prepFlags one of PREP_NONE, PREP_REQUIRED, or PREP_ONLY
710     * @return the signed width of the run based on the run direction; only
711     * valid if needWidth is true
712     */
713    private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
714            int runIndex, int start, int limit, boolean runIsRtl, Canvas c,
715            float x, int top, int y, int bottom, FontMetricsInt fmi,
716            boolean needWidth, int prepFlags) {
717
718        float ret = 0;
719
720        // Preparation replaces the first character of the series with the
721        // object-replacement character and the remainder with zero width
722        // non-break space aka BOM.  Cursor movement code skips over the BOMs
723        // so that the replacement character is the only character 'seen'.
724        if (prepFlags != PREP_NONE && limit > start &&
725                (runIndex > mPreppedIndex ||
726                        (runIndex == mPreppedIndex && start >= mPreppedLimit))) {
727            char[] chars = mChars;
728            chars[start] = '\ufffc';
729            for (int i = start + 1; i < limit; ++i) {
730                chars[i] = '\ufeff'; // used as ZWNBS, marks positions to skip
731            }
732            mPreppedIndex = runIndex;
733            mPreppedLimit = limit;
734        }
735
736        if (prepFlags != PREP_ONLY) {
737            int textStart = mStart + start;
738            int textLimit = mStart + limit;
739
740            if (needWidth || (c != null && runIsRtl)) {
741                ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
742            }
743
744            if (c != null) {
745                if (runIsRtl) {
746                    x -= ret;
747                }
748                replacement.draw(c, mText, textStart, textLimit,
749                        x, top, y, bottom, wp);
750            }
751        }
752
753        return runIsRtl ? -ret : ret;
754    }
755
756    /**
757     * Utility function for handling a unidirectional run.  The run must not
758     * contain tabs or emoji but can contain styles.
759     *
760     * @param p the base paint
761     * @param runIndex the run index
762     * @param start the line-relative start of the run
763     * @param offset the offset to measure to, between start and limit inclusive
764     * @param limit the limit of the run
765     * @param runIsRtl true if the run is right-to-left
766     * @param c the canvas, can be null
767     * @param x the end of the run closest to the leading margin
768     * @param top the top of the line
769     * @param y the baseline
770     * @param bottom the bottom of the line
771     * @param fmi receives metrics information, can be null
772     * @param needWidth true if the width is required
773     * @param prepFlags one of PREP_NONE, PREP_REQUIRED, or PREP_ONLY
774     * @return the signed width of the run based on the run direction; only
775     * valid if needWidth is true
776     */
777    private float handleRun(int runIndex, int start, int offset,
778            int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
779            int bottom, FontMetricsInt fmi, boolean needWidth, int prepFlags) {
780
781        // Shaping needs to take into account context up to metric boundaries,
782        // but rendering needs to take into account character style boundaries.
783        // So we iterate through metric runs, shape using the initial
784        // paint (the same typeface is used up to the next metric boundary),
785        // then within each metric run iterate through character style runs.
786        float ox = x;
787        for (int i = start, inext; i < offset; i = inext) {
788            TextPaint wp = mWorkPaint;
789            wp.set(mPaint);
790
791            int mnext;
792            if (mSpanned == null) {
793                inext = limit;
794                mnext = offset;
795            } else {
796                inext = mSpanned.nextSpanTransition(mStart + i, mStart + limit,
797                        MetricAffectingSpan.class) - mStart;
798
799                mnext = inext < offset ? inext : offset;
800                MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + i,
801                        mStart + mnext, MetricAffectingSpan.class);
802
803                if (spans.length > 0) {
804                    ReplacementSpan replacement = null;
805                    for (int j = 0; j < spans.length; j++) {
806                        MetricAffectingSpan span = spans[j];
807                        if (span instanceof ReplacementSpan) {
808                            replacement = (ReplacementSpan)span;
809                        } else {
810                            span.updateDrawState(wp); // XXX or measureState?
811                        }
812                    }
813
814                    if (replacement != null) {
815                        x += handleReplacement(replacement, wp, runIndex, i,
816                                mnext, runIsRtl, c, x, top, y, bottom, fmi,
817                                needWidth || mnext < offset, prepFlags);
818                        continue;
819                    }
820                }
821            }
822
823            if (prepFlags != PREP_NONE) {
824                handlePrep(wp, runIndex, i, inext, runIsRtl);
825            }
826
827            if (prepFlags != PREP_ONLY) {
828                if (mSpanned == null || c == null) {
829                    x += handleText(wp, i, mnext, runIsRtl, c, x, top,
830                            y, bottom, fmi, needWidth || mnext < offset);
831                } else {
832                    for (int j = i, jnext; j < mnext; j = jnext) {
833                        jnext = mSpanned.nextSpanTransition(mStart + j,
834                                mStart + mnext, CharacterStyle.class) - mStart;
835
836                        CharacterStyle[] spans = mSpanned.getSpans(mStart + j,
837                                mStart + jnext, CharacterStyle.class);
838
839                        wp.set(mPaint);
840                        for (int k = 0; k < spans.length; k++) {
841                            CharacterStyle span = spans[k];
842                            span.updateDrawState(wp);
843                        }
844
845                        x += handleText(wp, j, jnext, runIsRtl, c, x,
846                                top, y, bottom, fmi, needWidth || jnext < offset);
847                    }
848                }
849            }
850        }
851
852        return x - ox;
853    }
854
855    private static final int PREP_NONE = 0;
856    private static final int PREP_NEEDED = 1;
857    private static final int PREP_ONLY = 2;
858
859    /**
860     * Prepares text for measuring or rendering.
861     *
862     * @param paint the paint used to shape the text
863     * @param runIndex the run index
864     * @param start the start of the text to prepare
865     * @param limit the limit of the text to prepare
866     * @param runIsRtl true if the run is right-to-left
867     */
868    private void handlePrep(TextPaint paint, int runIndex, int start, int limit,
869            boolean runIsRtl) {
870
871        // The current implementation 'prepares' text by manipulating the
872        // character array.  In order to keep track of what ranges have
873        // already been prepared, it uses the runIndex and the limit of
874        // the prepared text within that run.  This index is required
875        // since operations that prepare the text always proceed in visual
876        // order and the limit itself does not let us know which runs have
877        // been processed and which have not.
878        //
879        // This bookkeeping is an attempt to let us process a line partially,
880        // for example, by only shaping up to the cursor position.  This may
881        // not make sense if we can reuse the line, say by caching repeated
882        // accesses to the same line for both measuring and drawing, since in
883        // those cases we'd always prepare the entire line.  At the
884        // opposite extreme, we might shape and then immediately discard only
885        // the run of text we're working with at the moment, instead of retaining
886        // the results of shaping (as the chars array is).  In this case as well
887        // we would not need to do the index/limit bookkeeping.
888        //
889        // Technically, the only reason for bookkeeping is so that we don't
890        // re-mirror already-mirrored glyphs, since the shaping and object
891        // replacement operations will not change already-processed text.
892
893        if (runIndex > mPreppedIndex ||
894                (runIndex == mPreppedIndex && start >= mPreppedLimit)) {
895            if (runIsRtl) {
896                int runLen = limit - start;
897                AndroidCharacter.mirror(mChars, start, runLen);
898                ArabicShaping.SHAPER.shape(mChars, start, runLen);
899
900                // Note: tweaked MockShaper to put '\ufeff' in place of
901                // alef when it forms lam-alef ligatures, so no extra
902                // processing is necessary here.
903            }
904            mPreppedIndex = runIndex;
905            mPreppedLimit = limit;
906        }
907    }
908
909    /**
910     * Render a text run with the set-up paint.
911     *
912     * @param c the canvas
913     * @param wp the paint used to render the text
914     * @param start the run start
915     * @param limit the run limit
916     * @param runIsRtl true if the run is right-to-left
917     * @param x the x position of the left edge of the run
918     * @param y the baseline of the run
919     */
920    private void drawTextRun(Canvas c, TextPaint wp, int start, int limit,
921            boolean runIsRtl, float x, int y) {
922
923        int flags = runIsRtl ? Canvas.DIRECTION_RTL : Canvas.DIRECTION_LTR;
924        if (mCharsValid) {
925            c.drawTextRun(mChars, start, limit - start, x, y, flags, wp);
926        } else {
927            c.drawTextRun(mText, mStart + start, mStart + limit, x, y, flags, wp);
928        }
929    }
930
931    /**
932     * Returns the ascent of the text at start.  This is used for scaling
933     * emoji.
934     *
935     * @param pos the line-relative position
936     * @return the ascent of the text at start
937     */
938    float ascent(int pos) {
939        if (mSpanned == null) {
940            return mPaint.ascent();
941        }
942
943        pos += mStart;
944        MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1,
945                MetricAffectingSpan.class);
946        if (spans.length == 0) {
947            return mPaint.ascent();
948        }
949
950        TextPaint wp = mWorkPaint;
951        wp.set(mPaint);
952        for (MetricAffectingSpan span : spans) {
953            span.updateMeasureState(wp);
954        }
955        return wp.ascent();
956    }
957
958    /**
959     * Returns the next tab position.
960     *
961     * @param h the (unsigned) offset from the leading margin
962     * @return the (unsigned) tab position after this offset
963     */
964    float nextTab(float h) {
965        if (mTabs != null) {
966            return mTabs.nextTab(h);
967        }
968        return TabStops.nextDefaultStop(h, TAB_INCREMENT);
969    }
970
971    private static final int TAB_INCREMENT = 20;
972}
973