static Action newReplaceText(CharSequence text, int start, int end) { if (start < 0 || start > end) { Log.e(LOGTAG, "invalid replace text offsets: " + start + " to " + end); throw new IllegalArgumentException("invalid replace text offsets"); } int actionType = TYPE_REPLACE_TEXT; if (text instanceof Spanned) { final Spanned spanned = (Spanned) text; final Object[] spans = spanned.getSpans(0, spanned.length(), Object.class); for (Object span : spans) { if ((spanned.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) { actionType = TYPE_COMPOSE_TEXT; break; } } } final Action action = new Action(actionType); action.mSequence = text; action.mStart = start; action.mEnd = end; return action; }
/** * Extract the text from a HTML based string. This is similar to what HTML.fromHtml(...) does, but * this method also removes the embedded images instead of replacing them by a small rectangular * representation character. * * @param html * @return */ public static String extractText(CharSequence html) { String result = html.toString(); // recognize images in textview HTML contents if (html instanceof Spanned) { Spanned text = (Spanned) html; Object[] styles = text.getSpans(0, text.length(), Object.class); ArrayList<Pair<Integer, Integer>> removals = new ArrayList<Pair<Integer, Integer>>(); for (Object style : styles) { if (style instanceof ImageSpan) { int start = text.getSpanStart(style); int end = text.getSpanEnd(style); removals.add(Pair.of(start, end)); } } // sort reversed and delete image spans Collections.sort( removals, new Comparator<Pair<Integer, Integer>>() { @Override public int compare(Pair<Integer, Integer> lhs, Pair<Integer, Integer> rhs) { return rhs.getRight().compareTo(lhs.getRight()); } }); result = text.toString(); for (Pair<Integer, Integer> removal : removals) { result = result.substring(0, removal.getLeft()) + result.substring(removal.getRight()); } } // some line breaks are still in the text, source is unknown return StringUtils.replace(result, "<br />", "\n").trim(); }
public void store(View v) { Spanned s = (Spanned) mTextView.getText(); BackgroundColorSpan[] spans = s.getSpans(0, s.length(), BackgroundColorSpan.class); BufferedWriter bw = null; try { int len = spans.length; bw = new BufferedWriter(new FileWriter(mFile)); bw.write(String.valueOf(len)); bw.newLine(); for (BackgroundColorSpan span : spans) { int start = s.getSpanStart(span); int end = s.getSpanEnd(span); int color = span.getBackgroundColor(); bw.write("" + start + "," + end + "," + color); bw.newLine(); } bw.write(mText); clear(v); } catch (IOException e) { Log.e(TAG, "IO error", e); } finally { closeQuietly(bw); } }
/** See android.text.HtmlToSpannedConverter#getLast(android.text.Spanned, java.lang.Class) */ private static <T> T getLastSpan(Spanned text, Class<T> kind) { T[] spans = text.getSpans(0, text.length(), kind); if (spans.length == 0) { return null; } else { return spans[spans.length - 1]; } }
private static CharSequence removeJumpingBeansSpans(Spanned text) { SpannableStringBuilder sbb = new SpannableStringBuilder(text.toString()); Object[] spans = text.getSpans(0, text.length(), Object.class); for (Object span : spans) { if (!(span instanceof JumpingBeansSpan)) { sbb.setSpan(span, text.getSpanStart(span), text.getSpanEnd(span), text.getSpanFlags(span)); } } return sbb; }
@Override public void onFinishTemporaryDetach() { super.onFinishTemporaryDetach(); if (mContainsImages && getText() instanceof Spanned) { Spanned text = (Spanned) getText(); TextInlineImageSpan[] spans = text.getSpans(0, text.length(), TextInlineImageSpan.class); for (TextInlineImageSpan span : spans) { span.onFinishTemporaryDetach(); } } }
public ClickableSpan[] getLinkAt(float x, float y) { Integer offset = findOffsetForPosition(x, y); if (offset == null) { return null; } Spanned text = (Spanned) childView.getText(); ClickableSpan[] spans = text.getSpans(offset, offset, ClickableSpan.class); return spans; }
public boolean hasLinkAt(float x, float y) { Integer offset = findOffsetForPosition(x, y); if (offset == null) { return false; } Spanned text = (Spanned) childView.getText(); ClickableSpan[] spans = text.getSpans(offset, offset, ClickableSpan.class); return spans != null && spans.length > 0; }
/** * Returns null if not boring; the width, ascent, and descent in the provided Metrics object (or a * new one if the provided one was null) if boring. */ public static Metrics isBoring(CharSequence text, TextPaint paint, Metrics metrics) { char[] temp = TextUtils.obtain(500); int len = text.length(); boolean boring = true; outer: for (int i = 0; i < len; i += 500) { int j = i + 500; if (j > len) j = len; TextUtils.getChars(text, i, j, temp, 0); int n = j - i; for (int a = 0; a < n; a++) { char c = temp[a]; if (c == '\n' || c == '\t' || c >= FIRST_RIGHT_TO_LEFT) { boring = false; break outer; } } } TextUtils.recycle(temp); if (boring && text instanceof Spanned) { Spanned sp = (Spanned) text; Object[] styles = sp.getSpans(0, text.length(), ParagraphStyle.class); if (styles.length > 0) { boring = false; } } if (boring) { Metrics fm = metrics; if (fm == null) { fm = new Metrics(); } int wid; synchronized (sTemp) { wid = (int) (FloatMath.ceil(Styled.measureText(paint, sTemp, text, 0, text.length(), fm))); } fm.width = wid; return fm; } else { return null; } }
@Override protected boolean verifyDrawable(Drawable drawable) { if (mContainsImages && getText() instanceof Spanned) { Spanned text = (Spanned) getText(); TextInlineImageSpan[] spans = text.getSpans(0, text.length(), TextInlineImageSpan.class); for (TextInlineImageSpan span : spans) { if (span.getDrawable() == drawable) { return true; } } } return super.verifyDrawable(drawable); }
@Override public void invalidateDrawable(Drawable drawable) { if (mContainsImages && getText() instanceof Spanned) { Spanned text = (Spanned) getText(); TextInlineImageSpan[] spans = text.getSpans(0, text.length(), TextInlineImageSpan.class); for (TextInlineImageSpan span : spans) { if (span.getDrawable() == drawable) { invalidate(); } } } super.invalidateDrawable(drawable); }
@Test public void testTextDecorationLineLineThroughApplied() { UIManagerModule uiManager = getUIManagerModule(); ReactRootView rootView = createText( uiManager, JavaOnlyMap.of(ViewProps.TEXT_DECORATION_LINE, "line-through"), JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, "test text")); TextView textView = (TextView) rootView.getChildAt(0); Spanned text = (Spanned) textView.getText(); UnderlineSpan[] underlineSpans = text.getSpans(0, text.length(), UnderlineSpan.class); StrikethroughSpan strikeThroughSpan = getSingleSpan(textView, StrikethroughSpan.class); assertThat(underlineSpans).hasSize(0); assertThat(strikeThroughSpan instanceof StrikethroughSpan).isTrue(); }
@TargetApi(Build.VERSION_CODES.JELLY_BEAN) public static String getImageUrl(final Context context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) return null; final ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); final ClipData primaryClip = cm.getPrimaryClip(); if (primaryClip.getItemCount() > 0) { final ClipData.Item item = primaryClip.getItemAt(0); final CharSequence styledText = item.coerceToStyledText(context); if (styledText instanceof Spanned) { final Spanned spanned = (Spanned) styledText; final ImageSpan[] imageSpans = spanned.getSpans(0, spanned.length(), ImageSpan.class); if (imageSpans.length == 1) return imageSpans[0].getSource(); } } return null; }
public static CharSequence getHtmlText(String text) { // fixes an android bug (?): text layout fails on text with nested style tags text = removeNestedTags(text, new String[] {"i", "b", "strong"}); final Spanned htmlText = Html.fromHtml(text); if (htmlText.getSpans(0, htmlText.length(), URLSpan.class).length == 0) { return htmlText; } final Spannable newHtmlText = Spannable.Factory.getInstance().newSpannable(htmlText); for (URLSpan span : newHtmlText.getSpans(0, newHtmlText.length(), URLSpan.class)) { final int start = newHtmlText.getSpanStart(span); final int end = newHtmlText.getSpanEnd(span); final int flags = newHtmlText.getSpanFlags(span); final String url = NetworkLibrary.Instance().rewriteUrl(span.getURL(), true); newHtmlText.removeSpan(span); newHtmlText.setSpan(new URLSpan(url), start, end, flags); } return newHtmlText; }
@Override public int reactTagForTouch(float touchX, float touchY) { Spanned text = (Spanned) getText(); int target = getId(); int x = (int) touchX; int y = (int) touchY; Layout layout = getLayout(); if (layout == null) { // If the layout is null, the view hasn't been properly laid out yet. Therefore, we can't find // the exact text tag that has been touched, and the correct tag to return is the default one. return target; } int line = layout.getLineForVertical(y); int lineStartX = (int) layout.getLineLeft(line); int lineEndX = (int) layout.getLineRight(line); // TODO(5966918): Consider extending touchable area for text spans by some DP constant if (x >= lineStartX && x <= lineEndX) { int index = layout.getOffsetForHorizontal(line, x); // We choose the most inner span (shortest) containing character at the given index // if no such span can be found we will send the textview's react id as a touch handler // In case when there are more than one spans with same length we choose the last one // from the spans[] array, since it correspond to the most inner react element ReactTagSpan[] spans = text.getSpans(index, index, ReactTagSpan.class); if (spans != null) { int targetSpanTextLength = text.length(); for (int i = 0; i < spans.length; i++) { int spanStart = text.getSpanStart(spans[i]); int spanEnd = text.getSpanEnd(spans[i]); if (spanEnd > index && (spanEnd - spanStart) <= targetSpanTextLength) { target = spans[i].getReactTag(); targetSpanTextLength = (spanEnd - spanStart); } } } } return target; }
/** * 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; }
@Override public void setObject(Item item) { try { dobroitem = (DobroPostItem) item; } catch (ClassCastException e) { e.printStackTrace(); return; } if (dobroitem == null || dobroitem.post == null) return; dobroitem.post.setLastContext(getContext()); this.board = dobroitem.post.getBoardName(); this.thread = dobroitem.post.getThreadDisplay_id(); ((HorizontalScrollView) findViewById(R.id.horizScroll)).scrollTo(0, 0); CharSequence txt = dobroitem.post.getFormattedText(); if (txt.length() > 800) { Pattern p1 = Pattern.compile("\\.|\n"); Matcher m1 = p1.matcher(txt); Pattern p2 = Pattern.compile("\\b"); Matcher m2 = p2.matcher(txt); if (m1.find(500) && m1.end() < 700) txt = txt.subSequence(0, m1.end()); else if (m2.find(500) && m2.start() < 700) txt = txt.subSequence(0, m2.start()); else txt = txt.subSequence(0, 500); messageView.setText(txt, BufferType.SPANNABLE); SpannableStringBuilder builder = new SpannableStringBuilder("\nСообщение слишком длинное. Полная версия"); builder.setSpan( new ClickableSpan() { @Override public void onClick(View widget) { try { messageView.setText(dobroitem.post.getFormattedText(), BufferType.SPANNABLE); } catch (IndexOutOfBoundsException e) { } } }, 1, builder.length(), 0); messageView.append(builder.subSequence(0, builder.length())); } else messageView.setText(txt, BufferType.SPANNABLE); // FIXME messageView.setMovementMethod(LinkMovementMethod.getInstance()); messageView.setClickable(false); messageView.setFocusable(false); messageView.setFocusableInTouchMode(false); /* refsView.setMovementMethod(LinkMovementMethod.getInstance()); refsView.setClickable(false); refsView.setFocusable(false); refsView.setFocusableInTouchMode(false); */ // imagesLayout.removeAllViewsInLayout(); if (dobroitem.post.isOp()) { numberView.setVisibility(GONE); titleView.setText( dobroitem.post.getSubject().length() == 0 ? getContext().getString(R.string.untitled) : dobroitem.post.getSubject()); titleView.setVisibility(VISIBLE); } else { int pos = dobroitem.post.getNumber(); if (pos > 0) { numberView.setVisibility(VISIBLE); numberView.setText(String.valueOf(pos)); } else numberView.setVisibility(GONE); titleView.setText(""); titleView.setVisibility(GONE); } TypedValue backgroundRef = new TypedValue(); getContext().getTheme().resolveAttribute(R.attr.dcPicrelatedColor, backgroundRef, true); int backgroundColor = getContext().getResources().getColor(backgroundRef.resourceId); SharedPreferences prefs = DobroApplication.getApplicationStatic().getDefaultPrefs(); boolean show_info = prefs.getBoolean("show_fileinfo", true); boolean force_op_load = prefs.getBoolean("op_pictures_force", false); if (dobroitem.post.getFiles().length > 0) { for (DobroFile file : dobroitem.post.getFiles()) { CachedAsyncImageView imageView = new CachedAsyncImageView(getContext()); VerticalTextView textView = null; String fname1 = file.getThumb().substring(file.getThumb().lastIndexOf("/") + 1); int e = fname1.lastIndexOf("s."); if (e > 0) fname1 = fname1.substring(0, e) + file.getSrc().substring(file.getSrc().lastIndexOf(".")); String fname2 = file.getSrc().substring(file.getSrc().lastIndexOf("/") + 1); String size = humanReadableByteCount(file.getSize(), true); String date = getThumbnailDate(file.getThumb()); if (date == null) try { date = file.getSrc().split("/")[2]; date = date.substring(2) + "." + date.substring(0, 2); } catch (Exception ex) { date = "?"; } imageView.setInfo( getContext() .getString( R.string.file_info_parretn, fname1, fname2, size, date, file.getRating(), getMetadataText(file.getMetadata()))); if (file.getMetadata().width != null) imageView.setSize(file.getMetadata().height, file.getMetadata().width); if (show_info) { textView = new VerticalTextView(getContext(), null); textView.setLinkedImageView(imageView); String text = ""; if (dobroitem.post.getBoardName().equals("b") || dobroitem.post.getBoardName().equals("rf")) text += fname1; else text += fname2; text += "\n" + size; if (file.getMetadata() != null && file.getMetadata().width != null && file.getMetadata().height != null) { text += ", " + String.valueOf(file.getMetadata().width) + "x" + String.valueOf(file.getMetadata().height); } textView.setText(text); textView.setGravity(Gravity.BOTTOM); textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); } imageView.setRating(file.getRat()); imageView.setForceLoad(force_op_load && dobroitem.post.isOp()); imageView.setUrl(file.getThumb()); final String urlTag = file.getSrc(); imageView.setTag(urlTag); imageView.setPadding(2, 2, 2, 2); imageView.setAdjustViewBounds(true); imageView.setMaxHeight(mImageHeight); imageView.setMaxWidth(mImageHeight); imageView.setBackgroundColor(backgroundColor); imageView.setScaleType(ScaleType.CENTER); imageView.setOnCreateContextMenuListener(imageView); float h = mImageHeight; int w = Math.min( Math.round((h / (float) file.getThumb_height()) * file.getThumb_width()), file.getThumb_width()); imageView.setLayoutParams(new LayoutParams(w + 4, mImageHeight + 4)); if (show_info) { textView.setWidth(mImageHeight + 4); textView.setLinkedImageView(imageView); imagesLayout.addView(textView); } imagesLayout.addView(imageView); } imagesLayout.setVisibility(VISIBLE); } else { imagesLayout.setVisibility(GONE); } if (prefs.getBoolean("youtube", true)) { Spanned s = null; boolean cast_ok = false; try { s = (Spanned) dobroitem.post.getFormattedText(); cast_ok = true; } catch (ClassCastException e) { cast_ok = false; } if (cast_ok) { URLSpan[] spans = s.getSpans(0, s.length(), URLSpan.class); for (URLSpan span : spans) { String url = span.getURL(); Pattern p = Pattern.compile("(youtube\\.com/watch\\?v=|youtu\\.be/)(\\S{11})"); Matcher m = p.matcher(url); if (!m.find()) continue; String yt_id = m.group(2); String text = s.subSequence(s.getSpanStart(span), s.getSpanEnd(span)).toString(); CachedAsyncImageView imageView = new CachedAsyncImageView(getContext()); imageView.setLayoutParams( new LayoutParams((int) ((mImageHeight + 4) / 0.75), mImageHeight + 4)); imageView.setRating(Rating.SWF); imageView.setForceLoad(false); imageView.setTag(url); imageView.setPadding(2, 2, 2, 2); imageView.setAdjustViewBounds(true); imageView.setMaxHeight(mImageHeight); imageView.setMaxWidth((int) ((mImageHeight + 4) / 0.75)); imageView.setUrl("http://img.youtube.com/vi/" + yt_id + "/0.jpg"); imageView.setBackgroundColor(backgroundColor); imageView.setScaleType(ScaleType.CENTER); VerticalTextView textView = new VerticalTextView(getContext(), null); textView.setGravity(Gravity.BOTTOM); textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); textView.setText(text); textView.setLinkedImageView(imageView); textView.setWidth(mImageHeight + 4); if (show_info) imagesLayout.addView(textView); imagesLayout.addView(imageView); imagesLayout.setVisibility(VISIBLE); } } } metadataRightView.setText( getContext() .getString( R.string.post_date, String.valueOf(dobroitem.post.getDate().replace(' ', '\n')))); metadataLeftView.setText( dobroitem.post.getName() + "\n" + getContext() .getString(R.string.post_id, String.valueOf(dobroitem.post.getDisplay_id()))); List<String> refs = dobroitem.post.getRefs(); if (!refs.isEmpty()) { /* CharSequence seq = getContext().getText(R.string.answers); SpannableStringBuilder s = new SpannableStringBuilder(seq); s.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, seq.length(), 0); s.append(" "); for (String ref : refs) { int start = s.length(); s.append(ref + ", "); s.setSpan( new DobroLinkSpan(dobroitem.post.getBoardName(), ref .substring(2), getContext(), null), start, start + ref.length(), 0); } refsView.setText(s.subSequence(0, s.length() - 2), BufferType.SPANNABLE); */ refsButton.setText(String.format("/%d", refs.size())); refsButton.setVisibility(View.VISIBLE); } else refsButton.setVisibility(View.GONE); checkSpells(); }
private static int getNumForegroundColorSpansBetween(Spanned value, int start, int end) { return value.getSpans(start, end, ForegroundColorSpan.class).length; }
/** Make sure TextView has exactly one span and that span has given type. */ private static <TSPAN> TSPAN getSingleSpan(TextView textView, Class<TSPAN> spanClass) { Spanned text = (Spanned) textView.getText(); TSPAN[] spans = text.getSpans(0, text.length(), spanClass); assertThat(spans).hasSize(1); return spans[0]; }
private void sendTextToGecko(CharSequence text, int caretPos) { if (DEBUG) Log.d(LOGTAG, "IME: sendTextToGecko(\"" + text + "\")"); // Handle composition text styles if (text != null && text instanceof Spanned) { Spanned span = (Spanned) text; int spanStart = 0, spanEnd = 0; boolean pastSelStart = false, pastSelEnd = false; do { int rangeType = GeckoEvent.IME_RANGE_CONVERTEDTEXT; int rangeStyles = 0, rangeForeColor = 0, rangeBackColor = 0; // Find next offset where there is a style transition spanEnd = span.nextSpanTransition(spanStart + 1, text.length(), CharacterStyle.class); // Empty range, continue if (spanEnd <= spanStart) continue; // Get and iterate through list of span objects within range CharacterStyle[] styles = span.getSpans(spanStart, spanEnd, CharacterStyle.class); for (CharacterStyle style : styles) { if (style instanceof UnderlineSpan) { // Text should be underlined rangeStyles |= GeckoEvent.IME_RANGE_UNDERLINE; } else if (style instanceof ForegroundColorSpan) { // Text should be of a different foreground color rangeStyles |= GeckoEvent.IME_RANGE_FORECOLOR; rangeForeColor = ((ForegroundColorSpan) style).getForegroundColor(); } else if (style instanceof BackgroundColorSpan) { // Text should be of a different background color rangeStyles |= GeckoEvent.IME_RANGE_BACKCOLOR; rangeBackColor = ((BackgroundColorSpan) style).getBackgroundColor(); } } // Add range to array, the actual styles are // applied when IME_SET_TEXT is sent if (DEBUG) { Log.d( LOGTAG, String.format( ". . . sendTextToGecko: IME_ADD_RANGE, %d, %d, %d, %d, %d, %d", spanStart, spanEnd - spanStart, rangeType, rangeStyles, rangeForeColor, rangeBackColor)); } GeckoAppShell.sendEventToGecko( GeckoEvent.createIMERangeEvent( spanStart, spanEnd - spanStart, rangeType, rangeStyles, rangeForeColor, rangeBackColor)); spanStart = spanEnd; } while (spanStart < text.length()); } else { if (DEBUG) Log.d( LOGTAG, ". . . sendTextToGecko: IME_ADD_RANGE, 0, " + text.length() + ", IME_RANGE_RAWINPUT, IME_RANGE_UNDERLINE)"); GeckoAppShell.sendEventToGecko( GeckoEvent.createIMERangeEvent( 0, text == null ? 0 : text.length(), GeckoEvent.IME_RANGE_RAWINPUT, GeckoEvent.IME_RANGE_UNDERLINE, 0, 0)); } // Change composition (treating selection end as where the caret is) if (DEBUG) { Log.d( LOGTAG, ". . . sendTextToGecko: IME_SET_TEXT, IME_RANGE_CARETPOSITION, \"" + text + "\")"); } GeckoAppShell.sendEventToGecko( GeckoEvent.createIMERangeEvent( caretPos, 0, GeckoEvent.IME_RANGE_CARETPOSITION, 0, 0, 0, text.toString())); }
private static Object getLast(final Spanned text, final Class<?> kind) { Object[] spans = text.getSpans(0, text.length(), kind); return spans.length > 0 ? spans[spans.length - 1] : null; }
/** * 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; } }