use of android.text.style.MetricAffectingSpan in project android_frameworks_base by ParanoidAndroid.
the class StaticLayout method generate.
/* package */
void generate(CharSequence source, int bufStart, int bufEnd, TextPaint paint, int outerWidth, TextDirectionHeuristic textDir, float spacingmult, float spacingadd, boolean includepad, boolean trackpad, float ellipsizedWidth, TextUtils.TruncateAt ellipsize) {
mLineCount = 0;
int v = 0;
boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
Paint.FontMetricsInt fm = mFontMetricsInt;
int[] chooseHtv = null;
MeasuredText measured = mMeasured;
Spanned spanned = null;
if (source instanceof Spanned)
spanned = (Spanned) source;
// XXX
int DEFAULT_DIR = DIR_LEFT_TO_RIGHT;
int paraEnd;
for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
if (paraEnd < 0)
paraEnd = bufEnd;
else
paraEnd++;
int firstWidthLineLimit = mLineCount + 1;
int firstWidth = outerWidth;
int restWidth = outerWidth;
LineHeightSpan[] chooseHt = null;
if (spanned != null) {
LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd, LeadingMarginSpan.class);
for (int i = 0; i < sp.length; i++) {
LeadingMarginSpan lms = sp[i];
firstWidth -= sp[i].getLeadingMargin(true);
restWidth -= sp[i].getLeadingMargin(false);
// paragraph.
if (lms instanceof LeadingMarginSpan2) {
LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
int lmsFirstLine = getLineForOffset(spanned.getSpanStart(lms2));
firstWidthLineLimit = lmsFirstLine + lms2.getLeadingMarginLineCount();
}
}
chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
if (chooseHt.length != 0) {
if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
chooseHtv = new int[ArrayUtils.idealIntArraySize(chooseHt.length)];
}
for (int i = 0; i < chooseHt.length; i++) {
int o = spanned.getSpanStart(chooseHt[i]);
if (o < paraStart) {
// starts in this layout, before the
// current paragraph
chooseHtv[i] = getLineTop(getLineForOffset(o));
} else {
// starts in this paragraph
chooseHtv[i] = v;
}
}
}
}
measured.setPara(source, paraStart, paraEnd, textDir);
char[] chs = measured.mChars;
float[] widths = measured.mWidths;
byte[] chdirs = measured.mLevels;
int dir = measured.mDir;
boolean easy = measured.mEasy;
int width = firstWidth;
float w = 0;
// here is the offset of the starting character of the line we are currently measuring
int here = paraStart;
// ok is a character offset located after a word separator (space, tab, number...) where
// we would prefer to cut the current line. Equals to here when no such break was found.
int ok = paraStart;
float okWidth = w;
int okAscent = 0, okDescent = 0, okTop = 0, okBottom = 0;
// fit is a character offset such that the [here, fit[ range fits in the allowed width.
// We will cut the line there if no ok position is found.
int fit = paraStart;
float fitWidth = w;
int fitAscent = 0, fitDescent = 0, fitTop = 0, fitBottom = 0;
boolean hasTabOrEmoji = false;
boolean hasTab = false;
TabStops tabStops = null;
for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
if (spanned == null) {
spanEnd = paraEnd;
int spanLen = spanEnd - spanStart;
measured.addStyleRun(paint, spanLen, fm);
} else {
spanEnd = spanned.nextSpanTransition(spanStart, paraEnd, MetricAffectingSpan.class);
int spanLen = spanEnd - spanStart;
MetricAffectingSpan[] spans = spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
measured.addStyleRun(paint, spans, spanLen, fm);
}
int fmTop = fm.top;
int fmBottom = fm.bottom;
int fmAscent = fm.ascent;
int fmDescent = fm.descent;
for (int j = spanStart; j < spanEnd; j++) {
char c = chs[j - paraStart];
if (c == CHAR_NEW_LINE) {
// intentionally left empty
} else if (c == CHAR_TAB) {
if (hasTab == false) {
hasTab = true;
hasTabOrEmoji = true;
if (spanned != null) {
// First tab this para, check for tabstops
TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, paraEnd, TabStopSpan.class);
if (spans.length > 0) {
tabStops = new TabStops(TAB_INCREMENT, spans);
}
}
}
if (tabStops != null) {
w = tabStops.nextTab(w);
} else {
w = TabStops.nextDefaultStop(w, TAB_INCREMENT);
}
} else if (c >= CHAR_FIRST_HIGH_SURROGATE && c <= CHAR_LAST_LOW_SURROGATE && j + 1 < spanEnd) {
int emoji = Character.codePointAt(chs, j - paraStart);
if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
Bitmap bm = EMOJI_FACTORY.getBitmapFromAndroidPua(emoji);
if (bm != null) {
Paint whichPaint;
if (spanned == null) {
whichPaint = paint;
} else {
whichPaint = mWorkPaint;
}
float wid = bm.getWidth() * -whichPaint.ascent() / bm.getHeight();
w += wid;
hasTabOrEmoji = true;
j++;
} else {
w += widths[j - paraStart];
}
} else {
w += widths[j - paraStart];
}
} else {
w += widths[j - paraStart];
}
boolean isSpaceOrTab = c == CHAR_SPACE || c == CHAR_TAB || c == CHAR_ZWSP;
if (w <= width || isSpaceOrTab) {
fitWidth = w;
fit = j + 1;
if (fmTop < fitTop)
fitTop = fmTop;
if (fmAscent < fitAscent)
fitAscent = fmAscent;
if (fmDescent > fitDescent)
fitDescent = fmDescent;
if (fmBottom > fitBottom)
fitBottom = fmBottom;
// From the Unicode Line Breaking Algorithm (at least approximately)
boolean isLineBreak = isSpaceOrTab || // / is class SY and - is class HY, except when followed by a digit
((c == CHAR_SLASH || c == CHAR_HYPHEN) && (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) || // (non-starters), which can be broken after but not before
(c >= CHAR_FIRST_CJK && isIdeographic(c, true) && j + 1 < spanEnd && isIdeographic(chs[j + 1 - paraStart], false));
if (isLineBreak) {
okWidth = w;
ok = j + 1;
if (fitTop < okTop)
okTop = fitTop;
if (fitAscent < okAscent)
okAscent = fitAscent;
if (fitDescent > okDescent)
okDescent = fitDescent;
if (fitBottom > okBottom)
okBottom = fitBottom;
}
} else {
final boolean moreChars = (j + 1 < spanEnd);
int endPos;
int above, below, top, bottom;
float currentTextWidth;
if (ok != here) {
endPos = ok;
above = okAscent;
below = okDescent;
top = okTop;
bottom = okBottom;
currentTextWidth = okWidth;
} else if (fit != here) {
endPos = fit;
above = fitAscent;
below = fitDescent;
top = fitTop;
bottom = fitBottom;
currentTextWidth = fitWidth;
} else {
endPos = here + 1;
above = fm.ascent;
below = fm.descent;
top = fm.top;
bottom = fm.bottom;
currentTextWidth = widths[here - paraStart];
}
v = out(source, here, endPos, above, below, top, bottom, v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, hasTabOrEmoji, needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad, chs, widths, paraStart, ellipsize, ellipsizedWidth, currentTextWidth, paint, moreChars);
here = endPos;
// restart j-span loop from here, compensating for the j++
j = here - 1;
ok = fit = here;
w = 0;
fitAscent = fitDescent = fitTop = fitBottom = 0;
okAscent = okDescent = okTop = okBottom = 0;
if (--firstWidthLineLimit <= 0) {
width = restWidth;
}
if (here < spanStart) {
// The text was cut before the beginning of the current span range.
// Exit the span loop, and get spanStart to start over from here.
measured.setPos(here);
spanEnd = here;
break;
}
if (mLineCount >= mMaximumVisibleLineCount) {
break;
}
}
}
}
if (paraEnd != here && mLineCount < mMaximumVisibleLineCount) {
if ((fitTop | fitBottom | fitDescent | fitAscent) == 0) {
paint.getFontMetricsInt(fm);
fitTop = fm.top;
fitBottom = fm.bottom;
fitAscent = fm.ascent;
fitDescent = fm.descent;
}
// Log.e("text", "output rest " + here + " to " + end);
v = out(source, here, paraEnd, fitAscent, fitDescent, fitTop, fitBottom, v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, hasTabOrEmoji, needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad, chs, widths, paraStart, ellipsize, ellipsizedWidth, w, paint, paraEnd != bufEnd);
}
paraStart = paraEnd;
if (paraEnd == bufEnd)
break;
}
if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) && mLineCount < mMaximumVisibleLineCount) {
// Log.e("text", "output last " + bufEnd);
paint.getFontMetricsInt(fm);
v = out(source, bufEnd, bufEnd, fm.ascent, fm.descent, fm.top, fm.bottom, v, spacingmult, spacingadd, null, null, fm, false, needMultiply, null, DEFAULT_DIR, true, bufEnd, includepad, trackpad, null, null, bufStart, ellipsize, ellipsizedWidth, 0, paint, false);
}
}
use of android.text.style.MetricAffectingSpan in project android_frameworks_base by ParanoidAndroid.
the class TextLine method ascent.
/**
* 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();
}
use of android.text.style.MetricAffectingSpan in project platform_frameworks_base by android.
the class StaticLayout method generate.
/* package */
void generate(Builder b, boolean includepad, boolean trackpad) {
CharSequence source = b.mText;
int bufStart = b.mStart;
int bufEnd = b.mEnd;
TextPaint paint = b.mPaint;
int outerWidth = b.mWidth;
TextDirectionHeuristic textDir = b.mTextDir;
float spacingmult = b.mSpacingMult;
float spacingadd = b.mSpacingAdd;
float ellipsizedWidth = b.mEllipsizedWidth;
TextUtils.TruncateAt ellipsize = b.mEllipsize;
// TODO: move to builder to avoid allocation costs
LineBreaks lineBreaks = new LineBreaks();
// store span end locations
int[] spanEndCache = new int[4];
// store fontMetrics per span range
// must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
int[] fmCache = new int[4 * 4];
// TODO: also respect LocaleSpan within the text
b.setLocale(paint.getTextLocale());
mLineCount = 0;
int v = 0;
boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
Paint.FontMetricsInt fm = b.mFontMetricsInt;
int[] chooseHtv = null;
MeasuredText measured = b.mMeasuredText;
Spanned spanned = null;
if (source instanceof Spanned)
spanned = (Spanned) source;
int paraEnd;
for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
if (paraEnd < 0)
paraEnd = bufEnd;
else
paraEnd++;
int firstWidthLineCount = 1;
int firstWidth = outerWidth;
int restWidth = outerWidth;
LineHeightSpan[] chooseHt = null;
if (spanned != null) {
LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd, LeadingMarginSpan.class);
for (int i = 0; i < sp.length; i++) {
LeadingMarginSpan lms = sp[i];
firstWidth -= sp[i].getLeadingMargin(true);
restWidth -= sp[i].getLeadingMargin(false);
// leading margin spans, not just this particular one
if (lms instanceof LeadingMarginSpan2) {
LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
firstWidthLineCount = Math.max(firstWidthLineCount, lms2.getLeadingMarginLineCount());
}
}
chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
if (chooseHt.length == 0) {
// So that out() would not assume it has any contents
chooseHt = null;
} else {
if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
}
for (int i = 0; i < chooseHt.length; i++) {
int o = spanned.getSpanStart(chooseHt[i]);
if (o < paraStart) {
// starts in this layout, before the
// current paragraph
chooseHtv[i] = getLineTop(getLineForOffset(o));
} else {
// starts in this paragraph
chooseHtv[i] = v;
}
}
}
}
measured.setPara(source, paraStart, paraEnd, textDir, b);
char[] chs = measured.mChars;
float[] widths = measured.mWidths;
byte[] chdirs = measured.mLevels;
int dir = measured.mDir;
boolean easy = measured.mEasy;
// tab stop locations
int[] variableTabStops = null;
if (spanned != null) {
TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, paraEnd, TabStopSpan.class);
if (spans.length > 0) {
int[] stops = new int[spans.length];
for (int i = 0; i < spans.length; i++) {
stops[i] = spans[i].getTabStop();
}
Arrays.sort(stops, 0, stops.length);
variableTabStops = stops;
}
}
nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart, firstWidth, firstWidthLineCount, restWidth, variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency);
if (mLeftIndents != null || mRightIndents != null) {
// TODO(raph) performance: it would be better to do this once per layout rather
// than once per paragraph, but that would require a change to the native
// interface.
int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
int indentsLen = Math.max(1, Math.max(leftLen, rightLen) - mLineCount);
int[] indents = new int[indentsLen];
for (int i = 0; i < indentsLen; i++) {
int leftMargin = mLeftIndents == null ? 0 : mLeftIndents[Math.min(i + mLineCount, leftLen - 1)];
int rightMargin = mRightIndents == null ? 0 : mRightIndents[Math.min(i + mLineCount, rightLen - 1)];
indents[i] = leftMargin + rightMargin;
}
nSetIndents(b.mNativePtr, indents);
}
// measurement has to be done before performing line breaking
// but we don't want to recompute fontmetrics or span ranges the
// second time, so we cache those and then use those stored values
int fmCacheCount = 0;
int spanEndCacheCount = 0;
for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
if (fmCacheCount * 4 >= fmCache.length) {
int[] grow = new int[fmCacheCount * 4 * 2];
System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
fmCache = grow;
}
if (spanEndCacheCount >= spanEndCache.length) {
int[] grow = new int[spanEndCacheCount * 2];
System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
spanEndCache = grow;
}
if (spanned == null) {
spanEnd = paraEnd;
int spanLen = spanEnd - spanStart;
measured.addStyleRun(paint, spanLen, fm);
} else {
spanEnd = spanned.nextSpanTransition(spanStart, paraEnd, MetricAffectingSpan.class);
int spanLen = spanEnd - spanStart;
MetricAffectingSpan[] spans = spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
measured.addStyleRun(paint, spans, spanLen, fm);
}
// the order of storage here (top, bottom, ascent, descent) has to match the code below
// where these values are retrieved
fmCache[fmCacheCount * 4 + 0] = fm.top;
fmCache[fmCacheCount * 4 + 1] = fm.bottom;
fmCache[fmCacheCount * 4 + 2] = fm.ascent;
fmCache[fmCacheCount * 4 + 3] = fm.descent;
fmCacheCount++;
spanEndCache[spanEndCacheCount] = spanEnd;
spanEndCacheCount++;
}
nGetWidths(b.mNativePtr, widths);
int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks, lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
int[] breaks = lineBreaks.breaks;
float[] lineWidths = lineBreaks.widths;
int[] flags = lineBreaks.flags;
final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
final boolean ellipsisMayBeApplied = ellipsize != null && (ellipsize == TextUtils.TruncateAt.END || (mMaximumVisibleLineCount == 1 && ellipsize != TextUtils.TruncateAt.MARQUEE));
if (remainingLineCount > 0 && remainingLineCount < breakCount && ellipsisMayBeApplied) {
// Calculate width and flag.
float width = 0;
int flag = 0;
for (int i = remainingLineCount - 1; i < breakCount; i++) {
if (i == breakCount - 1) {
width += lineWidths[i];
} else {
for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
width += widths[j];
}
}
flag |= flags[i] & TAB_MASK;
}
// Treat the last line and overflowed lines as a single line.
breaks[remainingLineCount - 1] = breaks[breakCount - 1];
lineWidths[remainingLineCount - 1] = width;
flags[remainingLineCount - 1] = flag;
breakCount = remainingLineCount;
}
// here is the offset of the starting character of the line we are currently measuring
int here = paraStart;
int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
int fmCacheIndex = 0;
int spanEndCacheIndex = 0;
int breakIndex = 0;
for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
// retrieve end of span
spanEnd = spanEndCache[spanEndCacheIndex++];
// retrieve cached metrics, order matches above
fm.top = fmCache[fmCacheIndex * 4 + 0];
fm.bottom = fmCache[fmCacheIndex * 4 + 1];
fm.ascent = fmCache[fmCacheIndex * 4 + 2];
fm.descent = fmCache[fmCacheIndex * 4 + 3];
fmCacheIndex++;
if (fm.top < fmTop) {
fmTop = fm.top;
}
if (fm.ascent < fmAscent) {
fmAscent = fm.ascent;
}
if (fm.descent > fmDescent) {
fmDescent = fm.descent;
}
if (fm.bottom > fmBottom) {
fmBottom = fm.bottom;
}
// skip breaks ending before current span range
while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
breakIndex++;
}
while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
int endPos = paraStart + breaks[breakIndex];
boolean moreChars = (endPos < bufEnd);
v = out(source, here, endPos, fmAscent, fmDescent, fmTop, fmBottom, v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex], needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad, chs, widths, paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint, moreChars);
if (endPos < spanEnd) {
// preserve metrics for current span
fmTop = fm.top;
fmBottom = fm.bottom;
fmAscent = fm.ascent;
fmDescent = fm.descent;
} else {
fmTop = fmBottom = fmAscent = fmDescent = 0;
}
here = endPos;
breakIndex++;
if (mLineCount >= mMaximumVisibleLineCount) {
return;
}
}
}
if (paraEnd == bufEnd)
break;
}
if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) && mLineCount < mMaximumVisibleLineCount) {
// Log.e("text", "output last " + bufEnd);
measured.setPara(source, bufEnd, bufEnd, textDir, b);
paint.getFontMetricsInt(fm);
v = out(source, bufEnd, bufEnd, fm.ascent, fm.descent, fm.top, fm.bottom, v, spacingmult, spacingadd, null, null, fm, 0, needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd, includepad, trackpad, null, null, bufStart, ellipsize, ellipsizedWidth, 0, paint, false);
}
}
use of android.text.style.MetricAffectingSpan in project XobotOS by xamarin.
the class TextLine method handleRun.
/**
* Utility function for handling a unidirectional run. The run must not
* contain tabs or emoji but can contain styles.
*
*
* @param start the line-relative start of the run
* @param measureLimit the offset to measure to, between start and limit inclusive
* @param limit the limit of the run
* @param runIsRtl true if the run is right-to-left
* @param c the canvas, can be null
* @param x the end of the run 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 is required
* @return the signed width of the run based on the run direction; only
* valid if needWidth is true
*/
private float handleRun(int start, int measureLimit, int limit, boolean runIsRtl, Canvas c, float x, int top, int y, int bottom, FontMetricsInt fmi, boolean needWidth) {
// Case of an empty line, make sure we update fmi according to mPaint
if (start == measureLimit) {
TextPaint wp = mWorkPaint;
wp.set(mPaint);
if (fmi != null) {
expandMetricsFromPaint(fmi, wp);
}
return 0f;
}
if (mSpanned == null) {
TextPaint wp = mWorkPaint;
wp.set(mPaint);
final int mlimit = measureLimit;
return handleText(wp, start, mlimit, start, limit, runIsRtl, c, x, top, y, bottom, fmi, needWidth || mlimit < measureLimit);
}
final SpanSet<MetricAffectingSpan> metricAffectingSpans = new SpanSet<MetricAffectingSpan>(mSpanned, mStart + start, mStart + limit, MetricAffectingSpan.class);
final SpanSet<CharacterStyle> characterStyleSpans = new SpanSet<CharacterStyle>(mSpanned, mStart + start, mStart + limit, CharacterStyle.class);
// Shaping needs to take into account context up to metric boundaries,
// but rendering needs to take into account character style boundaries.
// So we iterate through metric runs to get metric bounds,
// then within each metric run iterate through character style runs
// for the run bounds.
final float originalX = x;
for (int i = start, inext; i < measureLimit; i = inext) {
TextPaint wp = mWorkPaint;
wp.set(mPaint);
inext = metricAffectingSpans.getNextTransition(mStart + i, mStart + limit) - mStart;
int mlimit = Math.min(inext, measureLimit);
ReplacementSpan replacement = null;
for (int j = 0; j < metricAffectingSpans.numberOfSpans; j++) {
// empty by construction. This special case in getSpans() explains the >= & <= tests
if ((metricAffectingSpans.spanStarts[j] >= mStart + mlimit) || (metricAffectingSpans.spanEnds[j] <= mStart + i))
continue;
MetricAffectingSpan span = metricAffectingSpans.spans[j];
if (span instanceof ReplacementSpan) {
replacement = (ReplacementSpan) span;
} else {
// We might have a replacement that uses the draw
// state, otherwise measure state would suffice.
span.updateDrawState(wp);
}
}
if (replacement != null) {
x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y, bottom, fmi, needWidth || mlimit < measureLimit);
continue;
}
if (c == null) {
x += handleText(wp, i, mlimit, i, inext, runIsRtl, c, x, top, y, bottom, fmi, needWidth || mlimit < measureLimit);
} else {
for (int j = i, jnext; j < mlimit; j = jnext) {
jnext = characterStyleSpans.getNextTransition(mStart + j, mStart + mlimit) - mStart;
wp.set(mPaint);
for (int k = 0; k < characterStyleSpans.numberOfSpans; k++) {
// Intentionally using >= and <= as explained above
if ((characterStyleSpans.spanStarts[k] >= mStart + jnext) || (characterStyleSpans.spanEnds[k] <= mStart + j))
continue;
CharacterStyle span = characterStyleSpans.spans[k];
span.updateDrawState(wp);
}
x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x, top, y, bottom, fmi, needWidth || jnext < measureLimit);
}
}
}
return x - originalX;
}
use of android.text.style.MetricAffectingSpan in project XobotOS by xamarin.
the class TextLine method getOffsetBeforeAfter.
/**
* Returns the next valid offset within this directional run, skipping
* conjuncts and zero-width characters. This should not be called to walk
* off the end of the line, since the returned values might not be valid
* on neighboring lines. If the returned offset is less than zero or
* greater than the line length, the offset should be recomputed on the
* preceding or following line, respectively.
*
* @param runIndex the run index
* @param runStart the start of the run
* @param runLimit the limit of the run
* @param runIsRtl true if the run is right-to-left
* @param offset the offset
* @param after true if the new offset should logically follow the provided
* offset
* @return the new offset
*/
private int getOffsetBeforeAfter(int runIndex, int runStart, int runLimit, boolean runIsRtl, int offset, boolean after) {
if (runIndex < 0 || offset == (after ? mLen : 0)) {
// return accurate values. These are a guess.
if (after) {
return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart;
}
return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart;
}
TextPaint wp = mWorkPaint;
wp.set(mPaint);
int spanStart = runStart;
int spanLimit;
if (mSpanned == null) {
spanLimit = runLimit;
} else {
int target = after ? offset + 1 : offset;
int limit = mStart + runLimit;
while (true) {
spanLimit = mSpanned.nextSpanTransition(mStart + spanStart, limit, MetricAffectingSpan.class) - mStart;
if (spanLimit >= target) {
break;
}
spanStart = spanLimit;
}
MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart, mStart + spanLimit, MetricAffectingSpan.class);
spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
if (spans.length > 0) {
ReplacementSpan replacement = null;
for (int j = 0; j < spans.length; j++) {
MetricAffectingSpan span = spans[j];
if (span instanceof ReplacementSpan) {
replacement = (ReplacementSpan) span;
} else {
span.updateMeasureState(wp);
}
}
if (replacement != null) {
// the start or end of this span.
return after ? spanLimit : spanStart;
}
}
}
int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
if (mCharsValid) {
return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart, flags, offset, cursorOpt);
} else {
return wp.getTextRunCursor(mText, mStart + spanStart, mStart + spanLimit, flags, mStart + offset, cursorOpt) - mStart;
}
}
Aggregations