TextRun(int offset, int length, boolean isRotated, boolean isSpanned) { this.offset = offset; this.length = length; this.isRotated = isRotated; TextPaint wp; if (isSpanned) { wp = mWorkPaint; wp.set(mPaint); MetricAffectingSpan[] spans = ((Spanned) mText).getSpans(offset, offset + length, MetricAffectingSpan.class); for(MetricAffectingSpan span : spans) { span.updateDrawState(wp); } } else { wp = mPaint; } // just record the normal non-rotated values here // measure and draw will take rotation into account measuredWidth = wp.measureText(mText, offset, offset + length); measuredHeight = wp.getFontMetrics().bottom - wp.getFontMetrics().top; }
/** * Returns the ascent of the text at start. This is used for scaling * emoji. * * @param pos the line-relative position * @return the ascent of the text at start */ float ascent(int pos) { if (mSpanned == null) { return mPaint.ascent(); } pos += mStart; MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1, MetricAffectingSpan.class); if (spans.length == 0) { return mPaint.ascent(); } TextPaint wp = mWorkPaint; wp.set(mPaint); for (MetricAffectingSpan span : spans) { span.updateMeasureState(wp); } return wp.ascent(); }
/** * Apply typeface to a plane text and return spannableString * * @param text Text that you want to apply typeface * @param typeface Typeface that you want to apply to your text * @return spannableString */ public static SpannableString applyTypefaceToString(String text, final Typeface typeface) { SpannableString spannableString = new SpannableString(text); spannableString.setSpan(new MetricAffectingSpan() { @Override public void updateMeasureState(TextPaint p) { p.setTypeface(typeface); // Note: This flag is required for proper typeface rendering p.setFlags(p.getFlags() | Paint.SUBPIXEL_TEXT_FLAG); } @Override public void updateDrawState(TextPaint tp) { tp.setTypeface(typeface); // Note: This flag is required for proper typeface rendering tp.setFlags(tp.getFlags() | Paint.SUBPIXEL_TEXT_FLAG); } }, 0, spannableString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return spannableString; }
@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; }
private static final float getCharSize(MetricAffectingSpan[] sizeSpans, int char_idx, float size, Spanned source){ float res = 0; final TextPaint p = new TextPaint(); if(sizeSpans == null) return size; boolean found = false; for(MetricAffectingSpan span: sizeSpans){ final int s = source.getSpanStart(span); final int e = source.getSpanEnd(span); if((s <= char_idx) && (char_idx <= e)){ span.updateMeasureState(p); found = true; } } if(!found) return size; res = p.getTextSize(); return res; }
/** * Get new text paint. * * @param p init paint object * @param text some text * @param startInText start position in text * @param endInText end position in text * @return new instance of TextPaint */ @NonNull private TextPaint getTextPaint(Paint p, CharSequence text, int startInText, int endInText) { final TextPaint textPaint = new TextPaint(p); if (text instanceof SpannedString) { SpannedString spanned = (SpannedString) text; final MetricAffectingSpan[] spans = spanned.getSpans(startInText, endInText, MetricAffectingSpan.class); if (spans.length > 0) { for (MetricAffectingSpan span : spans) { span.updateMeasureState(textPaint); } } } return textPaint; }
private void mCustomiseScriptSpan(MetricAffectingSpan[] spanArray, SpannableStringBuilder builder) { for(MetricAffectingSpan metricAffectingSpan : spanArray) { int start = builder.getSpanStart(metricAffectingSpan); int end = builder.getSpanEnd(metricAffectingSpan); RelativeSizeSpan sizeSpan = new RelativeSizeSpan(0.6f); builder.setSpan(sizeSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); builder.setSpan(new CalligraphyTypefaceSpan(mBoldItalicTypeface), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } }
private static MetricAffectingSpan convert(CharacterStyle style) { if (style instanceof MetricAffectingSpan) { return (MetricAffectingSpan) style; } else { return new MetricAffectAdapter(style); } }
private void onSpanAdded(Object span) { if (span instanceof MetricAffectingSpan) { mHasMetricAffectingSpan = true; } if (span instanceof ReplacementSpan) { mHasReplacementSpan = true; } if (span instanceof ParagraphStyle) { mHasParagraphStyle = true; } }
public static MetricAffectingSpan wrap(CharacterStyle style) { return new MetricAffectAdapter(style); }
@Override public void updateMeasureState(TextPaint p) { if (style instanceof MetricAffectingSpan) { ((MetricAffectingSpan) style).updateMeasureState(p); } }
public int getCharIndexFromCoordinate(float x, float y) { // Offset the top padding int height = getPaddingTop(); boolean need_break = false; final CharSequence text = getText(); final Layout layout = getLayout(); for (int i = 0; i < layout.getLineCount(); i++) { if(need_break) break; final float size = getTextSize(); Rect bounds = new Rect(); layout.getLineBounds(i, bounds); //Log.e("Bounds", i+": "+(bounds.right-bounds.left)); height += bounds.height(); if (height >= y) { need_break = true; int lineStart = layout.getLineStart(i); int lineEnd = layout.getLineEnd(i); //Log.e("LineLength", ""+(lineEnd - lineStart)); Spanned span = (Spanned)text; MetricAffectingSpan[] sizeSpans = span.getSpans(lineStart, lineEnd, MetricAffectingSpan.class); float scaleFactor = 1; /*final TextPaint p = new TextPaint(); if ( sizeSpans != null ) { for (int j = 0; j < sizeSpans.length; j++) { sizeSpans[j].updateMeasureState(p); scaleFactor = p.getTextSize()/size; } }*/ span = (Spanned)text.subSequence(lineStart, lineEnd); String lineSpan = text.subSequence(lineStart, lineEnd).toString(); //Log.e("Line1", lineSpan); float[] widths = new float[lineSpan.length()]; TextPaint paint = getPaint(); paint.getTextWidths(lineSpan, widths); float width = 0; for (int j = 0; j < lineSpan.length(); j++) { final float sz = getCharSize(sizeSpans, j, size, span); //Log.e("MyTextView", "Size: "+size+" CharSpannedSize: "+sz); scaleFactor = sz/size; width += widths[j] * scaleFactor; if (width >= x) { return lineStart + j; } } } } return -1; }
@Override public MetricAffectingSpan getUnderlying() { return this; }
@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; } }
@Override public int nextSpanTransition(int start, int limit, Class type) { // Fast path for common time-critical spans that aren't there if (type == MetricAffectingSpan.class && !mHasMetricAffectingSpan) { return limit; } // Let superclass find it's boundary limit = mSpannableString.nextSpanTransition(start, limit, type); if ( mSpansArr.length == 0 || // We have no optimized spans isExcludedSpanType(type)) { // Query is about unoptimized span return limit; } // 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; } } for (; mid < mSpansArr.length && mSpansArr[mid].start <= limit; mid++) { SpanEntry entry = mSpansArr[mid]; if (entry.start > start && entry.start < limit && type.isInstance(entry.span)) { return entry.start; } if (entry.end > start && entry.end < limit && type.isInstance(entry.span)) { return entry.end; } } return limit; }
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = 0; if (currentBlock != null) { if (lastCreatedWidth != width) { CharSequence text; CharSequence author = getText(currentBlock.author, currentBlock.author, currentBlock); //TODO support styles Spannable spannableAuthor; MetricAffectingSpan spans[]; if (author instanceof Spannable) { spannableAuthor = (Spannable) author; spans = spannableAuthor.getSpans(0, author.length(), MetricAffectingSpan.class); } else { spannableAuthor = null; spans = null; } if (currentBlock.published_date != 0 && !TextUtils.isEmpty(author)) { text = LocaleController.formatString("ArticleDateByAuthor", R.string.ArticleDateByAuthor, LocaleController.getInstance().chatFullDate.format((long) currentBlock.published_date * 1000), author); } else if (!TextUtils.isEmpty(author)) { text = LocaleController.formatString("ArticleByAuthor", R.string.ArticleByAuthor, author); } else { text = LocaleController.getInstance().chatFullDate.format((long) currentBlock.published_date * 1000); } try { if (spans != null && spans.length > 0) { int idx = TextUtils.indexOf(text, author); if (idx != -1) { Spannable spannable = Spannable.Factory.getInstance().newSpannable(text); text = spannable; for (int a = 0; a < spans.length; a++) { spannable.setSpan(spans[a], idx + spannableAuthor.getSpanStart(spans[a]), idx + spannableAuthor.getSpanEnd(spans[a]), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } } } catch (Exception e) { FileLog.e(e); } textLayout = createLayoutForText(text, null, width - AndroidUtilities.dp(36), currentBlock); if (textLayout != null) { height += AndroidUtilities.dp(8 + 8) + textLayout.getHeight(); if (isRtl == 1) { textX = (int) Math.floor(width - textLayout.getLineWidth(0) - AndroidUtilities.dp(16)); } else { textX = AndroidUtilities.dp(18); } } //lastCreatedWidth = width; } } else { height = 1; } setMeasuredDimension(width, height); }