private FixingResult addSpacesAroundSpansUntilFixed(
      SpannableStringBuilder builder, int widthMeasureSpec, int heightMeasureSpec) {

    Object[] spans = builder.getSpans(0, builder.length(), Object.class);
    List<Object> spansWithSpacesBefore = new ArrayList<Object>(spans.length);
    List<Object> spansWithSpacesAfter = new ArrayList<Object>(spans.length);

    for (Object span : spans) {
      int spanStart = builder.getSpanStart(span);
      if (isNotSpace(builder, spanStart - 1)) {
        builder.insert(spanStart, " ");
        spansWithSpacesBefore.add(span);
      }

      int spanEnd = builder.getSpanEnd(span);
      if (isNotSpace(builder, spanEnd)) {
        builder.insert(spanEnd, " ");
        spansWithSpacesAfter.add(span);
      }

      try {
        setTextAndMeasure(builder, widthMeasureSpec, heightMeasureSpec);
        return FixingResult.fixed(spansWithSpacesBefore, spansWithSpacesAfter);
      } catch (IndexOutOfBoundsException notFixed) {
      }
    }
    if (HtmlTextView.DEBUG) {
      Log.d(HtmlTextView.TAG, "Could not fix the Spanned by adding spaces around spans");
    }
    return FixingResult.notFixed();
  }
  private void notifyCommitComposition() {
    // Gecko already committed its composition, and
    // we should remove the composition on our side as well.
    boolean wasComposing = false;
    final Object[] spans = mText.getSpans(0, mText.length(), Object.class);

    for (Object span : spans) {
      if ((mText.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) {
        mText.removeSpan(span);
        wasComposing = true;
      }
    }

    if (!wasComposing) {
      return;
    }

    // Generate a text change notification if we actually cleared the composition.
    final CharSequence text = TextUtils.stringOrSpannedString(mText);
    geckoPostToIc(
        new Runnable() {
          @Override
          public void run() {
            mListener.onTextChange(text, 0, text.length(), text.length());
          }
        });
  }
 /**
  * get real translate charsequence
  *
  * @param content
  * @return
  */
 public static CharSequence getTranslateTxt(CharSequence content) {
   StringBuilder sBuilder = new StringBuilder();
   if (content instanceof SpannableStringBuilder) {
     SpannableStringBuilder spanSb = (SpannableStringBuilder) content;
     if (spanSb.toString().contains(EMHolderEntity.FINAL_HOLDER)) {
       for (int i = 0; i < spanSb.length(); i++) {
         ReplacementSpan[] spans = spanSb.getSpans(i, i + 1, ReplacementSpan.class);
         if (spans.length > 0) {
           if (spans[0] instanceof EMImageSpan) {
             EMImageSpan imgSpan = (EMImageSpan) spans[0];
             sBuilder.append(imgSpan.mTransferTxt);
           } else if (spans[0] instanceof DefEmojSpan) {
             DefEmojSpan defSpan = (DefEmojSpan) spans[0];
             sBuilder.append(defSpan.mTransferTxt);
           }
         } else {
           sBuilder.append(spanSb.subSequence(i, i + 1));
         }
       }
     } else {
       sBuilder.append(content);
     }
   }
   return sBuilder;
 }
 private void clearCachedMark(SpannableStringBuilder ssb) {
   Cached[] cs = ssb.getSpans(0, ssb.length(), Cached.class);
   if (cs != null && cs.length > 0) {
     for (Cached c : cs) {
       ssb.removeSpan(c);
     }
   }
 }
Exemple #5
0
  private void showNote(boolean xml) {

    if (xml) {
      content.setText(note.getXmlContent());
      title.setText((CharSequence) note.getTitle());
      this.setTitle(this.getTitle() + " - XML");
      return;
    }

    LinkInternalSpan[] links =
        noteContent.getSpans(0, noteContent.length(), LinkInternalSpan.class);
    MatchFilter noteLinkMatchFilter = LinkInternalSpan.getNoteLinkMatchFilter(noteContent, links);

    // show the note (spannable makes the TextView able to output styled text)
    content.setText(noteContent, TextView.BufferType.SPANNABLE);

    // add links to stuff that is understood by Android except phone numbers because it's too
    // aggressive
    // TODO this is SLOWWWW!!!!

    int linkFlags = 0;

    if (Preferences.getBoolean(Preferences.Key.LINK_EMAILS)) linkFlags |= Linkify.EMAIL_ADDRESSES;
    if (Preferences.getBoolean(Preferences.Key.LINK_URLS)) linkFlags |= Linkify.WEB_URLS;
    if (Preferences.getBoolean(Preferences.Key.LINK_ADDRESSES)) linkFlags |= Linkify.MAP_ADDRESSES;

    Linkify.addLinks(content, linkFlags);

    // Custom phone number linkifier (fixes lp:512204)
    if (Preferences.getBoolean(Preferences.Key.LINK_PHONES))
      Linkify.addLinks(
          content,
          LinkifyPhone.PHONE_PATTERN,
          "tel:",
          LinkifyPhone.sPhoneNumberMatchFilter,
          Linkify.sPhoneNumberTransformFilter);

    // This will create a link every time a note title is found in the text.
    // The pattern contains a very dumb (title1)|(title2) escaped correctly
    // Then we transform the url from the note name to the note id to avoid characters that mess up
    // with the URI (ex: ?)
    if (Preferences.getBoolean(Preferences.Key.LINK_TITLES)) {
      Pattern pattern = NoteManager.buildNoteLinkifyPattern(this, note.getTitle());

      if (pattern != null) {
        Linkify.addLinks(
            content,
            pattern,
            Tomdroid.CONTENT_URI + "/",
            noteLinkMatchFilter,
            noteTitleTransformFilter);

        // content.setMovementMethod(LinkMovementMethod.getInstance());
      }
    }
    title.setText((CharSequence) note.getTitle());
  }
  /**
   * Returns the current FontFamilySpan in use on the given subsection of the builder.
   *
   * <p>If no FontFamily has been set yet, spanner.getDefaultFont() is returned.
   *
   * @param builder the text to check
   * @param start start of the section
   * @param end end of the section
   * @return a FontFamily object
   */
  private FontFamilySpan getFontFamilySpan(SpannableStringBuilder builder, int start, int end) {

    FontFamilySpan[] spans = builder.getSpans(start, end, FontFamilySpan.class);

    if (spans != null && spans.length > 0) {
      return spans[spans.length - 1];
    }

    return null;
  }
 private void notifyCancelComposition() {
   // Composition should have been cancelled on our side
   // through text update notifications; verify that here.
   if (DEBUG) {
     final Object[] spans = mText.getSpans(0, mText.length(), Object.class);
     for (Object span : spans) {
       if ((mText.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) {
         throw new IllegalStateException("composition not cancelled");
       }
     }
   }
 }
  /**
   * Set the necessary spans for each spoiler.
   *
   * <p>The algorithm works in the same way as <code>setCodeFont</code>.
   *
   * @param sequence
   * @return
   */
  private CharSequence setSpoilerStyle(SpannableStringBuilder sequence) {
    int start = 0;
    int end = 0;
    for (int i = 0; i < sequence.length(); i++) {
      if (sequence.charAt(i) == '[' && i < sequence.length() - 3) {
        if (sequence.charAt(i + 1) == '['
            && sequence.charAt(i + 2) == 's'
            && sequence.charAt(i + 3) == '[') {
          start = i;
        }
      } else if (sequence.charAt(i) == ']' && i < sequence.length() - 3) {
        if (sequence.charAt(i + 1) == 's'
            && sequence.charAt(i + 2) == ']'
            && sequence.charAt(i + 3) == ']') {
          end = i;
        }
      }

      if (end > start) {
        sequence.delete(end, end + 4);
        sequence.delete(start, start + 4);
        BackgroundColorSpan backgroundColorSpan = new BackgroundColorSpan(Color.BLACK);
        ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(Color.BLACK);

        URLSpan urlSpan = sequence.getSpans(start, start, URLSpan.class)[0];
        sequence.setSpan(
            urlSpan, sequence.getSpanStart(urlSpan), start - 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);

        // spoiler text has a space at the front
        sequence.setSpan(
            backgroundColorSpan, start + 1, end - 4, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
        sequence.setSpan(foregroundColorSpan, start, end - 4, Spannable.SPAN_INCLUSIVE_INCLUSIVE);

        storedSpoilerSpans.add(foregroundColorSpan);
        storedSpoilerSpans.add(backgroundColorSpan);
        // Shift 1 to account for remove of beginning "<"
        storedSpoilerStarts.add(start - 1);
        storedSpoilerStarts.add(start - 1);
        storedSpoilerEnds.add(end - 5);
        storedSpoilerEnds.add(end - 5);

        sequence.delete(start - 2, start - 1); // remove the trailing <
        start = 0;
        end = 0;
        i = i - 5; // move back to compensate for removal of [[s[
      }
    }

    return sequence;
  }
 /**
  * @desc
  *     <pre>表情解析,转成unicode字符</pre>
  *
  * @author Weiliang Hu
  * @date 2013-12-17
  * @param cs
  * @param mContext
  * @return
  */
 public static String convertToMsg(CharSequence cs, Context mContext) {
   SpannableStringBuilder ssb = new SpannableStringBuilder(cs);
   ImageSpan[] spans = ssb.getSpans(0, cs.length(), ImageSpan.class);
   for (int i = 0; i < spans.length; i++) {
     ImageSpan span = spans[i];
     String c = span.getSource();
     int a = ssb.getSpanStart(span);
     int b = ssb.getSpanEnd(span);
     if (c.contains("[")) {
       ssb.replace(a, b, convertUnicode(c));
     }
   }
   ssb.clearSpans();
   return ssb.toString();
 }
 private void handleClick(SpannableStringBuilder ssb, RichTextConfig config, boolean cached) {
   if (cached) {
     LongClickableURLSpan[] lcus = ssb.getSpans(0, ssb.length(), LongClickableURLSpan.class);
     if (lcus != null && lcus.length > 0) {
       for (LongClickableURLSpan lcu : lcus) {
         resetLinkSpan(ssb, config, lcu);
       }
     }
   } else {
     if (config.clickable >= 0) {
       // 处理超链接点击事件
       URLSpan[] urlSpans = ssb.getSpans(0, ssb.length(), URLSpan.class);
       for (int i = 0, size = urlSpans == null ? 0 : urlSpans.length; i < size; i++) {
         resetLinkSpan(ssb, config, urlSpans[i]);
       }
     } else {
       // 移除URLSpan
       URLSpan[] urlSpans = ssb.getSpans(0, ssb.length(), URLSpan.class);
       for (int i = 0, size = urlSpans == null ? 0 : urlSpans.length; i < size; i++) {
         ssb.removeSpan(urlSpans[i]);
       }
     }
   }
 }
  public static void setTextWithIcon(Context context, TextView textView, String text) {
    Spanned html = Html.fromHtml(text);
    SpannableStringBuilder builder = new SpannableStringBuilder(html);

    ImageSpan[] images = builder.getSpans(0, builder.length(), ImageSpan.class);
    if (images != null && images.length > 0) {
      ImageSpan imagePlaceholder = images[0];
      int start = builder.getSpanStart(imagePlaceholder);
      int end = builder.getSpanEnd(imagePlaceholder);
      int flags = builder.getSpanFlags(imagePlaceholder);

      ImageSpan imageSpan = new ImageSpan(context, R.drawable.lock_icon, ImageSpan.ALIGN_BASELINE);
      builder.setSpan(imageSpan, start, end, flags);
      builder.removeSpan(imagePlaceholder);
    }

    textView.setText(builder);
  }
  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);
  }
  private int handleImage(
      SpannableStringBuilder ssb,
      ImageGetterWrapper imageGetterWrapper,
      RichTextConfig config,
      boolean cached) {
    if (cached) {
      ClickableImageSpan[] cis = ssb.getSpans(0, ssb.length(), ClickableImageSpan.class);
      if (cis != null && cis.length > 0) {
        for (ClickableImageSpan ci : cis) {
          int start = ssb.getSpanStart(ci);
          int end = ssb.getSpanEnd(ci);
          ssb.removeSpan(ci);
          OnImageClickListener onImageClickListener = null;
          OnImageLongClickListener onImageLongClickListener = null;
          if (config.clickable > 0) {
            onImageClickListener = config.onImageClickListener;
            onImageLongClickListener = config.onImageLongClickListener;
          }
          Drawable drawable = imageGetterWrapper.getDrawable(ci.getSource());
          if (drawable == null) {
            drawable = new ColorDrawable(Color.TRANSPARENT);
          }
          ClickableImageSpan nci =
              new ClickableImageSpan(drawable, ci, onImageClickListener, onImageLongClickListener);
          ssb.setSpan(nci, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
        return cis.length;
      }
    } else if (!config.noImage) {
      ImageSpan[] iss = ssb.getSpans(0, ssb.length(), ImageSpan.class);
      if (iss != null && iss.length > 0) {
        ArrayList<String> imageUrls = new ArrayList<>(iss.length);
        for (int i = 0; i < iss.length; i++) {
          ImageSpan imageSpan = iss[i];
          String imageUrl = imageSpan.getSource();
          imageUrls.add(imageUrl);

          int start = ssb.getSpanStart(imageSpan);
          int end = ssb.getSpanEnd(imageSpan);
          ClickableSpan[] clickableSpans = ssb.getSpans(start, end, ClickableSpan.class);
          if (clickableSpans != null && clickableSpans.length != 0) {
            for (ClickableSpan cs : clickableSpans) {
              ssb.removeSpan(cs);
            }
          }
          OnImageClickListener onImageClickListener = null;
          OnImageLongClickListener onImageLongClickListener = null;
          if (config.clickable > 0) {
            onImageClickListener = config.onImageClickListener;
            onImageLongClickListener = config.onImageLongClickListener;
          }
          Drawable drawable = imageGetterWrapper.getDrawable(imageUrl);
          if (drawable == null) {
            drawable = new ColorDrawable(Color.TRANSPARENT);
          }
          ClickableImageSpan cacheImageSpan =
              new ClickableImageSpan(
                  drawable, imageUrls, i, onImageClickListener, onImageLongClickListener);
          ssb.removeSpan(imageSpan);
          ssb.setSpan(cacheImageSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
        return iss.length;
      }
    }
    return 0;
  }
 private boolean isCached(SpannableStringBuilder ssb) {
   Cached[] cs = ssb.getSpans(0, ssb.length(), Cached.class);
   return cs != null && cs.length > 0;
 }