use of android.text.style.TabStopSpan 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.TabStopSpan in project android_frameworks_base by ParanoidAndroid.
the class Layout method measurePara.
/* package */
static float measurePara(TextPaint paint, CharSequence text, int start, int end) {
MeasuredText mt = MeasuredText.obtain();
TextLine tl = TextLine.obtain();
try {
mt.setPara(text, start, end, TextDirectionHeuristics.LTR);
Directions directions;
int dir;
if (mt.mEasy) {
directions = DIRS_ALL_LEFT_TO_RIGHT;
dir = Layout.DIR_LEFT_TO_RIGHT;
} else {
directions = AndroidBidi.directions(mt.mDir, mt.mLevels, 0, mt.mChars, 0, mt.mLen);
dir = mt.mDir;
}
char[] chars = mt.mChars;
int len = mt.mLen;
boolean hasTabs = false;
TabStops tabStops = null;
for (int i = 0; i < len; ++i) {
if (chars[i] == '\t') {
hasTabs = true;
if (text instanceof Spanned) {
Spanned spanned = (Spanned) text;
int spanEnd = spanned.nextSpanTransition(start, end, TabStopSpan.class);
TabStopSpan[] spans = getParagraphSpans(spanned, start, spanEnd, TabStopSpan.class);
if (spans.length > 0) {
tabStops = new TabStops(TAB_INCREMENT, spans);
}
}
break;
}
}
tl.set(paint, text, start, end, dir, directions, hasTabs, tabStops);
return tl.metrics(null);
} finally {
TextLine.recycle(tl);
MeasuredText.recycle(mt);
}
}
use of android.text.style.TabStopSpan in project android_frameworks_base by ParanoidAndroid.
the class Layout method getHorizontal.
private float getHorizontal(int offset, boolean trailing, int line, boolean clamped) {
int start = getLineStart(line);
int end = getLineEnd(line);
int dir = getParagraphDirection(line);
boolean hasTabOrEmoji = getLineContainsTab(line);
Directions directions = getLineDirections(line);
TabStops tabStops = null;
if (hasTabOrEmoji && mText instanceof Spanned) {
// Just checking this line should be good enough, tabs should be
// consistent across all lines in a paragraph.
TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class);
if (tabs.length > 0) {
// XXX should reuse
tabStops = new TabStops(TAB_INCREMENT, tabs);
}
}
TextLine tl = TextLine.obtain();
tl.set(mPaint, mText, start, end, dir, directions, hasTabOrEmoji, tabStops);
float wid = tl.measure(offset - start, trailing, null);
TextLine.recycle(tl);
if (clamped && wid > mWidth) {
wid = mWidth;
}
int left = getParagraphLeft(line);
int right = getParagraphRight(line);
return getLineStartPos(line, left, right) + wid;
}
use of android.text.style.TabStopSpan in project android_frameworks_base by ParanoidAndroid.
the class Layout method getLineStartPos.
/**
* Return the start position of the line, given the left and right bounds
* of the margins.
*
* @param line the line index
* @param left the left bounds (0, or leading margin if ltr para)
* @param right the right bounds (width, minus leading margin if rtl para)
* @return the start position of the line (to right of line if rtl para)
*/
private int getLineStartPos(int line, int left, int right) {
// Adjust the point at which to start rendering depending on the
// alignment of the paragraph.
Alignment align = getParagraphAlignment(line);
int dir = getParagraphDirection(line);
if (align == Alignment.ALIGN_LEFT) {
align = (dir == DIR_LEFT_TO_RIGHT) ? Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
} else if (align == Alignment.ALIGN_RIGHT) {
align = (dir == DIR_LEFT_TO_RIGHT) ? Alignment.ALIGN_OPPOSITE : Alignment.ALIGN_NORMAL;
}
int x;
if (align == Alignment.ALIGN_NORMAL) {
if (dir == DIR_LEFT_TO_RIGHT) {
x = left;
} else {
x = right;
}
} else {
TabStops tabStops = null;
if (mSpannedText && getLineContainsTab(line)) {
Spanned spanned = (Spanned) mText;
int start = getLineStart(line);
int spanEnd = spanned.nextSpanTransition(start, spanned.length(), TabStopSpan.class);
TabStopSpan[] tabSpans = getParagraphSpans(spanned, start, spanEnd, TabStopSpan.class);
if (tabSpans.length > 0) {
tabStops = new TabStops(TAB_INCREMENT, tabSpans);
}
}
int max = (int) getLineExtent(line, tabStops, false);
if (align == Alignment.ALIGN_OPPOSITE) {
if (dir == DIR_LEFT_TO_RIGHT) {
x = right - max;
} else {
// max is negative here
x = left - max;
}
} else {
// Alignment.ALIGN_CENTER
max = max & ~1;
x = (left + right - max) >> 1;
}
}
return x;
}
use of android.text.style.TabStopSpan in project android_frameworks_base by ParanoidAndroid.
the class Layout method nextTab.
/**
* Returns the position of the next tab stop after h on the line.
*
* @param text the text
* @param start start of the line
* @param end limit of the line
* @param h the current horizontal offset
* @param tabs the tabs, can be null. If it is null, any tabs in effect
* on the line will be used. If there are no tabs, a default offset
* will be used to compute the tab stop.
* @return the offset of the next tab stop.
*/
/* package */
static float nextTab(CharSequence text, int start, int end, float h, Object[] tabs) {
float nh = Float.MAX_VALUE;
boolean alltabs = false;
if (text instanceof Spanned) {
if (tabs == null) {
tabs = getParagraphSpans((Spanned) text, start, end, TabStopSpan.class);
alltabs = true;
}
for (int i = 0; i < tabs.length; i++) {
if (!alltabs) {
if (!(tabs[i] instanceof TabStopSpan))
continue;
}
int where = ((TabStopSpan) tabs[i]).getTabStop();
if (where < nh && where > h)
nh = where;
}
if (nh != Float.MAX_VALUE)
return nh;
}
return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT;
}
Aggregations