/** * Utility function for handling a unidirectional run. The run must not contain tabs or emoji but * can contain styles. * * @param start the line-relative start of the run * @param measureLimit the offset to measure to, between start and limit inclusive * @param limit the limit of the run * @param runIsRtl true if the run is right-to-left * @param c the canvas, can be null * @param x the end of the run closest to the leading margin * @param top the top of the line * @param y the baseline * @param bottom the bottom of the line * @param fmi receives metrics information, can be null * @param needWidth true if the width is required * @return the signed width of the run based on the run direction; only valid if needWidth is true */ private float handleRun( int start, int measureLimit, int limit, boolean runIsRtl, Canvas c, float x, int top, int y, int bottom, FontMetricsInt fmi, boolean needWidth) { // Case of an empty line, make sure we update fmi according to mPaint if (start == measureLimit) { TextPaint wp = mWorkPaint; wp.set(mPaint); if (fmi != null) { expandMetricsFromPaint(fmi, wp); } return 0f; } if (mSpanned == null) { TextPaint wp = mWorkPaint; wp.set(mPaint); final int mlimit = measureLimit; return handleText( wp, start, mlimit, start, limit, runIsRtl, c, x, top, y, bottom, fmi, needWidth || mlimit < measureLimit); } mMetricAffectingSpanSpanSet.init(mSpanned, mStart + start, mStart + limit); mCharacterStyleSpanSet.init(mSpanned, mStart + start, mStart + limit); // Shaping needs to take into account context up to metric boundaries, // but rendering needs to take into account character style boundaries. // So we iterate through metric runs to get metric bounds, // then within each metric run iterate through character style runs // for the run bounds. final float originalX = x; for (int i = start, inext; i < measureLimit; i = inext) { TextPaint wp = mWorkPaint; wp.set(mPaint); inext = mMetricAffectingSpanSpanSet.getNextTransition(mStart + i, mStart + limit) - mStart; int mlimit = Math.min(inext, measureLimit); ReplacementSpan replacement = null; for (int j = 0; j < mMetricAffectingSpanSpanSet.numberOfSpans; j++) { // Both intervals [spanStarts..spanEnds] and [mStart + i..mStart + mlimit] are NOT // empty by construction. This special case in getSpans() explains the >= & <= tests if ((mMetricAffectingSpanSpanSet.spanStarts[j] >= mStart + mlimit) || (mMetricAffectingSpanSpanSet.spanEnds[j] <= mStart + i)) continue; MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j]; if (span instanceof ReplacementSpan) { replacement = (ReplacementSpan) span; } else { // We might have a replacement that uses the draw // state, otherwise measure state would suffice. span.updateDrawState(wp); } } if (replacement != null) { x += handleReplacement( replacement, wp, i, mlimit, runIsRtl, c, x, top, y, bottom, fmi, needWidth || mlimit < measureLimit); continue; } for (int j = i, jnext; j < mlimit; j = jnext) { jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + mlimit) - mStart; wp.set(mPaint); for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) { // Intentionally using >= and <= as explained above if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + jnext) || (mCharacterStyleSpanSet.spanEnds[k] <= mStart + j)) continue; CharacterStyle span = mCharacterStyleSpanSet.spans[k]; span.updateDrawState(wp); } // Only draw hyphen on last run in line if (jnext < mLen) { wp.setHyphenEdit(0); } x += handleText( wp, j, jnext, i, inext, runIsRtl, c, x, top, y, bottom, fmi, needWidth || jnext < measureLimit); } } return x - originalX; }