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); } } }
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; }