/**
   * 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;
  }