예제 #1
0
  private void process(boolean opening, Editable output, CharacterStyle... spanStyles) {
    int len = output.length();
    if (opening) {
      for (CharacterStyle style : spanStyles) {
        output.setSpan(style, len, len, Spannable.SPAN_MARK_MARK);
      }
    } else {
      for (CharacterStyle style : spanStyles) {
        Object obj = getLast(output, style.getClass());
        int where = output.getSpanStart(obj);

        output.removeSpan(obj);

        if (where != len) {
          if (style instanceof VideoClickableSpan) {
            // Need to set the video url to this span.
            String url = output.subSequence(where, len).toString();
            ((VideoClickableSpan) style).setVideoUrl(url);
          }
          output.setSpan(style, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
      }
    }
  }
예제 #2
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;
  }
예제 #3
0
  private static SpannableString style(
      Context ctx,
      HashMap<String, ITypeface> fonts,
      SpannableString textSpanned,
      List<CharacterStyle> styles,
      HashMap<String, List<CharacterStyle>> stylesFor) {
    if (fonts == null || fonts.size() == 0) {
      fonts = FONTS;
    }

    int startIndex = -1;
    String fontKey = "";

    // remember the position of removed chars
    ArrayList<RemoveInfo> removed = new ArrayList<RemoveInfo>();

    // StringBuilder text = new StringBuilder(textSpanned.toString());
    StringBuilder text = new StringBuilder(textSpanned);

    // find the first "{"
    while ((startIndex = text.indexOf("{", startIndex + 1)) != -1) {
      // make sure we are still within the bounds of the text
      if (text.length() < startIndex + 5) {
        startIndex = -1;
        break;
      }
      // make sure the found text is a real fontKey
      if (!text.substring(startIndex + 4, startIndex + 5).equals("-")) {
        break;
      }
      // get the fontKey
      fontKey = text.substring(startIndex + 1, startIndex + 4);
      // check if the fontKey is a registeredFont
      if (fonts.containsKey(fontKey)) {
        break;
      }
    }
    if (startIndex == -1) {
      return new SpannableString(text);
    }

    // remember total removed chars
    int removedChars = 0;

    LinkedList<StyleContainer> styleContainers = new LinkedList<StyleContainer>();
    do {
      // get the information from the iconString
      int endIndex = text.substring(startIndex).indexOf("}") + startIndex + 1;
      String iconString = text.substring(startIndex + 1, endIndex - 1);
      iconString = iconString.replaceAll("-", "_");
      try {
        // get the correct character for this Font and Icon
        IIcon icon = fonts.get(fontKey).getIcon(iconString);
        // we can only add an icon which is a font
        if (icon != null) {
          char fontChar = icon.getCharacter();
          String iconValue = String.valueOf(fontChar);

          // get just the icon identifier
          text = text.replace(startIndex, endIndex, iconValue);

          // store some info about the removed chars
          removedChars = removedChars + (endIndex - startIndex);
          removed.add(new RemoveInfo(startIndex, (endIndex - startIndex - 1), removedChars));

          // add the current icon to the container
          styleContainers.add(
              new StyleContainer(startIndex, startIndex + 1, iconString, fonts.get(fontKey)));
        }
      } catch (IllegalArgumentException e) {
        Log.w(Iconics.TAG, "Wrong icon name: " + iconString);
      }

      // reset fontKey so we can react if we are at the end but haven't found any more matches
      fontKey = null;

      // check the rest of the text for matches
      while ((startIndex = text.indexOf("{", startIndex + 1)) != -1) {
        // make sure we are still within the bounds
        if (text.length() < startIndex + 5) {
          startIndex = -1;
          break;
        }
        // check if the 5. char is a "-"
        if (text.substring(startIndex + 4, startIndex + 5).equals("-")) {
          // get the fontKey
          fontKey = text.substring(startIndex + 1, startIndex + 4);
          // check if the fontKey is registered
          if (fonts.containsKey(fontKey)) {
            break;
          }
        }
      }
    } while (startIndex != -1 && fontKey != null);

    SpannableString sb = new SpannableString(text);

    // reapply all previous styles
    for (StyleSpan span : textSpanned.getSpans(0, textSpanned.length(), StyleSpan.class)) {
      int spanStart = newSpanPoint(textSpanned.getSpanStart(span), removed);
      int spanEnd = newSpanPoint(textSpanned.getSpanEnd(span), removed);
      if (spanStart >= 0 && spanEnd > 0) {
        sb.setSpan(span, spanStart, spanEnd, textSpanned.getSpanFlags(span));
      }
    }

    // set all the icons and styles
    for (StyleContainer styleContainer : styleContainers) {
      sb.setSpan(
          new IconicsTypefaceSpan("sans-serif", styleContainer.getFont().getTypeface(ctx)),
          styleContainer.getStartIndex(),
          styleContainer.getEndIndex(),
          Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

      if (stylesFor.containsKey(styleContainer.getIcon())) {
        for (CharacterStyle style : stylesFor.get(styleContainer.getIcon())) {
          sb.setSpan(
              CharacterStyle.wrap(style),
              styleContainer.getStartIndex(),
              styleContainer.getEndIndex(),
              Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
      } else if (styles != null) {
        for (CharacterStyle style : styles) {
          sb.setSpan(
              CharacterStyle.wrap(style),
              styleContainer.getStartIndex(),
              styleContainer.getEndIndex(),
              Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
      }
    }

    // sb = applyKerning(sb, 1);

    return sb;
  }
예제 #4
0
  private void icUpdateGecko(boolean force) {

    // Skip if receiving a repeated request, or
    // if suppressing compositions during text selection.
    if ((!force && mIcUpdateSeqno == mLastIcUpdateSeqno) || mSuppressCompositions) {
      if (DEBUG) {
        Log.d(LOGTAG, "icUpdateGecko() skipped");
      }
      return;
    }
    mLastIcUpdateSeqno = mIcUpdateSeqno;
    mActionQueue.syncWithGecko();

    if (DEBUG) {
      Log.d(LOGTAG, "icUpdateGecko()");
    }

    final int selStart = mText.getSpanStart(Selection.SELECTION_START);
    final int selEnd = mText.getSpanEnd(Selection.SELECTION_END);
    int composingStart = mText.length();
    int composingEnd = 0;
    Object[] spans = mText.getSpans(0, composingStart, Object.class);

    for (Object span : spans) {
      if ((mText.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) {
        composingStart = Math.min(composingStart, mText.getSpanStart(span));
        composingEnd = Math.max(composingEnd, mText.getSpanEnd(span));
      }
    }
    if (DEBUG) {
      Log.d(LOGTAG, " range = " + composingStart + "-" + composingEnd);
      Log.d(LOGTAG, " selection = " + selStart + "-" + selEnd);
    }
    if (composingStart >= composingEnd) {
      if (selStart >= 0 && selEnd >= 0) {
        onImeSetSelection(selStart, selEnd);
      } else {
        onImeRemoveComposition();
      }
      return;
    }

    if (selEnd >= composingStart && selEnd <= composingEnd) {
      onImeAddCompositionRange(
          selEnd - composingStart,
          selEnd - composingStart,
          IME_RANGE_CARETPOSITION,
          0,
          0,
          false,
          0,
          0,
          0);
    }
    int rangeStart = composingStart;
    TextPaint tp = new TextPaint();
    TextPaint emptyTp = new TextPaint();
    // set initial foreground color to 0, because we check for tp.getColor() == 0
    // below to decide whether to pass a foreground color to Gecko
    emptyTp.setColor(0);
    do {
      int rangeType, rangeStyles = 0, rangeLineStyle = IME_RANGE_LINE_NONE;
      boolean rangeBoldLine = false;
      int rangeForeColor = 0, rangeBackColor = 0, rangeLineColor = 0;
      int rangeEnd = mText.nextSpanTransition(rangeStart, composingEnd, Object.class);

      if (selStart > rangeStart && selStart < rangeEnd) {
        rangeEnd = selStart;
      } else if (selEnd > rangeStart && selEnd < rangeEnd) {
        rangeEnd = selEnd;
      }
      CharacterStyle[] styleSpans = mText.getSpans(rangeStart, rangeEnd, CharacterStyle.class);

      if (DEBUG) {
        Log.d(LOGTAG, " found " + styleSpans.length + " spans @ " + rangeStart + "-" + rangeEnd);
      }

      if (styleSpans.length == 0) {
        rangeType =
            (selStart == rangeStart && selEnd == rangeEnd)
                ? IME_RANGE_SELECTEDRAWTEXT
                : IME_RANGE_RAWINPUT;
      } else {
        rangeType =
            (selStart == rangeStart && selEnd == rangeEnd)
                ? IME_RANGE_SELECTEDCONVERTEDTEXT
                : IME_RANGE_CONVERTEDTEXT;
        tp.set(emptyTp);
        for (CharacterStyle span : styleSpans) {
          span.updateDrawState(tp);
        }
        int tpUnderlineColor = 0;
        float tpUnderlineThickness = 0.0f;

        // These TextPaint fields only exist on Android ICS+ and are not in the SDK.
        if (Versions.feature14Plus) {
          tpUnderlineColor = (Integer) getField(tp, "underlineColor", 0);
          tpUnderlineThickness = (Float) getField(tp, "underlineThickness", 0.0f);
        }
        if (tpUnderlineColor != 0) {
          rangeStyles |= IME_RANGE_UNDERLINE | IME_RANGE_LINECOLOR;
          rangeLineColor = tpUnderlineColor;
          // Approximately translate underline thickness to what Gecko understands
          if (tpUnderlineThickness <= 0.5f) {
            rangeLineStyle = IME_RANGE_LINE_DOTTED;
          } else {
            rangeLineStyle = IME_RANGE_LINE_SOLID;
            if (tpUnderlineThickness >= 2.0f) {
              rangeBoldLine = true;
            }
          }
        } else if (tp.isUnderlineText()) {
          rangeStyles |= IME_RANGE_UNDERLINE;
          rangeLineStyle = IME_RANGE_LINE_SOLID;
        }
        if (tp.getColor() != 0) {
          rangeStyles |= IME_RANGE_FORECOLOR;
          rangeForeColor = tp.getColor();
        }
        if (tp.bgColor != 0) {
          rangeStyles |= IME_RANGE_BACKCOLOR;
          rangeBackColor = tp.bgColor;
        }
      }
      onImeAddCompositionRange(
          rangeStart - composingStart,
          rangeEnd - composingStart,
          rangeType,
          rangeStyles,
          rangeLineStyle,
          rangeBoldLine,
          rangeForeColor,
          rangeBackColor,
          rangeLineColor);
      rangeStart = rangeEnd;

      if (DEBUG) {
        Log.d(
            LOGTAG,
            " added "
                + rangeType
                + " : "
                + Integer.toHexString(rangeStyles)
                + " : "
                + Integer.toHexString(rangeForeColor)
                + " : "
                + Integer.toHexString(rangeBackColor));
      }
    } while (rangeStart < composingEnd);

    onImeUpdateComposition(composingStart, composingEnd);
  }
  /**
   * Draws and/or measures a uniform run of text on a single line. No span of interest should start
   * or end in the middle of this run (if not drawing, character spans that don't affect metrics can
   * be ignored). Neither should the run direction change in the middle of the run.
   *
   * <p>
   *
   * <p>The x position is the leading edge of the text. In a right-to-left paragraph, this will be
   * to the right of the text to be drawn. Paint should not have an Align value other than LEFT or
   * positioning will isCancelled confused.
   *
   * <p>
   *
   * <p>On return, workPaint will reflect the original paint plus any modifications made by
   * character styles on the run.
   *
   * <p>
   *
   * <p>The returned width is signed and will be < 0 if the paragraph direction is right-to-left.
   */
  private static float drawUniformRun(
      Canvas canvas,
      Spanned text,
      int start,
      int end,
      int dir,
      boolean runIsRtl,
      float x,
      int top,
      int y,
      int bottom,
      Paint.FontMetricsInt fmi,
      TextPaint paint,
      TextPaint workPaint,
      boolean needWidth) {

    boolean haveWidth = false;
    float ret = 0;
    CharacterStyle[] spans = text.getSpans(start, end, CharacterStyle.class);

    ReplacementSpan replacement = null;

    // XXX: This shouldn't be modifying paint, only workPaint.
    // However, the members belonging to TextPaint should have default
    // values anyway.  Better to ensure this in the Layout constructor.
    paint.bgColor = 0;
    paint.baselineShift = 0;
    workPaint.set(paint);

    if (spans.length > 0) {
      for (CharacterStyle span : spans) {
        if (span instanceof ReplacementSpan) {
          replacement = (ReplacementSpan) span;
        } else {
          span.updateDrawState(workPaint);
        }
      }
    }

    if (replacement == null) {
      CharSequence tmp;
      int tmpstart, tmpend;

      if (runIsRtl) {
        tmp = TextUtils.getReverse(text, start, end);
        tmpstart = 0;
        // XXX: assumes getReverse doesn't change the length of the text
        tmpend = end - start;
      } else {
        tmp = text;
        tmpstart = start;
        tmpend = end;
      }

      if (fmi != null) {
        workPaint.getFontMetricsInt(fmi);
      }

      if (canvas != null) {
        if (workPaint.bgColor != 0) {
          int c = workPaint.getColor();
          Paint.Style s = workPaint.getStyle();
          workPaint.setColor(workPaint.bgColor);
          workPaint.setStyle(Paint.Style.FILL);

          if (!haveWidth) {
            ret = workPaint.measureText(tmp, tmpstart, tmpend);
            haveWidth = true;
          }

          if (dir == Layout.DIR_RIGHT_TO_LEFT) {
            canvas.drawRect(x - ret, top, x, bottom, workPaint);
          } else {
            canvas.drawRect(x, top, x + ret, bottom, workPaint);
          }

          workPaint.setStyle(s);
          workPaint.setColor(c);
        }

        if (dir == Layout.DIR_RIGHT_TO_LEFT) {
          if (!haveWidth) {
            ret = workPaint.measureText(tmp, tmpstart, tmpend);
            haveWidth = true;
          }

          canvas.drawText(tmp, tmpstart, tmpend, x - ret, y + workPaint.baselineShift, workPaint);
        } else {
          if (needWidth) {
            if (!haveWidth) {
              ret = workPaint.measureText(tmp, tmpstart, tmpend);
              haveWidth = true;
            }
          }

          canvas.drawText(tmp, tmpstart, tmpend, x, y + workPaint.baselineShift, workPaint);
        }
      } else {
        if (needWidth && !haveWidth) {
          ret = workPaint.measureText(tmp, tmpstart, tmpend);
          haveWidth = true;
        }
      }
    } else {
      ret = replacement.getSize(workPaint, text, start, end, fmi);

      if (canvas != null) {
        if (dir == Layout.DIR_RIGHT_TO_LEFT) {
          replacement.draw(canvas, text, start, end, x - ret, top, y, bottom, workPaint);
        } else {
          replacement.draw(canvas, text, start, end, x, top, y, bottom, workPaint);
        }
      }
    }

    if (dir == Layout.DIR_RIGHT_TO_LEFT) {
      return -ret;
    } else {
      return ret;
    }
  }