Fix alignment issues with RTL paragraphs.
Also remove unused debugging code that depends on junit.
Remove trailing whitespace in changed code.
Change-Id: Ie02d1b8220c599a672ee6e91af0fba634e0f620c
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 3b8f295..2f7720a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -30,9 +30,10 @@
import android.text.style.ParagraphStyle;
import android.text.style.ReplacementSpan;
import android.text.style.TabStopSpan;
+import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
import android.view.KeyEvent;
-import junit.framework.Assert;
+import java.util.Arrays;
/**
* A base class that manages text layout in visual elements on
@@ -42,7 +43,6 @@
* For text that will not change, use a {@link StaticLayout}.
*/
public abstract class Layout {
- private static final boolean DEBUG = false;
private static final ParagraphStyle[] NO_PARA_SPANS =
ArrayUtils.emptyArray(ParagraphStyle.class);
@@ -87,8 +87,7 @@
next = end;
// note, omits trailing paragraph char
- float w = measurePara(paint, workPaint,
- source, i, next, true, null);
+ float w = measurePara(paint, workPaint, source, i, next);
if (w > need)
need = w;
@@ -186,7 +185,6 @@
dbottom = sTempRect.bottom;
}
-
int top = 0;
int bottom = getLineTop(getLineCount());
@@ -209,13 +207,15 @@
boolean spannedText = mSpannedText;
ParagraphStyle[] spans = NO_PARA_SPANS;
- int spanend = 0;
+ int spanEnd = 0;
int textLength = 0;
// First, draw LineBackgroundSpans.
- // LineBackgroundSpans know nothing about the alignment or direction of
- // the layout or line. XXX: Should they?
+ // LineBackgroundSpans know nothing about the alignment, margins, or
+ // direction of the layout or line. XXX: Should they?
+ // They are evaluated at each line.
if (spannedText) {
+ Spanned sp = (Spanned) buf;
textLength = buf.length();
for (int i = first; i <= last; i++) {
int start = previousLineEnd;
@@ -227,12 +227,14 @@
previousLineBottom = lbottom;
int lbaseline = lbottom - getLineDescent(i);
- if (start >= spanend) {
- Spanned sp = (Spanned) buf;
- spanend = sp.nextSpanTransition(start, textLength,
- LineBackgroundSpan.class);
- spans = sp.getSpans(start, spanend,
- LineBackgroundSpan.class);
+ if (start >= spanEnd) {
+ // These should be infrequent, so we'll use this so that
+ // we don't have to check as often.
+ spanEnd = sp.nextSpanTransition(start, textLength,
+ LineBackgroundSpan.class);
+ // All LineBackgroundSpans on a line contribute to its
+ // background.
+ spans = sp.getSpans(start, end, LineBackgroundSpan.class);
}
for (int n = 0; n < spans.length; n++) {
@@ -245,7 +247,7 @@
}
}
// reset to their original values
- spanend = 0;
+ spanEnd = 0;
previousLineBottom = getLineTop(first);
previousLineEnd = getLineStart(first);
spans = NO_PARA_SPANS;
@@ -266,8 +268,11 @@
}
Alignment align = mAlignment;
+ TabStops tabStops = null;
+ boolean tabStopsIsInitialized = false;
TextLine tl = TextLine.obtain();
+
// Next draw the lines, one at a time.
// the baseline is the top of the following line minus the current
// line's descent.
@@ -282,18 +287,29 @@
previousLineBottom = lbottom;
int lbaseline = lbottom - getLineDescent(i);
- boolean isFirstParaLine = false;
+ int dir = getParagraphDirection(i);
+ int left = 0;
+ int right = mWidth;
+
if (spannedText) {
- if (start == 0 || buf.charAt(start - 1) == '\n') {
- isFirstParaLine = true;
- }
- // New batch of paragraph styles, compute the alignment.
- // Last alignment style wins.
- if (start >= spanend) {
- Spanned sp = (Spanned) buf;
- spanend = sp.nextSpanTransition(start, textLength,
+ Spanned sp = (Spanned) buf;
+ boolean isFirstParaLine = (start == 0 ||
+ buf.charAt(start - 1) == '\n');
+
+ // New batch of paragraph styles, collect into spans array.
+ // Compute the alignment, last alignment style wins.
+ // Reset tabStops, we'll rebuild if we encounter a line with
+ // tabs.
+ // We expect paragraph spans to be relatively infrequent, use
+ // spanEnd so that we can check less frequently. Since
+ // paragraph styles ought to apply to entire paragraphs, we can
+ // just collect the ones present at the start of the paragraph.
+ // If spanEnd is before the end of the paragraph, that's not
+ // our problem.
+ if (start >= spanEnd && (i == first || isFirstParaLine)) {
+ spanEnd = sp.nextSpanTransition(start, textLength,
ParagraphStyle.class);
- spans = sp.getSpans(start, spanend, ParagraphStyle.class);
+ spans = sp.getSpans(start, spanEnd, ParagraphStyle.class);
align = mAlignment;
for (int n = spans.length-1; n >= 0; n--) {
@@ -302,45 +318,49 @@
break;
}
}
+
+ tabStopsIsInitialized = false;
}
- }
- int dir = getParagraphDirection(i);
- int left = 0;
- int right = mWidth;
-
- // Draw all leading margin spans. Adjust left or right according
- // to the paragraph direction of the line.
- if (spannedText) {
+ // Draw all leading margin spans. Adjust left or right according
+ // to the paragraph direction of the line.
final int length = spans.length;
for (int n = 0; n < length; n++) {
if (spans[n] instanceof LeadingMarginSpan) {
LeadingMarginSpan margin = (LeadingMarginSpan) spans[n];
+ boolean useFirstLineMargin = isFirstParaLine;
+ if (margin instanceof LeadingMarginSpan2) {
+ int count = ((LeadingMarginSpan2) margin).getLeadingMarginLineCount();
+ int startLine = getLineForOffset(sp.getSpanStart(margin));
+ useFirstLineMargin = i < startLine + count;
+ }
if (dir == DIR_RIGHT_TO_LEFT) {
margin.drawLeadingMargin(c, paint, right, dir, ltop,
lbaseline, lbottom, buf,
start, end, isFirstParaLine, this);
-
- right -= margin.getLeadingMargin(isFirstParaLine);
+ right -= margin.getLeadingMargin(useFirstLineMargin);
} else {
margin.drawLeadingMargin(c, paint, left, dir, ltop,
lbaseline, lbottom, buf,
start, end, isFirstParaLine, this);
-
- boolean useMargin = isFirstParaLine;
- if (margin instanceof LeadingMarginSpan.LeadingMarginSpan2) {
- int count = ((LeadingMarginSpan.LeadingMarginSpan2)margin).getLeadingMarginLineCount();
- useMargin = count > i;
- }
- left += margin.getLeadingMargin(useMargin);
+ left += margin.getLeadingMargin(useFirstLineMargin);
}
}
}
}
- // Adjust the point at which to start rendering depending on the
- // alignment of the paragraph.
+ boolean hasTabOrEmoji = getLineContainsTab(i);
+ // Can't tell if we have tabs for sure, currently
+ if (hasTabOrEmoji && !tabStopsIsInitialized) {
+ if (tabStops == null) {
+ tabStops = new TabStops(TAB_INCREMENT, spans);
+ } else {
+ tabStops.reset(TAB_INCREMENT, spans);
+ }
+ tabStopsIsInitialized = true;
+ }
+
int x;
if (align == Alignment.ALIGN_NORMAL) {
if (dir == DIR_LEFT_TO_RIGHT) {
@@ -349,44 +369,83 @@
x = right;
}
} else {
- int max = (int)getLineMax(i, spans, false);
+ int max = (int)getLineExtent(i, tabStops, false);
if (align == Alignment.ALIGN_OPPOSITE) {
- if (dir == DIR_RIGHT_TO_LEFT) {
- x = left + max;
- } else {
+ if (dir == DIR_LEFT_TO_RIGHT) {
x = right - max;
- }
- } else {
- // Alignment.ALIGN_CENTER
- max = max & ~1;
- int half = (right - left - max) >> 1;
- if (dir == DIR_RIGHT_TO_LEFT) {
- x = right - half;
} else {
- x = left + half;
+ x = left - max;
}
+ } else { // Alignment.ALIGN_CENTER
+ max = max & ~1;
+ x = (right + left - max) >> 1;
}
}
Directions directions = getLineDirections(i);
- boolean hasTab = getLineContainsTab(i);
if (directions == DIRS_ALL_LEFT_TO_RIGHT &&
- !spannedText && !hasTab) {
- if (DEBUG) {
- Assert.assertTrue(dir == DIR_LEFT_TO_RIGHT);
- Assert.assertNotNull(c);
- }
+ !spannedText && !hasTabOrEmoji) {
// XXX: assumes there's nothing additional to be done
c.drawText(buf, start, end, x, lbaseline, paint);
} else {
- tl.set(paint, buf, start, end, dir, directions, hasTab, spans);
+ tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops);
tl.draw(c, x, ltop, lbaseline, lbottom);
}
}
+
TextLine.recycle(tl);
}
/**
+ * 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);
+
+ 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 = spanned.getSpans(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 {
+ x = left - max;
+ }
+ } else { // Alignment.ALIGN_CENTER
+ max = max & ~1;
+ x = (left + right - max) >> 1;
+ }
+ }
+ return x;
+ }
+
+ /**
* Return the text that is displayed by this Layout.
*/
public final CharSequence getText() {
@@ -647,45 +706,28 @@
int start = getLineStart(line);
int end = getLineEnd(line);
int dir = getParagraphDirection(line);
- boolean tab = getLineContainsTab(line);
+ boolean hasTabOrEmoji = getLineContainsTab(line);
Directions directions = getLineDirections(line);
- TabStopSpan[] tabs = null;
- if (tab && mText instanceof Spanned) {
- tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
+ 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 = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
+ if (tabs.length > 0) {
+ tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
+ }
}
TextLine tl = TextLine.obtain();
- tl.set(mPaint, mText, start, end, dir, directions, tab, tabs);
+ tl.set(mPaint, mText, start, end, dir, directions, hasTabOrEmoji, tabStops);
float wid = tl.measure(offset - start, trailing, null);
TextLine.recycle(tl);
- Alignment align = getParagraphAlignment(line);
int left = getParagraphLeft(line);
int right = getParagraphRight(line);
- if (align == Alignment.ALIGN_NORMAL) {
- if (dir == DIR_RIGHT_TO_LEFT)
- return right + wid;
- else
- return left + wid;
- }
-
- float max = getLineMax(line);
-
- if (align == Alignment.ALIGN_OPPOSITE) {
- if (dir == DIR_RIGHT_TO_LEFT)
- return left + max + wid;
- else
- return right - max + wid;
- } else { /* align == Alignment.ALIGN_CENTER */
- int imax = ((int) max) & ~1;
-
- if (dir == DIR_RIGHT_TO_LEFT)
- return right - (((right - left) - imax) / 2) + wid;
- else
- return left + ((right - left) - imax) / 2 + wid;
- }
+ return getLineStartPos(line, left, right) + wid;
}
/**
@@ -743,29 +785,73 @@
}
/**
- * Gets the horizontal extent of the specified line, excluding
- * trailing whitespace.
+ * Gets the unsigned horizontal extent of the specified line, including
+ * leading margin indent, but excluding trailing whitespace.
*/
public float getLineMax(int line) {
- return getLineMax(line, null, false);
+ float margin = getParagraphLeadingMargin(line);
+ float signedExtent = getLineExtent(line, false);
+ return margin + signedExtent >= 0 ? signedExtent : -signedExtent;
}
/**
- * Gets the horizontal extent of the specified line, including
- * trailing whitespace.
+ * Gets the unsigned horizontal extent of the specified line, including
+ * leading margin indent and trailing whitespace.
*/
public float getLineWidth(int line) {
- return getLineMax(line, null, true);
+ float margin = getParagraphLeadingMargin(line);
+ float signedExtent = getLineExtent(line, true);
+ return margin + signedExtent >= 0 ? signedExtent : -signedExtent;
}
- private float getLineMax(int line, Object[] tabs, boolean full) {
+ /**
+ * Like {@link #getLineExtent(int,TabStops,boolean)} but determines the
+ * tab stops instead of using the ones passed in.
+ * @param line the index of the line
+ * @param full whether to include trailing whitespace
+ * @return the extent of the line
+ */
+ private float getLineExtent(int line, boolean full) {
int start = getLineStart(line);
int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
- boolean hasTabs = getLineContainsTab(line);
+
+ boolean hasTabsOrEmoji = getLineContainsTab(line);
+ TabStops tabStops = null;
+ if (hasTabsOrEmoji && mText instanceof Spanned) {
+ // Just checking this line should be good enough, tabs should be
+ // consistent across all lines in a paragraph.
+ TabStopSpan[] tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
+ if (tabs.length > 0) {
+ tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
+ }
+ }
Directions directions = getLineDirections(line);
+ int dir = getParagraphDirection(line);
TextLine tl = TextLine.obtain();
- tl.set(mPaint, mText, start, end, 1, directions, hasTabs, tabs);
+ tl.set(mPaint, mText, start, end, dir, directions, hasTabsOrEmoji, tabStops);
+ float width = tl.metrics(null);
+ TextLine.recycle(tl);
+ return width;
+ }
+
+ /**
+ * Returns the signed horizontal extent of the specified line, excluding
+ * leading margin. If full is false, excludes trailing whitespace.
+ * @param line the index of the line
+ * @param tabStops the tab stops, can be null if we know they're not used.
+ * @param full whether to include trailing whitespace
+ * @return the extent of the text on this line
+ */
+ private float getLineExtent(int line, TabStops tabStops, boolean full) {
+ int start = getLineStart(line);
+ int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
+ boolean hasTabsOrEmoji = getLineContainsTab(line);
+ Directions directions = getLineDirections(line);
+ int dir = getParagraphDirection(line);
+
+ TextLine tl = TextLine.obtain();
+ tl.set(mPaint, mText, start, end, dir, directions, hasTabsOrEmoji, tabStops);
float width = tl.metrics(null);
TextLine.recycle(tl);
return width;
@@ -910,10 +996,6 @@
}
private int getLineVisibleEnd(int line, int start, int end) {
- if (DEBUG) {
- Assert.assertTrue(getLineStart(line) == start && getLineStart(line+1) == end);
- }
-
CharSequence text = mText;
char ch;
if (line == getLineCount() - 1) {
@@ -1243,84 +1325,107 @@
* Get the left edge of the specified paragraph, inset by left margins.
*/
public final int getParagraphLeft(int line) {
- int dir = getParagraphDirection(line);
-
int left = 0;
-
- boolean par = false;
- int off = getLineStart(line);
- if (off == 0 || mText.charAt(off - 1) == '\n')
- par = true;
-
- if (dir == DIR_LEFT_TO_RIGHT) {
- if (mSpannedText) {
- Spanned sp = (Spanned) mText;
- LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line),
- getLineEnd(line),
- LeadingMarginSpan.class);
-
- for (int i = 0; i < spans.length; i++) {
- boolean margin = par;
- LeadingMarginSpan span = spans[i];
- if (span instanceof LeadingMarginSpan.LeadingMarginSpan2) {
- int count = ((LeadingMarginSpan.LeadingMarginSpan2)span).getLeadingMarginLineCount();
- margin = count >= line;
- }
- left += span.getLeadingMargin(margin);
- }
- }
+ int dir = getParagraphDirection(line);
+ if (dir == DIR_RIGHT_TO_LEFT || !mSpannedText) {
+ return left; // leading margin has no impact, or no styles
}
-
- return left;
+ return getParagraphLeadingMargin(line);
}
/**
* Get the right edge of the specified paragraph, inset by right margins.
*/
public final int getParagraphRight(int line) {
- int dir = getParagraphDirection(line);
-
int right = mWidth;
+ int dir = getParagraphDirection(line);
+ if (dir == DIR_LEFT_TO_RIGHT || !mSpannedText) {
+ return right; // leading margin has no impact, or no styles
+ }
+ return right - getParagraphLeadingMargin(line);
+ }
- boolean par = false;
- int off = getLineStart(line);
- if (off == 0 || mText.charAt(off - 1) == '\n')
- par = true;
-
-
- if (dir == DIR_RIGHT_TO_LEFT) {
- if (mSpannedText) {
- Spanned sp = (Spanned) mText;
- LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line),
- getLineEnd(line),
- LeadingMarginSpan.class);
-
- for (int i = 0; i < spans.length; i++) {
- right -= spans[i].getLeadingMargin(par);
- }
+ /**
+ * Returns the effective leading margin (unsigned) for this line,
+ * taking into account LeadingMarginSpan and LeadingMarginSpan2.
+ * @param line the line index
+ * @return the leading margin of this line
+ */
+ private int getParagraphLeadingMargin(int line) {
+ if (!mSpannedText) {
+ return 0;
+ }
+ Spanned spanned = (Spanned) mText;
+
+ int lineStart = getLineStart(line);
+ int lineEnd = getLineEnd(line);
+ int spanEnd = spanned.nextSpanTransition(lineStart, lineEnd,
+ LeadingMarginSpan.class);
+ LeadingMarginSpan[] spans = spanned.getSpans(lineStart, spanEnd,
+ LeadingMarginSpan.class);
+ if (spans.length == 0) {
+ return 0; // no leading margin span;
+ }
+
+ int margin = 0;
+
+ boolean isFirstParaLine = lineStart == 0 ||
+ spanned.charAt(lineStart - 1) == '\n';
+
+ for (int i = 0; i < spans.length; i++) {
+ LeadingMarginSpan span = spans[i];
+ boolean useFirstLineMargin = isFirstParaLine;
+ if (span instanceof LeadingMarginSpan2) {
+ int spStart = spanned.getSpanStart(span);
+ int spanLine = getLineForOffset(spStart);
+ int count = ((LeadingMarginSpan2)span).getLeadingMarginLineCount();
+ useFirstLineMargin = line < spanLine + count;
}
+ margin += span.getLeadingMargin(useFirstLineMargin);
}
- return right;
+ return margin;
}
/* package */
static float measurePara(TextPaint paint, TextPaint workPaint,
- CharSequence text, int start, int end, boolean hasTabs,
- Object[] tabs) {
+ CharSequence text, int start, int end) {
MeasuredText mt = MeasuredText.obtain();
TextLine tl = TextLine.obtain();
try {
mt.setPara(text, start, end, DIR_REQUEST_LTR);
Directions directions;
- if (mt.mEasy){
+ 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;
}
- tl.set(paint, text, start, end, 1, directions, hasTabs, tabs);
+ 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 = spanned.getSpans(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);
@@ -1329,6 +1434,67 @@
}
/**
+ * @hide
+ */
+ /* package */ static class TabStops {
+ private int[] mStops;
+ private int mNumStops;
+ private int mIncrement;
+
+ TabStops(int increment, Object[] spans) {
+ reset(increment, spans);
+ }
+
+ void reset(int increment, Object[] spans) {
+ this.mIncrement = increment;
+
+ int ns = 0;
+ if (spans != null) {
+ int[] stops = this.mStops;
+ for (Object o : spans) {
+ if (o instanceof TabStopSpan) {
+ if (stops == null) {
+ stops = new int[10];
+ } else if (ns == stops.length) {
+ int[] nstops = new int[ns * 2];
+ for (int i = 0; i < ns; ++i) {
+ nstops[i] = stops[i];
+ }
+ stops = nstops;
+ }
+ stops[ns++] = ((TabStopSpan) o).getTabStop();
+ }
+ }
+ if (ns > 1) {
+ Arrays.sort(stops, 0, ns);
+ }
+ if (stops != this.mStops) {
+ this.mStops = stops;
+ }
+ }
+ this.mNumStops = ns;
+ }
+
+ float nextTab(float h) {
+ int ns = this.mNumStops;
+ if (ns > 0) {
+ int[] stops = this.mStops;
+ for (int i = 0; i < ns; ++i) {
+ int stop = stops[i];
+ if (stop > h) {
+ return stop;
+ }
+ }
+ }
+ return nextDefaultStop(h, mIncrement);
+ }
+
+ public static float nextDefaultStop(float h, int inc) {
+ return ((int) ((h + inc) / inc)) * inc;
+ }
+ }
+
+ /**
* Returns the position of the next tab stop after h on the line.
*
* @param text the text
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 3d7e35b..1646b9e 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -23,6 +23,8 @@
import android.text.style.LeadingMarginSpan;
import android.text.style.LineHeightSpan;
import android.text.style.MetricAffectingSpan;
+import android.text.style.TabStopSpan;
+import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
/**
* StaticLayout is a Layout for text that will not be edited after it
@@ -146,7 +148,7 @@
paraEnd++;
int paraLen = paraEnd - paraStart;
- int firstWidthLineCount = 1;
+ int firstWidthLineLimit = mLineCount + 1;
int firstwidth = outerwidth;
int restwidth = outerwidth;
@@ -159,10 +161,16 @@
LeadingMarginSpan lms = sp[i];
firstwidth -= sp[i].getLeadingMargin(true);
restwidth -= sp[i].getLeadingMargin(false);
- if (lms instanceof LeadingMarginSpan.LeadingMarginSpan2) {
- firstWidthLineCount =
- ((LeadingMarginSpan.LeadingMarginSpan2)lms)
- .getLeadingMarginLineCount();
+
+ // LeadingMarginSpan2 is odd. The count affects all
+ // leading margin spans, not just this particular one,
+ // and start from the top of the span, not the top of the
+ // paragraph.
+ if (lms instanceof LeadingMarginSpan2) {
+ LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
+ int lmsFirstLine = getLineForOffset(spanned.getSpanStart(lms2));
+ firstWidthLineLimit = lmsFirstLine +
+ lms2.getLeadingMarginLineCount();
}
}
@@ -214,7 +222,9 @@
float fitwidth = w;
int fitascent = 0, fitdescent = 0, fittop = 0, fitbottom = 0;
- boolean tab = false;
+ boolean hasTabOrEmoji = false;
+ boolean hasTab = false;
+ TabStops tabStops = null;
for (int spanStart = paraStart, spanEnd = spanStart, nextSpanStart;
spanStart < paraEnd; spanStart = nextSpanStart) {
@@ -252,8 +262,21 @@
if (c == '\n') {
;
} else if (c == '\t') {
- w = Layout.nextTab(sub, paraStart, paraEnd, w, null);
- tab = true;
+ if (hasTab == false) {
+ hasTab = true;
+ hasTabOrEmoji = true;
+ // First tab this para, check for tabstops
+ TabStopSpan[] spans = spanned.getSpans(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 >= 0xD800 && c <= 0xDFFF && j + 1 < spanEnd) {
int emoji = Character.codePointAt(chs, j - paraStart);
@@ -275,7 +298,7 @@
bm.getHeight();
w += wid;
- tab = true;
+ hasTabOrEmoji = true;
j++;
} else {
w += widths[j - paraStart];
@@ -351,7 +374,7 @@
okascent, okdescent, oktop, okbottom,
v,
spacingmult, spacingadd, chooseht,
- choosehtv, fm, tab,
+ choosehtv, fm, hasTabOrEmoji,
needMultiply, paraStart, chdirs, dir, easy,
ok == bufend, includepad, trackpad,
chs, widths, here - paraStart,
@@ -387,7 +410,7 @@
okascent, okdescent, oktop, okbottom,
v,
spacingmult, spacingadd, chooseht,
- choosehtv, fm, tab,
+ choosehtv, fm, hasTabOrEmoji,
needMultiply, paraStart, chdirs, dir, easy,
ok == bufend, includepad, trackpad,
chs, widths, here - paraStart,
@@ -403,7 +426,7 @@
fittop, fitbottom,
v,
spacingmult, spacingadd, chooseht,
- choosehtv, fm, tab,
+ choosehtv, fm, hasTabOrEmoji,
needMultiply, paraStart, chdirs, dir, easy,
fit == bufend, includepad, trackpad,
chs, widths, here - paraStart,
@@ -424,7 +447,7 @@
fm.top, fm.bottom,
v,
spacingmult, spacingadd, chooseht,
- choosehtv, fm, tab,
+ choosehtv, fm, hasTabOrEmoji,
needMultiply, paraStart, chdirs, dir, easy,
here + 1 == bufend, includepad,
trackpad,
@@ -449,7 +472,7 @@
fitascent = fitdescent = fittop = fitbottom = 0;
okascent = okdescent = oktop = okbottom = 0;
- if (--firstWidthLineCount <= 0) {
+ if (--firstWidthLineLimit <= 0) {
width = restwidth;
}
}
@@ -473,7 +496,7 @@
fittop, fitbottom,
v,
spacingmult, spacingadd, chooseht,
- choosehtv, fm, tab,
+ choosehtv, fm, hasTabOrEmoji,
needMultiply, paraStart, chdirs, dir, easy,
paraEnd == bufend, includepad, trackpad,
chs, widths, here - paraStart,
@@ -613,7 +636,7 @@
int above, int below, int top, int bottom, int v,
float spacingmult, float spacingadd,
LineHeightSpan[] chooseht, int[] choosehtv,
- Paint.FontMetricsInt fm, boolean tab,
+ Paint.FontMetricsInt fm, boolean hasTabOrEmoji,
boolean needMultiply, int pstart, byte[] chdirs,
int dir, boolean easy, boolean last,
boolean includepad, boolean trackpad,
@@ -700,7 +723,7 @@
lines[off + mColumns + START] = end;
lines[off + mColumns + TOP] = v;
- if (tab)
+ if (hasTabOrEmoji)
lines[off + TAB] |= TAB_MASK;
lines[off + DIR] |= dir << DIR_SHIFT;
@@ -914,7 +937,7 @@
private static final int DIR_SHIFT = 30;
private static final int TAB_MASK = 0x20000000;
- private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
+ private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
/*
* This is reused across calls to generate()
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index fae3fc3..bd410c8 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -25,10 +25,10 @@
import android.graphics.Paint.FontMetricsInt;
import android.icu.text.ArabicShaping;
import android.text.Layout.Directions;
+import android.text.Layout.TabStops;
import android.text.style.CharacterStyle;
import android.text.style.MetricAffectingSpan;
import android.text.style.ReplacementSpan;
-import android.text.style.TabStopSpan;
import android.util.Log;
/**
@@ -51,7 +51,7 @@
private int mDir;
private Directions mDirections;
private boolean mHasTabs;
- private TabStopSpan[] mTabs;
+ private TabStops mTabs;
private char[] mChars;
private boolean mCharsValid;
@@ -117,11 +117,10 @@
* @param dir the paragraph direction of this line
* @param directions the directions information of this line
* @param hasTabs true if the line might contain tabs or emoji
- * @param spans array of paragraph-level spans, of which only TabStopSpans
- * are used. Can be null.
+ * @param tabStops the tabStops. Can be null.
*/
void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
- Directions directions, boolean hasTabs, Object[] spans) {
+ Directions directions, boolean hasTabs, TabStops tabStops) {
mPaint = paint;
mText = text;
mStart = start;
@@ -148,38 +147,8 @@
mChars = new char[ArrayUtils.idealCharArraySize(mLen)];
}
TextUtils.getChars(text, start, limit, mChars, 0);
-
- if (hasTabs) {
- TabStopSpan[] tabs = mTabs;
- int tabLen = 0;
- if (mSpanned != null && spans == null) {
- TabStopSpan[] newTabs = mSpanned.getSpans(start, limit,
- TabStopSpan.class);
- if (tabs == null || tabs.length < newTabs.length) {
- tabs = newTabs;
- } else {
- for (int i = 0; i < newTabs.length; ++i) {
- tabs[i] = newTabs[i];
- }
- }
- tabLen = newTabs.length;
- } else if (spans != null) {
- if (tabs == null || tabs.length < spans.length) {
- tabs = new TabStopSpan[spans.length];
- }
- for (int i = 0; i < spans.length; ++i) {
- if (spans[i] instanceof TabStopSpan) {
- tabs[tabLen++] = (TabStopSpan) spans[i];
- }
- }
- }
-
- if (tabs != null && tabLen < tabs.length){
- tabs[tabLen] = null;
- }
- mTabs = tabs;
- }
}
+ mTabs = tabStops;
}
/**
@@ -993,23 +962,10 @@
* @return the (unsigned) tab position after this offset
*/
float nextTab(float h) {
- float nh = Float.MAX_VALUE;
- boolean alltabs = false;
-
- if (mHasTabs && mTabs != null) {
- TabStopSpan[] tabs = mTabs;
- for (int i = 0; i < tabs.length && tabs[i] != null; ++i) {
- int where = tabs[i].getTabStop();
- if (where < nh && where > h) {
- nh = where;
- }
- }
- if (nh != Float.MAX_VALUE) {
- return nh;
- }
+ if (mTabs != null) {
+ return mTabs.nextTab(h);
}
-
- return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT;
+ return TabStops.nextDefaultStop(h, TAB_INCREMENT);
}
private static final int TAB_INCREMENT = 20;
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index 42ad10e..c30db20 100644
--- a/core/java/android/text/method/Touch.java
+++ b/core/java/android/text/method/Touch.java
@@ -18,13 +18,12 @@
import android.text.Layout;
import android.text.NoCopySpan;
-import android.text.Layout.Alignment;
import android.text.Spannable;
-import android.util.Log;
+import android.text.Layout.Alignment;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.TextView;
-import android.view.KeyEvent;
public class Touch {
private Touch() { }
@@ -45,6 +44,7 @@
int left = Integer.MAX_VALUE;
int right = 0;
Alignment a = null;
+ boolean ltr = true;
for (int i = top; i <= bottom; i++) {
left = (int) Math.min(left, layout.getLineLeft(i));
@@ -52,6 +52,7 @@
if (a == null) {
a = layout.getParagraphAlignment(i);
+ ltr = layout.getParagraphDirection(i) > 0;
}
}
@@ -59,10 +60,12 @@
int width = widget.getWidth();
int diff = 0;
+ // align_opposite does NOT mean align_right, we need the paragraph
+ // direction to resolve it to left or right
if (right - left < width - padding) {
if (a == Alignment.ALIGN_CENTER) {
diff = (width - padding - (right - left)) / 2;
- } else if (a == Alignment.ALIGN_OPPOSITE) {
+ } else if (ltr == (a == Alignment.ALIGN_OPPOSITE)) {
diff = width - padding - (right - left);
}
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3466c17..0de4f4e 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4847,6 +4847,8 @@
break;
case Gravity.RIGHT:
+ // Note, Layout resolves ALIGN_OPPOSITE to left or
+ // right based on the paragraph direction.
alignment = Layout.Alignment.ALIGN_OPPOSITE;
break;
@@ -5668,11 +5670,15 @@
final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
+ // line might contain bidirectional text
+ final int lowChar = leftChar < rightChar ? leftChar : rightChar;
+ final int highChar = leftChar > rightChar ? leftChar : rightChar;
+
int newStart = start;
- if (newStart < leftChar) {
- newStart = leftChar;
- } else if (newStart > rightChar) {
- newStart = rightChar;
+ if (newStart < lowChar) {
+ newStart = lowChar;
+ } else if (newStart > highChar) {
+ newStart = highChar;
}
if (newStart != start) {