Beispiel #1
0
  /**
   * Returns the ascent of the text at start. This is used for scaling emoji.
   *
   * @param pos the line-relative position
   * @return the ascent of the text at start
   */
  float ascent(int pos) {
    if (mSpanned == null) {
      return mPaint.ascent();
    }

    pos += mStart;
    MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1, MetricAffectingSpan.class);
    if (spans.length == 0) {
      return mPaint.ascent();
    }

    TextPaint wp = mWorkPaint;
    wp.set(mPaint);
    for (MetricAffectingSpan span : spans) {
      span.updateMeasureState(wp);
    }
    return wp.ascent();
  }
 float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len, FontMetricsInt fm) {
   int i;
   float wid;
   TextPaint workPaint = this.mWorkPaint;
   workPaint.set(paint);
   workPaint.baselineShift = 0;
   ReplacementSpan replacement = null;
   for (MetricAffectingSpan span : spans) {
     if (span instanceof ReplacementSpan) {
       replacement = (ReplacementSpan) span;
     } else {
       span.updateMeasureState(workPaint);
     }
   }
   if (replacement == null) {
     wid = addStyleRun(workPaint, len, fm);
   } else {
     wid =
         (float)
             replacement.getSize(
                 workPaint,
                 this.mText,
                 this.mTextStart + this.mPos,
                 (this.mTextStart + this.mPos) + len,
                 fm);
     float[] w = this.mWidths;
     w[this.mPos] = wid;
     int e = this.mPos + len;
     for (i = this.mPos + 1; i < e; i++) {
       w[i] = 0.0f;
     }
     this.mPos += len;
   }
   if (fm != null) {
     if (workPaint.baselineShift < 0) {
       fm.ascent += workPaint.baselineShift;
       fm.top += workPaint.baselineShift;
     } else {
       fm.descent += workPaint.baselineShift;
       fm.bottom += workPaint.baselineShift;
     }
   }
   return wid;
 }
  /**
   * Returns the advance widths for a uniform left-to-right run of text with no style changes in the
   * middle of the run. If any style is replacement text, the first character will isCancelled the
   * width of the replacement and the remaining characters will isCancelled a width of 0.
   *
   * @param paint the paint, will not be modified
   * @param workPaint a paint to modify; on return will reflect the original paint plus the effect
   *     of all spans on the run
   * @param text the text
   * @param start the start of the run
   * @param end the limit of the run
   * @param widths array to receive the advance widths of the characters. Must be at least a large
   *     as (end - start).
   * @param fmi FontMetrics information; can be null
   * @return the actual number of widths returned
   */
  public static int getTextWidths(
      TextPaint paint,
      TextPaint workPaint,
      Spanned text,
      int start,
      int end,
      float[] widths,
      Paint.FontMetricsInt fmi) {
    MetricAffectingSpan[] spans = text.getSpans(start, end, MetricAffectingSpan.class);

    ReplacementSpan replacement = null;
    workPaint.set(paint);

    for (MetricAffectingSpan span : spans) {
      if (span instanceof ReplacementSpan) {
        replacement = (ReplacementSpan) span;
      } else {
        span.updateMeasureState(workPaint);
      }
    }

    if (replacement == null) {
      workPaint.getFontMetricsInt(fmi);
      workPaint.getTextWidths(text, start, end, widths);
    } else {
      int wid = replacement.getSize(workPaint, text, start, end, fmi);

      if (end > start) {
        widths[0] = wid;
        for (int i = start + 1; i < end; i++) {
          widths[i - start] = 0;
        }
      }
    }
    return end - start;
  }
Beispiel #4
0
  /**
   * 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) {
        /// M: new FontMetrics method for complex text support.
        expandMetricsFromPaint(fmi, wp, mText);
      }
      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);
        }

        x +=
            handleText(
                wp,
                j,
                jnext,
                i,
                inext,
                runIsRtl,
                c,
                x,
                top,
                y,
                bottom,
                fmi,
                needWidth || jnext < measureLimit);
      }
    }

    return x - originalX;
  }
Beispiel #5
0
  /**
   * Returns the next valid offset within this directional run, skipping conjuncts and zero-width
   * characters. This should not be called to walk off the end of the line, since the returned
   * values might not be valid on neighboring lines. If the returned offset is less than zero or
   * greater than the line length, the offset should be recomputed on the preceding or following
   * line, respectively.
   *
   * @param runIndex the run index
   * @param runStart the start of the run
   * @param runLimit the limit of the run
   * @param runIsRtl true if the run is right-to-left
   * @param offset the offset
   * @param after true if the new offset should logically follow the provided offset
   * @return the new offset
   */
  private int getOffsetBeforeAfter(
      int runIndex, int runStart, int runLimit, boolean runIsRtl, int offset, boolean after) {

    if (runIndex < 0 || offset == (after ? mLen : 0)) {
      // Walking off end of line.  Since we don't know
      // what cursor positions are available on other lines, we can't
      // return accurate values.  These are a guess.
      if (after) {
        return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart;
      }
      return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart;
    }

    TextPaint wp = mWorkPaint;
    wp.set(mPaint);

    int spanStart = runStart;
    int spanLimit;
    if (mSpanned == null) {
      spanLimit = runLimit;
    } else {
      int target = after ? offset + 1 : offset;
      int limit = mStart + runLimit;
      while (true) {
        spanLimit =
            mSpanned.nextSpanTransition(mStart + spanStart, limit, MetricAffectingSpan.class)
                - mStart;
        if (spanLimit >= target) {
          break;
        }
        spanStart = spanLimit;
      }

      MetricAffectingSpan[] spans =
          mSpanned.getSpans(mStart + spanStart, mStart + spanLimit, MetricAffectingSpan.class);
      spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);

      if (spans.length > 0) {
        ReplacementSpan replacement = null;
        for (int j = 0; j < spans.length; j++) {
          MetricAffectingSpan span = spans[j];
          if (span instanceof ReplacementSpan) {
            replacement = (ReplacementSpan) span;
          } else {
            span.updateMeasureState(wp);
          }
        }

        if (replacement != null) {
          // If we have a replacement span, we're moving either to
          // the start or end of this span.
          return after ? spanLimit : spanStart;
        }
      }
    }

    int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
    int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
    if (mCharsValid) {
      return wp.getTextRunCursor(
          mChars, spanStart, spanLimit - spanStart, flags, offset, cursorOpt);
    } else {
      return wp.getTextRunCursor(
              mText, mStart + spanStart, mStart + spanLimit, flags, mStart + offset, cursorOpt)
          - mStart;
    }
  }