/** * Do not support cross Span. * * @param text text * @param parentSpan parentSpan * @param start start index of parentSpan * @param end end index of parentSpan * @param paint TextPaint * @return recursive calculated width */ public int recursiveGetSizeWithReplacementSpan(CharSequence text, ReplacementSpan parentSpan, @IntRange(from = 0) int start, @IntRange(from = 0) int end, Paint paint) { if (text instanceof Spanned) { Spanned spannedText = (Spanned) text; List<ReplacementSpan> spans = getSortedReplacementSpans(spannedText, start, end); if (!spans.isEmpty()) { int lastIndexCursor = 0; int width = 0; for (ReplacementSpan span : spans) { if (span == parentSpan) { continue; } int spanStart = spannedText.getSpanStart(span); int spanEnd = spannedText.getSpanEnd(span); width += parentSpan.getSize(paint, text, lastIndexCursor, spanStart, null); width += span.getSize(paint, text, spanStart, spanEnd, null); lastIndexCursor = spanEnd; } if (lastIndexCursor < end) { width += parentSpan.getSize(paint, text, lastIndexCursor, end, null); } return width; } } return parentSpan.getSize(paint, text, start, end, null); }
@Override public <T> T[] getSpans(int start, int end, Class<T> type) { if (mEllipsisEnd >= end && mEllipsisStart <= end) { T[] spans1 = mSourceSpanned.getSpans(start, Math.max(mEllipsisStart, start), type); T[] spans2 = mSourceSpanned.getSpans(Math.min(end, mEllipsisEnd), end, type); int offset = mCustomEllipsisSpan != null && (type.isAssignableFrom(ReplacementSpan.class) || type == mCustomEllipsisSpan.getClass()) ? 1 : 0; int minLen = spans1.length + spans2.length + offset; T[] spans = (T[]) Array.newInstance(type, minLen); if (spans.length > minLen) { spans = Arrays.copyOf(spans, minLen); } System.arraycopy(spans1, 0, spans, 0, spans1.length); if (offset > 0) { spans[spans1.length] = (T) mCustomEllipsisSpan; } System.arraycopy(spans2, 0, spans, spans1.length + offset, spans2.length); return spans; } return mSourceSpanned.getSpans(start, end, type); }
/** * 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; }
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; }
@Override public int getSize(@NonNull Rect outRect, @NonNull Paint paint, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @Nullable Paint.FontMetricsInt fm) { int width = super.getSize(outRect, paint, text, start, end, fm); if (styles != null) { for (CharacterStyle style : styles) { if (style instanceof SupportSpan) { width = Math.max(width, ((SupportSpan) style).getSize(frame, paint, text, start, end, fm)); } else if (style instanceof ReplacementSpan) { width = Math.max(width, ((ReplacementSpan) style).getSize(paint, text, start, end, fm)); } else if (paint instanceof TextPaint) { if (style instanceof MetricAffectingSpan) { ((MetricAffectingSpan) style).updateMeasureState((TextPaint) paint); } } } } frame.right = width; return width; }
@Override public int getSize(@NonNull Paint paint, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @Nullable Paint.FontMetricsInt fm) { width = 0; for (CharacterStyle style : styles) { if (style instanceof ReplacementSpan) { width = Math.max(width, ((ReplacementSpan) style).getSize(paint, text, start, end, fm)); } else if (paint instanceof TextPaint) { if (style instanceof MetricAffectingSpan) { ((MetricAffectingSpan) style).updateMeasureState((TextPaint) paint); } } } if (fm != null) { paint.getFontMetricsInt(fm); } paint.getFontMetricsInt(fontMetricsInt); width = Math.max(width, (int) Math.ceil(paint.measureText(text, start, end))); frame.right = width; frame.top = fontMetricsInt.top; frame.bottom = fontMetricsInt.bottom; if (bitmap == null) { bitmap = Bitmap.createBitmap(width, frame.bottom - frame.top, Bitmap.Config.ARGB_8888); bitmapCanvas = new Canvas(bitmap); } return width; }
@Override public void draw(@NonNull Canvas canvas, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, float x, int top, int y, int bottom, @NonNull Paint paint) { // bitmapCanvas.drawColor(Color.GRAY, PorterDuff.Mode.CLEAR); int color = /*paint instanceof TextPaint ? ((TextPaint) paint).bgColor :*/ paint.getColor(); bitmapCanvas.drawColor(color); for (CharacterStyle style : styles) { if (style instanceof ReplacementSpan) { ((ReplacementSpan) style).draw(bitmapCanvas, text, start, end, 0, top, y, bottom, paint); } else if (paint instanceof TextPaint) { style.updateDrawState((TextPaint) paint); } } paint.setXfermode(xfermode); bitmapCanvas.drawText(text, start, end, 0, y, paint); canvas.drawBitmap(bitmap, x, 0, null); }
public static List<ReplacementSpan> getSortedReplacementSpans(final Spanned spanned, int start, int end) { List<ReplacementSpan> sortedSpans = new LinkedList<>(); ReplacementSpan[] spans = spanned.getSpans(start, end, ReplacementSpan.class); if (spans.length > 0) { sortedSpans.addAll(Arrays.asList(spans)); } Collections.sort(sortedSpans, new Comparator<ReplacementSpan>() { @Override public int compare(ReplacementSpan span1, ReplacementSpan span2) { return spanned.getSpanStart(span1) - spanned.getSpanStart(span2); } }); return sortedSpans; }
@Override public void draw(@NonNull Rect outRect, @NonNull Canvas canvas, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, float x, int top, int y, int bottom, @NonNull Paint paint) { for (CharacterStyle style : styles) { if (style instanceof SupportSpan) { ((SupportSpan) style).draw(frame, canvas, text, start, end, x, top, y, bottom, paint); } else if (style instanceof ReplacementSpan) { ((ReplacementSpan) style).draw(canvas, text, start, end, x, top, y, bottom, paint); } else if (paint instanceof TextPaint) { style.updateDrawState((TextPaint) paint); } } }
/** Starts {@code span} at the current position in the builder. */ public Truss pushSpan(Object span) { if (span instanceof ReplacementSpan) { replacements++; } else if (replacements > 0 && span instanceof CharacterStyle) { span = convert((CharacterStyle) span); } stack.addLast(new Span(builder.length(), span)); return this; }
/** End the most recently pushed span at the current position in the builder. */ public Truss popSpan() { Span span = stack.removeLast(); if (span.span instanceof ReplacementSpan) { replacements--; } builder.setSpan(span.span, span.start, builder.length(), SPAN_INCLUSIVE_EXCLUSIVE); return this; }
private void onSpanAdded(Object span) { if (span instanceof MetricAffectingSpan) { mHasMetricAffectingSpan = true; } if (span instanceof ReplacementSpan) { mHasReplacementSpan = true; } if (span instanceof ParagraphStyle) { mHasParagraphStyle = true; } }
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { manipulatedSpan = null; if (after < count && !manualModeOn) { ReplacementSpan[] spans = ((Spannable)s).getSpans(start, start+count, ReplacementSpan.class); if (spans.length == 1) { manipulatedSpan = spans[0]; } else { manipulatedSpan = null; } } }
public void setCustomEllipsisSpan(ReplacementSpan customEllipsisSpan) { mCustomEllipsisSpan = customEllipsisSpan; }
public ReplacementSpan getCustomEllipsisSpan() { return mCustomEllipsisSpan; }
public void setCustomCollapseSpan(ReplacementSpan collapseSpan) { mCollapseSpan = collapseSpan; }
/** * Utility function for measuring and rendering a replacement. * * @param replacement the replacement * @param wp the work paint * @param start the start of the run * @param limit the limit of the run * @param runIsRtl true if the run is right-to-left * @param c the canvas, can be null if not rendering * @param x the edge of the replacement 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 of the replacement is needed * @return the signed width of the run based on the run direction; only * valid if needWidth is true */ private float handleReplacement(ReplacementSpan replacement, TextPaint wp, int start, int limit, boolean runIsRtl, Canvas c, float x, int top, int y, int bottom, FontMetricsInt fmi, boolean needWidth) { float ret = 0; int textStart = mStart + start; int textLimit = mStart + limit; if (needWidth || (c != null && runIsRtl)) { int previousTop = 0; int previousAscent = 0; int previousDescent = 0; int previousBottom = 0; int previousLeading = 0; boolean needUpdateMetrics = (fmi != null); if (needUpdateMetrics) { previousTop = fmi.top; previousAscent = fmi.ascent; previousDescent = fmi.descent; previousBottom = fmi.bottom; previousLeading = fmi.leading; } ret = replacement.getSize(wp, mText, textStart, textLimit, fmi); if (needUpdateMetrics) { updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom, previousLeading); } } if (c != null) { if (runIsRtl) { x -= ret; } replacement.draw(c, mText, textStart, textLimit, x, top, y, bottom, wp); } return runIsRtl ? -ret : ret; }
public EMTranslatEntity assembleSpan(ArrayList<EMCharacterEntity> joinArr) throws JSONException { if (joinArr == null || joinArr.size() == 0) return null; SpannableStringBuilder spanSb = new SpannableStringBuilder(); Vector<EMCandiateEntity> allStressEMKeys = new Vector<>(); for (int i = 0; i < joinArr.size(); i++) { EMCharacterEntity entry = joinArr.get(i); switch (entry.mCharType) { case Normal: case Other: case Space: entry.mWordStart = spanSb.length(); spanSb.append(entry.mWord); break; case Emoj: SpannableStringBuilder tempSpan = (SpannableStringBuilder) entry.mWord; ReplacementSpan[] spans = tempSpan.getSpans(0, entry.mWord.length(), ReplacementSpan.class); if (spans.length > 0) { ReplacementSpan emojSpan = null; if (spans[0] instanceof EMImageSpan) { emojSpan = (EMImageSpan) spans[0]; } else if (spans[0] instanceof DefEmojSpan) { emojSpan = (DefEmojSpan) spans[0]; } spanSb.append(HOLDER_CHAR); spanSb.setSpan(emojSpan, spanSb.length() - 1, spanSb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } break; case Translate: String emojProperty = EMDBMagager.getInstance().queryEmojByTag(entry.mWord.toString()); if (StringUtil.isNullOrEmpty(emojProperty)) { spanSb.append(entry.mWord); } else { EMCandiateEntity candiateEmojEntity = new EMCandiateEntity(entry.mWord.toString(), entry.mWordStart); candiateEmojEntity.parseRespJson(new JSONObject(emojProperty)); ArrayList<EMCandiateProperty> emojTagEntities = candiateEmojEntity.mEmojEntities; if (emojTagEntities != null && emojTagEntities.size() == 1) { String emojTagId = "#|" + candiateEmojEntity.mEMKey + "_" + emojTagEntities.get(0).mUniqueId + "|"; candiateEmojEntity.mEMStart = spanSb.length(); spanSb.append(HOLDER_CHAR); DefEmojSpan emojResponseSpan = new DefEmojSpan(emojTagEntities.get(0)); emojResponseSpan.mOriginTxt = candiateEmojEntity.mEMKey; emojResponseSpan.mTransferTxt = emojTagId; spanSb.setSpan(emojResponseSpan, spanSb.toString().length() - 1, spanSb.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); // handle here if cache key and emoji property cacheCustomEmojToDB(emojTagId); }else { candiateEmojEntity.mEMStart = spanSb.length(); allStressEMKeys.add(candiateEmojEntity); spanSb.append(entry.mWord); } } break; } } EMTranslatEntity translatEntity = new EMTranslatEntity(spanSb, allStressEMKeys); return translatEntity; }
@SuppressWarnings("unchecked") @Override public <T> T[] getSpans(int start, int end, Class<T> type) { // Fast path for common time-critical spans that aren't there if (type == MetricAffectingSpan.class && !mHasMetricAffectingSpan) { return (T[]) EMPTY_METRIC_AFFECTING_SPAN_ARRAY; } if (type == ReplacementSpan.class && !mHasReplacementSpan) { return (T[]) EMPTY_REPLACEMENT_SPAN_ARRAY; } if (!mHasParagraphStyle) { if (type == LeadingMarginSpan.class) { return (T[]) EMPTY_LEADING_MARGIN_SPAN_ARRAY; } if (type == LineHeightSpan.class) { return (T[]) EMPTY_LINE_HEIGHT_SPAN_ARRAY; } if (type == TabStopSpan.class) { return (T[]) EMPTY_TAB_STOP_SPAN_ARRAY; } } T[] spansFromSuperclass = mSpannableString.getSpans(start, end, type); if ( mSpansArr.length == 0 || // We have no optimized spans isExcludedSpanType(type)) { // Query is about unoptimized span return spansFromSuperclass; } // Based on Arrays.binarySearch() int lo = 0; int hi = mSpansArr.length - 1; int mid = -2; while (lo <= hi) { mid = (lo + hi) >>> 1; int midVal = mSpansArr[mid].end; if (midVal < start) { lo = mid + 1; } else if (midVal > start) { hi = mid - 1; } else { break; } } // Iterate over spans in range List<T> result = null; for (; mid < mSpansArr.length && mSpansArr[mid].start < end; mid++) { if (mSpansArr[mid].end > start && type.isInstance(mSpansArr[mid].span)) { if (result == null) { result = LIST_POOL.acquire(); if (spansFromSuperclass.length != 0) { result.addAll(Arrays.asList(spansFromSuperclass)); } } result.add((T) mSpansArr[mid].span); } } // If we have list then make array and pass to superclass if (result == null) { return spansFromSuperclass; } else { T[] resultArray = result.toArray((T[]) Array.newInstance(type, result.size())); LIST_POOL.release(result); return resultArray; } }