summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Siyamed Sinir <siyamed@google.com> 2017-07-24 20:46:41 -0700
committer Siyamed Sinir <siyamed@google.com> 2017-07-25 18:31:47 -0700
commit0fa89d6f62c31a033a9dece9c7cfee2d50dc063d (patch)
treea465f53648fe9103231ba0843b8b3c9e2f75ccfa
parent62944851e279c27cb065fc242479d0d091f4566f (diff)
Store line extra in layouts
StaticLayout and Dynamic layout changes the values of line bottom and descent according to linespacing modification attributes. This loses information of the real line descent and bottom, which causes some utilities not to be able to draw themselves in correct positions. Examples are cursor drawing to the empty space, BulletSpan not being able to vertically center itself in the line, and DynamicLayout not being able to calculate the last line height properly. This CL stores the extra added to bottom and descent values so that it can be used later. Test: bit CtsTextTestCases:android.text.cts.LayoutTest Test: bit CtsTextTestCases:android.text.cts.StaticLayoutTest Test: bit CtsTextTestCases:android.text.cts.DynamicLayoutTest Test: bit FrameworksCoreTests:android.text.LayoutTest Test: bit FrameworksCoreTests:android.text.StaticLayoutTest Test: bit FrameworksCoreTests:android.text.DynamicLayoutTest Test: bit FrameworksCoreTests:android.text.DynamicLayoutBlocksTest Bug: 30870806 Bug: 33138492 Bug: 25194907 Change-Id: I7a1039e71e4999c75b5f26122fe6239e3ee24868
-rw-r--r--core/java/android/text/DynamicLayout.java20
-rw-r--r--core/java/android/text/Layout.java12
-rw-r--r--core/java/android/text/StaticLayout.java23
-rw-r--r--core/tests/coretests/src/android/text/DynamicLayoutTest.java74
-rw-r--r--core/tests/coretests/src/android/text/LayoutTest.java11
-rw-r--r--core/tests/coretests/src/android/text/StaticLayoutTest.java122
6 files changed, 212 insertions, 50 deletions
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index c7a5fce26c3e..91d829099917 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -365,6 +365,7 @@ public class DynamicLayout extends Layout
desc += botpad;
ints[DESCENT] = desc;
+ ints[EXTRA] = reflowed.getLineExtra(i);
objects[0] = reflowed.getLineDirections(i);
final int end = (i == n - 1) ? where + after : reflowed.getLineStart(i + 1);
@@ -692,6 +693,14 @@ public class DynamicLayout extends Layout
return mInts.getValue(line, DESCENT);
}
+ /**
+ * @hide
+ */
+ @Override
+ public int getLineExtra(int line) {
+ return mInts.getValue(line, EXTRA);
+ }
+
@Override
public int getLineStart(int line) {
return mInts.getValue(line, START) & START_MASK;
@@ -851,14 +860,15 @@ public class DynamicLayout extends Layout
private static final int TAB = START;
private static final int TOP = 1;
private static final int DESCENT = 2;
+ private static final int EXTRA = 3;
// HYPHEN and MAY_PROTRUDE_FROM_TOP_OR_BOTTOM share the same entry.
- private static final int HYPHEN = 3;
+ private static final int HYPHEN = 4;
private static final int MAY_PROTRUDE_FROM_TOP_OR_BOTTOM = HYPHEN;
- private static final int COLUMNS_NORMAL = 4;
+ private static final int COLUMNS_NORMAL = 5;
- private static final int ELLIPSIS_START = 4;
- private static final int ELLIPSIS_COUNT = 5;
- private static final int COLUMNS_ELLIPSIZE = 6;
+ private static final int ELLIPSIS_START = 5;
+ private static final int ELLIPSIS_COUNT = 6;
+ private static final int COLUMNS_ELLIPSIZE = 7;
private static final int START_MASK = 0x1FFFFFFF;
private static final int DIR_SHIFT = 30;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index a233ba118e7d..ff9ee313200d 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -17,6 +17,7 @@
package android.text;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
@@ -1466,6 +1467,17 @@ public abstract class Layout {
return getLineTop(line) - (getLineTop(line+1) - getLineDescent(line));
}
+ /**
+ * Return the extra space added as a result of line spacing attributes
+ * {@link #getSpacingAdd()} and {@link #getSpacingMultiplier()}. Default value is {@code zero}.
+ *
+ * @param line the index of the line, the value should be equal or greater than {@code zero}
+ * @hide
+ */
+ public int getLineExtra(@IntRange(from = 0) int line) {
+ return 0;
+ }
+
public int getOffsetToLeftOf(int offset) {
return getOffsetToLeftRightOf(offset, true);
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index a8c6aa6cbbbb..16badd50bf79 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -1005,6 +1005,7 @@ public class StaticLayout extends Layout {
lines[off + START] = start;
lines[off + TOP] = v;
lines[off + DESCENT] = below + extra;
+ lines[off + EXTRA] = extra;
// special case for non-ellipsized last visible line when maxLines is set
// store the height as if it was ellipsized
@@ -1195,6 +1196,14 @@ public class StaticLayout extends Layout {
return mLines[mColumns * line + TOP];
}
+ /**
+ * @hide
+ */
+ @Override
+ public int getLineExtra(int line) {
+ return mLines[mColumns * line + EXTRA];
+ }
+
@Override
public int getLineDescent(int line) {
return mLines[mColumns * line + DESCENT];
@@ -1217,6 +1226,9 @@ public class StaticLayout extends Layout {
@Override
public final Directions getLineDirections(int line) {
+ if (line > getLineCount()) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
return mLineDirections[line];
}
@@ -1368,16 +1380,17 @@ public class StaticLayout extends Layout {
*/
private int mMaxLineHeight = -1;
- private static final int COLUMNS_NORMAL = 4;
- private static final int COLUMNS_ELLIPSIZE = 6;
+ private static final int COLUMNS_NORMAL = 5;
+ private static final int COLUMNS_ELLIPSIZE = 7;
private static final int START = 0;
private static final int DIR = START;
private static final int TAB = START;
private static final int TOP = 1;
private static final int DESCENT = 2;
- private static final int HYPHEN = 3;
- private static final int ELLIPSIS_START = 4;
- private static final int ELLIPSIS_COUNT = 5;
+ private static final int EXTRA = 3;
+ private static final int HYPHEN = 4;
+ private static final int ELLIPSIS_START = 5;
+ private static final int ELLIPSIS_COUNT = 6;
private int[] mLines;
private Directions[] mLineDirections;
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutTest.java b/core/tests/coretests/src/android/text/DynamicLayoutTest.java
index 811bf2c43320..5ef08e02029e 100644
--- a/core/tests/coretests/src/android/text/DynamicLayoutTest.java
+++ b/core/tests/coretests/src/android/text/DynamicLayoutTest.java
@@ -18,6 +18,7 @@ package android.text;
import static android.text.Layout.Alignment.ALIGN_NORMAL;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -112,4 +113,77 @@ public class DynamicLayoutTest {
assertFalse(layout.getBlocksAlwaysNeedToBeRedrawn().contains(0));
assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().isEmpty());
}
+
+ @Test
+ public void testGetLineExtra_withoutLinespacing() {
+ final SpannableStringBuilder text = new SpannableStringBuilder("a\nb\nc");
+ final TextPaint textPaint = new TextPaint();
+
+ // create a StaticLayout to check against
+ final StaticLayout staticLayout = StaticLayout.Builder.obtain(text, 0,
+ text.length(), textPaint, WIDTH)
+ .setAlignment(ALIGN_NORMAL)
+ .setIncludePad(false)
+ .build();
+
+ // create the DynamicLayout
+ final DynamicLayout dynamicLayout = new DynamicLayout(text,
+ textPaint,
+ WIDTH,
+ ALIGN_NORMAL,
+ 1f /*spacingMultiplier*/,
+ 0 /*spacingAdd*/,
+ false /*includepad*/);
+
+ final int lineCount = staticLayout.getLineCount();
+ assertEquals(lineCount, dynamicLayout.getLineCount());
+ for (int i = 0; i < lineCount; i++) {
+ assertEquals(staticLayout.getLineExtra(i), dynamicLayout.getLineExtra(i));
+ }
+ }
+
+ @Test
+ public void testGetLineExtra_withLinespacing() {
+ final SpannableStringBuilder text = new SpannableStringBuilder("a\nb\nc");
+ final TextPaint textPaint = new TextPaint();
+ final float spacingMultiplier = 2f;
+ final float spacingAdd = 4;
+
+ // create a StaticLayout to check against
+ final StaticLayout staticLayout = StaticLayout.Builder.obtain(text, 0,
+ text.length(), textPaint, WIDTH)
+ .setAlignment(ALIGN_NORMAL)
+ .setIncludePad(false)
+ .setLineSpacing(spacingAdd, spacingMultiplier)
+ .build();
+
+ // create the DynamicLayout
+ final DynamicLayout dynamicLayout = new DynamicLayout(text,
+ textPaint,
+ WIDTH,
+ ALIGN_NORMAL,
+ spacingMultiplier,
+ spacingAdd,
+ false /*includepad*/);
+
+ final int lineCount = staticLayout.getLineCount();
+ assertEquals(lineCount, dynamicLayout.getLineCount());
+ for (int i = 0; i < lineCount - 1; i++) {
+ assertEquals(staticLayout.getLineExtra(i), dynamicLayout.getLineExtra(i));
+ }
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void testGetLineExtra_withNegativeValue() {
+ final DynamicLayout layout = new DynamicLayout("", new TextPaint(), 10 /*width*/,
+ ALIGN_NORMAL, 1.0f /*spacingMultiplier*/, 0f /*spacingAdd*/, false /*includepad*/);
+ layout.getLineExtra(-1);
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void testGetLineExtra_withParamGreaterThanLineCount() {
+ final DynamicLayout layout = new DynamicLayout("", new TextPaint(), 10 /*width*/,
+ ALIGN_NORMAL, 1.0f /*spacingMultiplier*/, 0f /*spacingAdd*/, false /*includepad*/);
+ layout.getLineExtra(100);
+ }
}
diff --git a/core/tests/coretests/src/android/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java
index 6d610bbbf5e9..6b262ebf2c41 100644
--- a/core/tests/coretests/src/android/text/LayoutTest.java
+++ b/core/tests/coretests/src/android/text/LayoutTest.java
@@ -215,6 +215,17 @@ public class LayoutTest {
}
@Test
+ public void testGetLineExtra_returnsZeroByDefault() {
+ final String text = "a\nb\nc\n";
+ final Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, 100 /* spacingMult*/, 100 /*spacingAdd*/);
+ final int lineCount = text.split("\n").length;
+ for (int i = 0; i < lineCount; i++) {
+ assertEquals(0, layout.getLineExtra(i));
+ }
+ }
+
+ @Test
public void testGetLineVisibleEnd() {
Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
mAlign, mSpacingMult, mSpacingAdd);
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index fb60e38fcbf6..ad16e8944736 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -107,7 +107,7 @@ public class StaticLayoutTest {
Layout l = b.build();
assertVertMetrics(l, 0, 0,
- fmi.ascent, fmi.descent);
+ new int[][]{{fmi.ascent, fmi.descent, 0}});
// other quick metrics
assertEquals(0, l.getLineStart(0));
@@ -124,14 +124,14 @@ public class StaticLayoutTest {
* Top and bottom padding are affected, as is the line descent and height.
*/
@Test
- public void testGetters2() {
+ public void testLineMetrics_withPadding() {
LayoutBuilder b = builder()
.setIncludePad(true);
FontMetricsInt fmi = b.paint.getFontMetricsInt();
Layout l = b.build();
assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
- fmi.top, fmi.bottom);
+ new int[][]{{fmi.top, fmi.bottom, 0}});
}
/**
@@ -139,16 +139,18 @@ public class StaticLayoutTest {
* Ascent of top line and descent of bottom line are affected.
*/
@Test
- public void testGetters3() {
+ public void testLineMetrics_withPaddingAndWidth() {
LayoutBuilder b = builder()
.setIncludePad(true)
.setWidth(50);
FontMetricsInt fmi = b.paint.getFontMetricsInt();
- Layout l = b.build();
+ Layout l = b.build();
assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
- fmi.top, fmi.descent,
- fmi.ascent, fmi.bottom);
+ new int[][]{
+ {fmi.top, fmi.descent, 0},
+ {fmi.ascent, fmi.bottom, 0}
+ });
}
/**
@@ -156,7 +158,7 @@ public class StaticLayoutTest {
* First line ascent is top, bottom line descent is bottom.
*/
@Test
- public void testGetters4() {
+ public void testLineMetrics_withThreeLines() {
LayoutBuilder b = builder()
.setText("This is a longer test")
.setIncludePad(true)
@@ -165,9 +167,11 @@ public class StaticLayoutTest {
Layout l = b.build();
assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
- fmi.top, fmi.descent,
- fmi.ascent, fmi.descent,
- fmi.ascent, fmi.bottom);
+ new int[][]{
+ {fmi.top, fmi.descent, 0},
+ {fmi.ascent, fmi.descent, 0},
+ {fmi.ascent, fmi.bottom, 0}
+ });
}
/**
@@ -176,7 +180,7 @@ public class StaticLayoutTest {
* even be non-zero leading.
*/
@Test
- public void testGetters5() {
+ public void testLineMetrics_withLargeText() {
LayoutBuilder b = builder()
.setText("This is a longer test")
.setIncludePad(true)
@@ -193,9 +197,11 @@ public class StaticLayoutTest {
// using leading, this will fail.
Layout l = b.build();
assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
- fmi.top, fmi.descent,
- fmi.ascent, fmi.descent,
- fmi.ascent, fmi.bottom);
+ new int[][]{
+ {fmi.top, fmi.descent, 0},
+ {fmi.ascent, fmi.descent, 0},
+ {fmi.ascent, fmi.bottom, 0}
+ });
}
/**
@@ -203,7 +209,7 @@ public class StaticLayoutTest {
* to 3 lines.
*/
@Test
- public void testGetters6() {
+ public void testLineMetrics_withSpacingAdd() {
int spacingAdd = 2; // int so expressions return int
LayoutBuilder b = builder()
.setText("This is a longer test")
@@ -214,9 +220,11 @@ public class StaticLayoutTest {
Layout l = b.build();
assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
- fmi.top, fmi.descent + spacingAdd,
- fmi.ascent, fmi.descent + spacingAdd,
- fmi.ascent, fmi.bottom);
+ new int[][]{
+ {fmi.top, fmi.descent + spacingAdd, spacingAdd},
+ {fmi.ascent, fmi.descent + spacingAdd, spacingAdd},
+ {fmi.ascent, fmi.bottom, 0}
+ });
}
/**
@@ -224,7 +232,7 @@ public class StaticLayoutTest {
* spacingMult = 1.5, wrapping to 3 lines.
*/
@Test
- public void testGetters7() {
+ public void testLineMetrics_withSpacingMult() {
LayoutBuilder b = builder()
.setText("This is a longer test")
.setIncludePad(true)
@@ -236,9 +244,13 @@ public class StaticLayoutTest {
Layout l = b.build();
assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
- fmi.top, fmi.descent + s.scale(fmi.descent - fmi.top),
- fmi.ascent, fmi.descent + s.scale(fmi.descent - fmi.ascent),
- fmi.ascent, fmi.bottom);
+ new int[][]{
+ {fmi.top, fmi.descent + s.scale(fmi.descent - fmi.top),
+ s.scale(fmi.descent - fmi.top)},
+ {fmi.ascent, fmi.descent + s.scale(fmi.descent - fmi.ascent),
+ s.scale(fmi.descent - fmi.ascent)},
+ {fmi.ascent, fmi.bottom, 0}
+ });
}
/**
@@ -246,7 +258,7 @@ public class StaticLayoutTest {
* spacingMult = 0.8 when wrapping to 3 lines.
*/
@Test
- public void testGetters8() {
+ public void testLineMetrics_withUnitIntervalSpacingMult() {
LayoutBuilder b = builder()
.setText("This is a longer test")
.setIncludePad(true)
@@ -258,9 +270,25 @@ public class StaticLayoutTest {
Layout l = b.build();
assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
- fmi.top, fmi.descent + s.scale(fmi.descent - fmi.top),
- fmi.ascent, fmi.descent + s.scale(fmi.descent - fmi.ascent),
- fmi.ascent, fmi.bottom);
+ new int[][]{
+ {fmi.top, fmi.descent + s.scale(fmi.descent - fmi.top),
+ s.scale(fmi.descent - fmi.top)},
+ {fmi.ascent, fmi.descent + s.scale(fmi.descent - fmi.ascent),
+ s.scale(fmi.descent - fmi.ascent)},
+ {fmi.ascent, fmi.bottom, 0}
+ });
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void testGetLineExtra_withNegativeValue() {
+ final Layout layout = builder().build();
+ layout.getLineExtra(-1);
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void testGetLineExtra_withParamGreaterThanLineCount() {
+ final Layout layout = builder().build();
+ layout.getLineExtra(100);
}
// ----- test utility classes and methods -----
@@ -341,26 +369,39 @@ public class StaticLayoutTest {
}
}
- private void assertVertMetrics(Layout l, int topPad, int botPad, int... values) {
+ /**
+ * Assert vertical metrics such as top, bottom, ascent, descent.
+ * @param l layout instance
+ * @param topPad top padding
+ * @param botPad bottom padding
+ * @param values values for each line where first is ascent, second is descent, and last one is
+ * extra
+ */
+ private void assertVertMetrics(Layout l, int topPad, int botPad, int[][] values) {
assertTopBotPadding(l, topPad, botPad);
assertLinesMetrics(l, values);
}
- private void assertLinesMetrics(Layout l, int... values) {
- // sanity check
- if ((values.length & 0x1) != 0) {
- throw new IllegalArgumentException(String.valueOf(values.length));
- }
-
- int lines = values.length >> 1;
+ /**
+ * Check given expected values against the Layout values.
+ * @param l layout instance
+ * @param values values for each line where first is ascent, second is descent, and last one is
+ * extra
+ */
+ private void assertLinesMetrics(Layout l, int[][] values) {
+ final int lines = values.length;
assertEquals(lines, l.getLineCount());
int t = 0;
- for (int i = 0, n = 0; i < lines; ++i, n += 2) {
- int a = values[n];
- int d = values[n+1];
+ for (int i = 0, n = 0; i < lines; ++i, n += 3) {
+ if (values[i].length != 3) {
+ throw new IllegalArgumentException(String.valueOf(values.length));
+ }
+ int a = values[i][0];
+ int d = values[i][1];
+ int extra = values[i][2];
int h = -a + d;
- assertLineMetrics(l, i, t, a, d, h);
+ assertLineMetrics(l, i, t, a, d, h, extra);
t += h;
}
@@ -368,12 +409,13 @@ public class StaticLayoutTest {
}
private void assertLineMetrics(Layout l, int line,
- int top, int ascent, int descent, int height) {
+ int top, int ascent, int descent, int height, int extra) {
String info = "line " + line;
assertEquals(info, top, l.getLineTop(line));
assertEquals(info, ascent, l.getLineAscent(line));
assertEquals(info, descent, l.getLineDescent(line));
assertEquals(info, height, l.getLineBottom(line) - top);
+ assertEquals(info, extra, l.getLineExtra(line));
}
private void assertTopBotPadding(Layout l, int topPad, int botPad) {