Merge "Reorganize MeasuredText API (2nd)"
This commit is contained in:
@@ -45,7 +45,7 @@ import java.util.Random;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class MeasuredTextMemoryUsageTest {
|
||||
public class PrecomputedTextMemoryUsageTest {
|
||||
private static final int WORD_LENGTH = 9; // Random word has 9 characters.
|
||||
private static final boolean NO_STYLE_TEXT = false;
|
||||
|
||||
@@ -53,7 +53,7 @@ public class MeasuredTextMemoryUsageTest {
|
||||
|
||||
private static int TRIAL_COUNT = 100;
|
||||
|
||||
public MeasuredTextMemoryUsageTest() {}
|
||||
public PrecomputedTextMemoryUsageTest() {}
|
||||
|
||||
private TextPerfUtils mTextUtil = new TextPerfUtils();
|
||||
|
||||
@@ -77,13 +77,16 @@ public class MeasuredTextMemoryUsageTest {
|
||||
@Test
|
||||
public void testMemoryUsage_NoHyphenation() {
|
||||
int[] memories = new int[TRIAL_COUNT];
|
||||
// Report median of randomly generated MeasuredText.
|
||||
for (int i = 0; i < TRIAL_COUNT; ++i) {
|
||||
memories[i] = new MeasuredText.Builder(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
|
||||
final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
|
||||
.build().getMemoryUsage();
|
||||
.build();
|
||||
|
||||
// Report median of randomly generated PrecomputedText.
|
||||
for (int i = 0; i < TRIAL_COUNT; ++i) {
|
||||
memories[i] = PrecomputedText.create(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), param)
|
||||
.getMemoryUsage();
|
||||
}
|
||||
reportMemoryUsage(median(memories), "MemoryUsage_NoHyphenation");
|
||||
}
|
||||
@@ -91,13 +94,16 @@ public class MeasuredTextMemoryUsageTest {
|
||||
@Test
|
||||
public void testMemoryUsage_Hyphenation() {
|
||||
int[] memories = new int[TRIAL_COUNT];
|
||||
// Report median of randomly generated MeasuredText.
|
||||
for (int i = 0; i < TRIAL_COUNT; ++i) {
|
||||
memories[i] = new MeasuredText.Builder(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
|
||||
final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
|
||||
.build().getMemoryUsage();
|
||||
.build();
|
||||
|
||||
// Report median of randomly generated PrecomputedText.
|
||||
for (int i = 0; i < TRIAL_COUNT; ++i) {
|
||||
memories[i] = PrecomputedText.create(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), param)
|
||||
.getMemoryUsage();
|
||||
}
|
||||
reportMemoryUsage(median(memories), "MemoryUsage_Hyphenation");
|
||||
}
|
||||
@@ -105,13 +111,16 @@ public class MeasuredTextMemoryUsageTest {
|
||||
@Test
|
||||
public void testMemoryUsage_NoHyphenation_WidthOnly() {
|
||||
int[] memories = new int[TRIAL_COUNT];
|
||||
// Report median of randomly generated MeasuredText.
|
||||
for (int i = 0; i < TRIAL_COUNT; ++i) {
|
||||
memories[i] = new MeasuredText.Builder(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
|
||||
final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
|
||||
.build(false /* width only */).getMemoryUsage();
|
||||
.build();
|
||||
|
||||
// Report median of randomly generated PrecomputedText.
|
||||
for (int i = 0; i < TRIAL_COUNT; ++i) {
|
||||
CharSequence cs = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
|
||||
memories[i] = PrecomputedText.createWidthOnly(cs, param, 0, cs.length())
|
||||
.getMemoryUsage();
|
||||
}
|
||||
reportMemoryUsage(median(memories), "MemoryUsage_NoHyphenation_WidthOnly");
|
||||
}
|
||||
@@ -119,13 +128,16 @@ public class MeasuredTextMemoryUsageTest {
|
||||
@Test
|
||||
public void testMemoryUsage_Hyphenatation_WidthOnly() {
|
||||
int[] memories = new int[TRIAL_COUNT];
|
||||
// Report median of randomly generated MeasuredText.
|
||||
for (int i = 0; i < TRIAL_COUNT; ++i) {
|
||||
memories[i] = new MeasuredText.Builder(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
|
||||
final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
|
||||
.build(false /* width only */).getMemoryUsage();
|
||||
.build();
|
||||
|
||||
// Report median of randomly generated PrecomputedText.
|
||||
for (int i = 0; i < TRIAL_COUNT; ++i) {
|
||||
CharSequence cs = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
|
||||
memories[i] = PrecomputedText.createWidthOnly(cs, param, 0, cs.length())
|
||||
.getMemoryUsage();
|
||||
}
|
||||
reportMemoryUsage(median(memories), "MemoryUsage_Hyphenation_WidthOnly");
|
||||
}
|
||||
@@ -42,7 +42,7 @@ import java.util.Random;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class MeasuredTextPerfTest {
|
||||
public class PrecomputedTextPerfTest {
|
||||
private static final int WORD_LENGTH = 9; // Random word has 9 characters.
|
||||
private static final int WORDS_IN_LINE = 8; // Roughly, 8 words in a line.
|
||||
private static final boolean NO_STYLE_TEXT = false;
|
||||
@@ -51,7 +51,7 @@ public class MeasuredTextPerfTest {
|
||||
private static TextPaint PAINT = new TextPaint();
|
||||
private static final int TEXT_WIDTH = WORDS_IN_LINE * WORD_LENGTH * (int) PAINT.getTextSize();
|
||||
|
||||
public MeasuredTextPerfTest() {}
|
||||
public PrecomputedTextPerfTest() {}
|
||||
|
||||
@Rule
|
||||
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
|
||||
@@ -66,120 +66,136 @@ public class MeasuredTextPerfTest {
|
||||
@Test
|
||||
public void testCreate_NoStyled_Hyphenation() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
|
||||
.build();
|
||||
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
|
||||
state.resumeTiming();
|
||||
|
||||
new MeasuredText.Builder(text, PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
|
||||
.build(true /* do full layout */);
|
||||
PrecomputedText.create(text, param);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_NoStyled_NoHyphenation() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
|
||||
.build();
|
||||
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
|
||||
state.resumeTiming();
|
||||
|
||||
new MeasuredText.Builder(text, PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
|
||||
.build(true /* do full layout */);
|
||||
PrecomputedText.create(text, param);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_NoStyled_Hyphenation_WidthOnly() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
|
||||
.build();
|
||||
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
|
||||
state.resumeTiming();
|
||||
|
||||
new MeasuredText.Builder(text, PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
|
||||
.build(false /* width only */);
|
||||
PrecomputedText.create(text, param);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_NoStyled_NoHyphenation_WidthOnly() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
|
||||
.build();
|
||||
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
|
||||
state.resumeTiming();
|
||||
|
||||
new MeasuredText.Builder(text, PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
|
||||
.build(false /* width only */);
|
||||
PrecomputedText.create(text, param);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_Styled_Hyphenation() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
|
||||
.build();
|
||||
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
|
||||
state.resumeTiming();
|
||||
|
||||
new MeasuredText.Builder(text, PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
|
||||
.build(true /* do full layout */);
|
||||
PrecomputedText.create(text, param);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_Styled_NoHyphenation() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
|
||||
.build();
|
||||
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
|
||||
state.resumeTiming();
|
||||
|
||||
new MeasuredText.Builder(text, PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
|
||||
.build(true /* do full layout */);
|
||||
PrecomputedText.create(text, param);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_Styled_Hyphenation_WidthOnly() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
|
||||
.build();
|
||||
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
|
||||
state.resumeTiming();
|
||||
|
||||
new MeasuredText.Builder(text, PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
|
||||
.build(false /* width only */);
|
||||
PrecomputedText.create(text, param);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_Styled_NoHyphenation_WidthOnly() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
|
||||
.build();
|
||||
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
|
||||
state.resumeTiming();
|
||||
|
||||
new MeasuredText.Builder(text, PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
|
||||
.build(false /* width only */);
|
||||
PrecomputedText.create(text, param);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,18 @@ public class StaticLayoutPerfTest {
|
||||
mTextUtil.resetRandom(0 /* seed */);
|
||||
}
|
||||
|
||||
private PrecomputedText makeMeasured(CharSequence text, TextPaint paint) {
|
||||
PrecomputedText.Params param = new PrecomputedText.Params.Builder(paint).build();
|
||||
return PrecomputedText.create(text, param);
|
||||
}
|
||||
|
||||
private PrecomputedText makeMeasured(CharSequence text, TextPaint paint, int strategy,
|
||||
int frequency) {
|
||||
PrecomputedText.Params param = new PrecomputedText.Params.Builder(paint)
|
||||
.setHyphenationFrequency(frequency).setBreakStrategy(strategy).build();
|
||||
return PrecomputedText.create(text, param);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_FixedText_NoStyle_Greedy_NoHyphenation() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
@@ -151,15 +163,13 @@ public class StaticLayoutPerfTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_MeasuredText_NoStyled_Greedy_NoHyphenation() {
|
||||
public void testCreate_PrecomputedText_NoStyled_Greedy_NoHyphenation() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final MeasuredText text = new MeasuredText.Builder(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
|
||||
.build();
|
||||
final PrecomputedText text = makeMeasured(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT,
|
||||
Layout.BREAK_STRATEGY_SIMPLE, Layout.HYPHENATION_FREQUENCY_NONE);
|
||||
state.resumeTiming();
|
||||
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
|
||||
@@ -170,15 +180,13 @@ public class StaticLayoutPerfTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_MeasuredText_NoStyled_Greedy_Hyphenation() {
|
||||
public void testCreate_PrecomputedText_NoStyled_Greedy_Hyphenation() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final MeasuredText text = new MeasuredText.Builder(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
|
||||
.build();
|
||||
final PrecomputedText text = makeMeasured(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT,
|
||||
Layout.BREAK_STRATEGY_SIMPLE, Layout.HYPHENATION_FREQUENCY_NORMAL);
|
||||
state.resumeTiming();
|
||||
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
|
||||
@@ -189,15 +197,13 @@ public class StaticLayoutPerfTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_MeasuredText_NoStyled_Balanced_NoHyphenation() {
|
||||
public void testCreate_PrecomputedText_NoStyled_Balanced_NoHyphenation() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final MeasuredText text = new MeasuredText.Builder(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
|
||||
.build();
|
||||
final PrecomputedText text = makeMeasured(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT,
|
||||
Layout.BREAK_STRATEGY_BALANCED, Layout.HYPHENATION_FREQUENCY_NONE);
|
||||
state.resumeTiming();
|
||||
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
|
||||
@@ -208,15 +214,13 @@ public class StaticLayoutPerfTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_MeasuredText_NoStyled_Balanced_Hyphenation() {
|
||||
public void testCreate_PrecomputedText_NoStyled_Balanced_Hyphenation() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final MeasuredText text = new MeasuredText.Builder(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
|
||||
.build();
|
||||
final PrecomputedText text = makeMeasured(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT,
|
||||
Layout.BREAK_STRATEGY_BALANCED, Layout.HYPHENATION_FREQUENCY_NORMAL);
|
||||
state.resumeTiming();
|
||||
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
|
||||
@@ -227,15 +231,13 @@ public class StaticLayoutPerfTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_MeasuredText_Styled_Greedy_NoHyphenation() {
|
||||
public void testCreate_PrecomputedText_Styled_Greedy_NoHyphenation() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final MeasuredText text = new MeasuredText.Builder(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
|
||||
.build();
|
||||
final PrecomputedText text = makeMeasured(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT,
|
||||
Layout.BREAK_STRATEGY_SIMPLE, Layout.HYPHENATION_FREQUENCY_NONE);
|
||||
state.resumeTiming();
|
||||
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
|
||||
@@ -328,13 +330,13 @@ public class StaticLayoutPerfTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDraw_MeasuredText_Styled() {
|
||||
public void testDraw_PrecomputedText_Styled() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
final RenderNode node = RenderNode.create("benchmark", null);
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final MeasuredText text = new MeasuredText.Builder(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build();
|
||||
final PrecomputedText text = makeMeasured(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT);
|
||||
final StaticLayout layout =
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
|
||||
final DisplayListCanvas c = node.start(1200, 200);
|
||||
@@ -345,13 +347,13 @@ public class StaticLayoutPerfTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDraw_MeasuredText_NoStyled() {
|
||||
public void testDraw_PrecomputedText_NoStyled() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
final RenderNode node = RenderNode.create("benchmark", null);
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final MeasuredText text = new MeasuredText.Builder(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build();
|
||||
final PrecomputedText text = makeMeasured(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT);
|
||||
final StaticLayout layout =
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
|
||||
final DisplayListCanvas c = node.start(1200, 200);
|
||||
@@ -362,13 +364,13 @@ public class StaticLayoutPerfTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDraw_MeasuredText_Styled_WithoutCache() {
|
||||
public void testDraw_PrecomputedText_Styled_WithoutCache() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
final RenderNode node = RenderNode.create("benchmark", null);
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final MeasuredText text = new MeasuredText.Builder(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build();
|
||||
final PrecomputedText text = makeMeasured(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT);
|
||||
final StaticLayout layout =
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
|
||||
final DisplayListCanvas c = node.start(1200, 200);
|
||||
@@ -380,13 +382,13 @@ public class StaticLayoutPerfTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDraw_MeasuredText_NoStyled_WithoutCache() {
|
||||
public void testDraw_PrecomputedText_NoStyled_WithoutCache() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
final RenderNode node = RenderNode.create("benchmark", null);
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final MeasuredText text = new MeasuredText.Builder(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build();
|
||||
final PrecomputedText text = makeMeasured(
|
||||
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT);
|
||||
final StaticLayout layout =
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
|
||||
final DisplayListCanvas c = node.start(1200, 200);
|
||||
|
||||
@@ -13840,6 +13840,7 @@ package android.graphics {
|
||||
method public int breakText(java.lang.String, boolean, float, float[]);
|
||||
method public void clearShadowLayer();
|
||||
method public float descent();
|
||||
method public boolean equalsForTextMeasurement(android.graphics.Paint);
|
||||
method public int getAlpha();
|
||||
method public int getColor();
|
||||
method public android.graphics.ColorFilter getColorFilter();
|
||||
@@ -43217,36 +43218,6 @@ package android.text {
|
||||
method public boolean isAllowed(char);
|
||||
}
|
||||
|
||||
public class MeasuredText implements android.text.Spanned {
|
||||
method public char charAt(int);
|
||||
method public int getBreakStrategy();
|
||||
method public int getEnd();
|
||||
method public int getHyphenationFrequency();
|
||||
method public android.text.TextPaint getPaint();
|
||||
method public int getParagraphCount();
|
||||
method public int getParagraphEnd(int);
|
||||
method public int getParagraphStart(int);
|
||||
method public int getSpanEnd(java.lang.Object);
|
||||
method public int getSpanFlags(java.lang.Object);
|
||||
method public int getSpanStart(java.lang.Object);
|
||||
method public <T> T[] getSpans(int, int, java.lang.Class<T>);
|
||||
method public int getStart();
|
||||
method public java.lang.CharSequence getText();
|
||||
method public android.text.TextDirectionHeuristic getTextDir();
|
||||
method public int length();
|
||||
method public int nextSpanTransition(int, int, java.lang.Class);
|
||||
method public java.lang.CharSequence subSequence(int, int);
|
||||
}
|
||||
|
||||
public static final class MeasuredText.Builder {
|
||||
ctor public MeasuredText.Builder(java.lang.CharSequence, android.text.TextPaint);
|
||||
method public android.text.MeasuredText build();
|
||||
method public android.text.MeasuredText.Builder setBreakStrategy(int);
|
||||
method public android.text.MeasuredText.Builder setHyphenationFrequency(int);
|
||||
method public android.text.MeasuredText.Builder setRange(int, int);
|
||||
method public android.text.MeasuredText.Builder setTextDirection(android.text.TextDirectionHeuristic);
|
||||
}
|
||||
|
||||
public abstract interface NoCopySpan {
|
||||
}
|
||||
|
||||
@@ -43258,6 +43229,38 @@ package android.text {
|
||||
method public abstract int getSpanTypeId();
|
||||
}
|
||||
|
||||
public class PrecomputedText implements android.text.Spanned {
|
||||
method public char charAt(int);
|
||||
method public static android.text.PrecomputedText create(java.lang.CharSequence, android.text.PrecomputedText.Params);
|
||||
method public int getParagraphCount();
|
||||
method public int getParagraphEnd(int);
|
||||
method public int getParagraphStart(int);
|
||||
method public android.text.PrecomputedText.Params getParams();
|
||||
method public int getSpanEnd(java.lang.Object);
|
||||
method public int getSpanFlags(java.lang.Object);
|
||||
method public int getSpanStart(java.lang.Object);
|
||||
method public <T> T[] getSpans(int, int, java.lang.Class<T>);
|
||||
method public java.lang.CharSequence getText();
|
||||
method public int length();
|
||||
method public int nextSpanTransition(int, int, java.lang.Class);
|
||||
method public java.lang.CharSequence subSequence(int, int);
|
||||
}
|
||||
|
||||
public static final class PrecomputedText.Params {
|
||||
method public int getBreakStrategy();
|
||||
method public int getHyphenationFrequency();
|
||||
method public android.text.TextDirectionHeuristic getTextDirection();
|
||||
method public android.text.TextPaint getTextPaint();
|
||||
}
|
||||
|
||||
public static class PrecomputedText.Params.Builder {
|
||||
ctor public PrecomputedText.Params.Builder(android.text.TextPaint);
|
||||
method public android.text.PrecomputedText.Params build();
|
||||
method public android.text.PrecomputedText.Params.Builder setBreakStrategy(int);
|
||||
method public android.text.PrecomputedText.Params.Builder setHyphenationFrequency(int);
|
||||
method public android.text.PrecomputedText.Params.Builder setTextDirection(android.text.TextDirectionHeuristic);
|
||||
}
|
||||
|
||||
public class Selection {
|
||||
method public static boolean extendDown(android.text.Spannable, android.text.Layout);
|
||||
method public static boolean extendLeft(android.text.Spannable, android.text.Layout);
|
||||
@@ -53641,6 +53644,7 @@ package android.widget {
|
||||
method public final android.content.res.ColorStateList getTextColors();
|
||||
method public java.util.Locale getTextLocale();
|
||||
method public android.os.LocaleList getTextLocales();
|
||||
method public android.text.PrecomputedText.Params getTextMetricsParams();
|
||||
method public float getTextScaleX();
|
||||
method public float getTextSize();
|
||||
method public int getTotalPaddingBottom();
|
||||
@@ -53770,6 +53774,7 @@ package android.widget {
|
||||
method public final void setTextKeepState(java.lang.CharSequence, android.widget.TextView.BufferType);
|
||||
method public void setTextLocale(java.util.Locale);
|
||||
method public void setTextLocales(android.os.LocaleList);
|
||||
method public void setTextMetricsParams(android.text.PrecomputedText.Params);
|
||||
method public void setTextScaleX(float);
|
||||
method public void setTextSize(float);
|
||||
method public void setTextSize(int, float);
|
||||
|
||||
@@ -347,8 +347,8 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
|
||||
TextLine line = TextLine.obtain();
|
||||
line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
|
||||
Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
|
||||
if (text instanceof MeasuredText) {
|
||||
MeasuredText mt = (MeasuredText) text;
|
||||
if (text instanceof PrecomputedText) {
|
||||
PrecomputedText mt = (PrecomputedText) text;
|
||||
// Reaching here means there is only one paragraph.
|
||||
MeasuredParagraph mp = mt.getMeasuredParagraph(0);
|
||||
fm.width = (int) Math.ceil(mp.getWidth(0, mp.getTextLength()));
|
||||
|
||||
@@ -1,427 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.text;
|
||||
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.util.IntArray;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A text which has already been measured.
|
||||
*/
|
||||
public class MeasuredText implements Spanned {
|
||||
private static final char LINE_FEED = '\n';
|
||||
|
||||
// The original text.
|
||||
private final @NonNull CharSequence mText;
|
||||
|
||||
// The inclusive start offset of the measuring target.
|
||||
private final @IntRange(from = 0) int mStart;
|
||||
|
||||
// The exclusive end offset of the measuring target.
|
||||
private final @IntRange(from = 0) int mEnd;
|
||||
|
||||
// The TextPaint used for measurement.
|
||||
private final @NonNull TextPaint mPaint;
|
||||
|
||||
// The requested text direction.
|
||||
private final @NonNull TextDirectionHeuristic mTextDir;
|
||||
|
||||
// The measured paragraph texts.
|
||||
private final @NonNull MeasuredParagraph[] mMeasuredParagraphs;
|
||||
|
||||
// The sorted paragraph end offsets.
|
||||
private final @NonNull int[] mParagraphBreakPoints;
|
||||
|
||||
// The break strategy for this measured text.
|
||||
private final @Layout.BreakStrategy int mBreakStrategy;
|
||||
|
||||
// The hyphenation frequency for this measured text.
|
||||
private final @Layout.HyphenationFrequency int mHyphenationFrequency;
|
||||
|
||||
/**
|
||||
* A Builder for MeasuredText
|
||||
*/
|
||||
public static final class Builder {
|
||||
// Mandatory parameters.
|
||||
private final @NonNull CharSequence mText;
|
||||
private final @NonNull TextPaint mPaint;
|
||||
|
||||
// Members to be updated by setters.
|
||||
private @IntRange(from = 0) int mStart;
|
||||
private @IntRange(from = 0) int mEnd;
|
||||
private TextDirectionHeuristic mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
|
||||
private @Layout.BreakStrategy int mBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY;
|
||||
private @Layout.HyphenationFrequency int mHyphenationFrequency =
|
||||
Layout.HYPHENATION_FREQUENCY_NORMAL;
|
||||
|
||||
|
||||
/**
|
||||
* Builder constructor
|
||||
*
|
||||
* @param text The text to be measured.
|
||||
* @param paint The paint to be used for drawing.
|
||||
*/
|
||||
public Builder(@NonNull CharSequence text, @NonNull TextPaint paint) {
|
||||
Preconditions.checkNotNull(text);
|
||||
Preconditions.checkNotNull(paint);
|
||||
|
||||
mText = text;
|
||||
mPaint = paint;
|
||||
mStart = 0;
|
||||
mEnd = text.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the range of measuring target.
|
||||
*
|
||||
* @param start The measuring target start offset in the text.
|
||||
* @param end The measuring target end offset in the text.
|
||||
*/
|
||||
public @NonNull Builder setRange(@IntRange(from = 0) int start,
|
||||
@IntRange(from = 0) int end) {
|
||||
Preconditions.checkArgumentInRange(start, 0, mText.length(), "start");
|
||||
Preconditions.checkArgumentInRange(end, 0, mText.length(), "end");
|
||||
Preconditions.checkArgument(start <= end, "The range is reversed.");
|
||||
|
||||
mStart = start;
|
||||
mEnd = end;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text direction heuristic
|
||||
*
|
||||
* The default value is {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
|
||||
*
|
||||
* @param textDir The text direction heuristic for resolving bidi behavior.
|
||||
* @return this builder, useful for chaining.
|
||||
*/
|
||||
public @NonNull Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
|
||||
Preconditions.checkNotNull(textDir);
|
||||
mTextDir = textDir;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the break strategy
|
||||
*
|
||||
* The default value is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}.
|
||||
*
|
||||
* @param breakStrategy The break strategy.
|
||||
* @return this builder, useful for chaining.
|
||||
*/
|
||||
public @NonNull Builder setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
|
||||
mBreakStrategy = breakStrategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the hyphenation frequency
|
||||
*
|
||||
* The default value is {@link Layout#HYPHENATION_FREQUENCY_NORMAL}.
|
||||
*
|
||||
* @param hyphenationFrequency The hyphenation frequency.
|
||||
* @return this builder, useful for chaining.
|
||||
*/
|
||||
public @NonNull Builder setHyphenationFrequency(
|
||||
@Layout.HyphenationFrequency int hyphenationFrequency) {
|
||||
mHyphenationFrequency = hyphenationFrequency;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the measured text
|
||||
*
|
||||
* @return the measured text.
|
||||
*/
|
||||
public @NonNull MeasuredText build() {
|
||||
return build(true /* build full layout result */);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public @NonNull MeasuredText build(boolean computeLayout) {
|
||||
final boolean needHyphenation = mBreakStrategy != Layout.BREAK_STRATEGY_SIMPLE
|
||||
&& mHyphenationFrequency != Layout.HYPHENATION_FREQUENCY_NONE;
|
||||
|
||||
final IntArray paragraphEnds = new IntArray();
|
||||
final ArrayList<MeasuredParagraph> measuredTexts = new ArrayList<>();
|
||||
|
||||
int paraEnd = 0;
|
||||
for (int paraStart = mStart; paraStart < mEnd; paraStart = paraEnd) {
|
||||
paraEnd = TextUtils.indexOf(mText, LINE_FEED, paraStart, mEnd);
|
||||
if (paraEnd < 0) {
|
||||
// No LINE_FEED(U+000A) character found. Use end of the text as the paragraph
|
||||
// end.
|
||||
paraEnd = mEnd;
|
||||
} else {
|
||||
paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph.
|
||||
}
|
||||
|
||||
paragraphEnds.add(paraEnd);
|
||||
measuredTexts.add(MeasuredParagraph.buildForStaticLayout(
|
||||
mPaint, mText, paraStart, paraEnd, mTextDir, needHyphenation,
|
||||
computeLayout, null /* no recycle */));
|
||||
}
|
||||
|
||||
return new MeasuredText(mText, mStart, mEnd, mPaint, mTextDir, mBreakStrategy,
|
||||
mHyphenationFrequency, measuredTexts.toArray(
|
||||
new MeasuredParagraph[measuredTexts.size()]),
|
||||
paragraphEnds.toArray());
|
||||
}
|
||||
};
|
||||
|
||||
// Use MeasuredText.Builder instead.
|
||||
private MeasuredText(@NonNull CharSequence text,
|
||||
@IntRange(from = 0) int start,
|
||||
@IntRange(from = 0) int end,
|
||||
@NonNull TextPaint paint,
|
||||
@NonNull TextDirectionHeuristic textDir,
|
||||
@Layout.BreakStrategy int breakStrategy,
|
||||
@Layout.HyphenationFrequency int frequency,
|
||||
@NonNull MeasuredParagraph[] measuredTexts,
|
||||
@NonNull int[] paragraphBreakPoints) {
|
||||
mText = text;
|
||||
mStart = start;
|
||||
mEnd = end;
|
||||
// Copy the paint so that we can keep the reference of typeface in native layout result.
|
||||
mPaint = new TextPaint(paint);
|
||||
mMeasuredParagraphs = measuredTexts;
|
||||
mParagraphBreakPoints = paragraphBreakPoints;
|
||||
mTextDir = textDir;
|
||||
mBreakStrategy = breakStrategy;
|
||||
mHyphenationFrequency = frequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying text.
|
||||
*/
|
||||
public @NonNull CharSequence getText() {
|
||||
return mText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the inclusive start offset of measured region.
|
||||
*/
|
||||
public @IntRange(from = 0) int getStart() {
|
||||
return mStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the exclusive end offset of measured region.
|
||||
*/
|
||||
public @IntRange(from = 0) int getEnd() {
|
||||
return mEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text direction associated with char sequence.
|
||||
*/
|
||||
public @NonNull TextDirectionHeuristic getTextDir() {
|
||||
return mTextDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paint used to measure this text.
|
||||
*/
|
||||
public @NonNull TextPaint getPaint() {
|
||||
return mPaint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the paragraph of this text.
|
||||
*/
|
||||
public @IntRange(from = 0) int getParagraphCount() {
|
||||
return mParagraphBreakPoints.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paragraph start offset of the text.
|
||||
*/
|
||||
public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) {
|
||||
Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
|
||||
return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paragraph end offset of the text.
|
||||
*/
|
||||
public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) {
|
||||
Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
|
||||
return mParagraphBreakPoints[paraIndex];
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public @NonNull MeasuredParagraph getMeasuredParagraph(@IntRange(from = 0) int paraIndex) {
|
||||
return mMeasuredParagraphs[paraIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the break strategy for this text.
|
||||
*/
|
||||
public @Layout.BreakStrategy int getBreakStrategy() {
|
||||
return mBreakStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hyphenation frequency for this text.
|
||||
*/
|
||||
public @Layout.HyphenationFrequency int getHyphenationFrequency() {
|
||||
return mHyphenationFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given TextPaint gives the same result of text layout for this text.
|
||||
* @hide
|
||||
*/
|
||||
public boolean canUseMeasuredResult(@NonNull TextPaint paint) {
|
||||
return mPaint.getTextSize() == paint.getTextSize()
|
||||
&& mPaint.getTextSkewX() == paint.getTextSkewX()
|
||||
&& mPaint.getTextScaleX() == paint.getTextScaleX()
|
||||
&& mPaint.getLetterSpacing() == paint.getLetterSpacing()
|
||||
&& mPaint.getWordSpacing() == paint.getWordSpacing()
|
||||
&& mPaint.getFlags() == paint.getFlags() // Maybe not all flag affects text layout.
|
||||
&& mPaint.getTextLocales() == paint.getTextLocales() // need to be equals?
|
||||
&& mPaint.getFontVariationSettings() == paint.getFontVariationSettings()
|
||||
&& mPaint.getTypeface() == paint.getTypeface()
|
||||
&& TextUtils.equals(mPaint.getFontFeatureSettings(), paint.getFontFeatureSettings());
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public int findParaIndex(@IntRange(from = 0) int pos) {
|
||||
// TODO: Maybe good to remove paragraph concept from MeasuredText and add substring layout
|
||||
// support to StaticLayout.
|
||||
for (int i = 0; i < mParagraphBreakPoints.length; ++i) {
|
||||
if (pos < mParagraphBreakPoints[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new IndexOutOfBoundsException(
|
||||
"pos must be less than " + mParagraphBreakPoints[mParagraphBreakPoints.length - 1]
|
||||
+ ", gave " + pos);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public float getWidth(@IntRange(from = 0) int start, @IntRange(from = 0) int end) {
|
||||
final int paraIndex = findParaIndex(start);
|
||||
final int paraStart = getParagraphStart(paraIndex);
|
||||
final int paraEnd = getParagraphEnd(paraIndex);
|
||||
if (start < paraStart || paraEnd < end) {
|
||||
throw new RuntimeException("Cannot measured across the paragraph:"
|
||||
+ "para: (" + paraStart + ", " + paraEnd + "), "
|
||||
+ "request: (" + start + ", " + end + ")");
|
||||
}
|
||||
return getMeasuredParagraph(paraIndex).getWidth(start - paraStart, end - paraStart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of native MeasuredText memory usage
|
||||
*
|
||||
* Note that this may not be aculate. Must be used only for testing purposes.
|
||||
* @hide
|
||||
*/
|
||||
public int getMemoryUsage() {
|
||||
int r = 0;
|
||||
for (int i = 0; i < getParagraphCount(); ++i) {
|
||||
r += getMeasuredParagraph(i).getMemoryUsage();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Spanned overrides
|
||||
//
|
||||
// Just proxy for underlying mText if appropriate.
|
||||
|
||||
@Override
|
||||
public <T> T[] getSpans(int start, int end, Class<T> type) {
|
||||
if (mText instanceof Spanned) {
|
||||
return ((Spanned) mText).getSpans(start, end, type);
|
||||
} else {
|
||||
return ArrayUtils.emptyArray(type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpanStart(Object tag) {
|
||||
if (mText instanceof Spanned) {
|
||||
return ((Spanned) mText).getSpanStart(tag);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpanEnd(Object tag) {
|
||||
if (mText instanceof Spanned) {
|
||||
return ((Spanned) mText).getSpanEnd(tag);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpanFlags(Object tag) {
|
||||
if (mText instanceof Spanned) {
|
||||
return ((Spanned) mText).getSpanFlags(tag);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextSpanTransition(int start, int limit, Class type) {
|
||||
if (mText instanceof Spanned) {
|
||||
return ((Spanned) mText).nextSpanTransition(start, limit, type);
|
||||
} else {
|
||||
return mText.length();
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// CharSequence overrides.
|
||||
//
|
||||
// Just proxy for underlying mText.
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return mText.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int index) {
|
||||
// TODO: Should this be index + mStart ?
|
||||
return mText.charAt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int start, int end) {
|
||||
// TODO: return MeasuredText.
|
||||
// TODO: Should this be index + mStart, end + mStart ?
|
||||
return mText.subSequence(start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mText.toString();
|
||||
}
|
||||
}
|
||||
501
core/java/android/text/PrecomputedText.java
Normal file
501
core/java/android/text/PrecomputedText.java
Normal file
@@ -0,0 +1,501 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.text;
|
||||
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.util.IntArray;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A text which has the character metrics data.
|
||||
*
|
||||
* A text object that contains the character metrics data and can be used to improve the performance
|
||||
* of text layout operations. When a PrecomputedText is created with a given {@link CharSequence},
|
||||
* it will measure the text metrics during the creation. This PrecomputedText instance can be set on
|
||||
* {@link android.widget.TextView} or {@link StaticLayout}. Since the text layout information will
|
||||
* be included in this instance, {@link android.widget.TextView} or {@link StaticLayout} will not
|
||||
* have to recalculate this information.
|
||||
*
|
||||
* Note that the {@link PrecomputedText} created from different parameters of the target {@link
|
||||
* android.widget.TextView} will be rejected internally and compute the text layout again with the
|
||||
* current {@link android.widget.TextView} parameters.
|
||||
*
|
||||
* <pre>
|
||||
* An example usage is:
|
||||
* <code>
|
||||
* void asyncSetText(final TextView textView, final String longString, Handler bgThreadHandler) {
|
||||
* // construct precompute related parameters using the TextView that we will set the text on.
|
||||
* final PrecomputedText.Params params = textView.getTextParams();
|
||||
* bgThreadHandler.post(() -> {
|
||||
* final PrecomputedText precomputedText =
|
||||
* PrecomputedText.create(expensiveLongString, params);
|
||||
* textView.post(() -> {
|
||||
* textView.setText(precomputedText);
|
||||
* });
|
||||
* });
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* Note that the {@link PrecomputedText} created from different parameters of the target
|
||||
* {@link android.widget.TextView} will be rejected internally and compute the text layout again
|
||||
* with the current {@link android.widget.TextView} parameters.
|
||||
*/
|
||||
public class PrecomputedText implements Spanned {
|
||||
private static final char LINE_FEED = '\n';
|
||||
|
||||
/**
|
||||
* The information required for building {@link PrecomputedText}.
|
||||
*
|
||||
* Contains information required for precomputing text measurement metadata, so it can be done
|
||||
* in isolation of a {@link android.widget.TextView} or {@link StaticLayout}, when final layout
|
||||
* constraints are not known.
|
||||
*/
|
||||
public static final class Params {
|
||||
// The TextPaint used for measurement.
|
||||
private final @NonNull TextPaint mPaint;
|
||||
|
||||
// The requested text direction.
|
||||
private final @NonNull TextDirectionHeuristic mTextDir;
|
||||
|
||||
// The break strategy for this measured text.
|
||||
private final @Layout.BreakStrategy int mBreakStrategy;
|
||||
|
||||
// The hyphenation frequency for this measured text.
|
||||
private final @Layout.HyphenationFrequency int mHyphenationFrequency;
|
||||
|
||||
/**
|
||||
* A builder for creating {@link Params}.
|
||||
*/
|
||||
public static class Builder {
|
||||
// The TextPaint used for measurement.
|
||||
private final @NonNull TextPaint mPaint;
|
||||
|
||||
// The requested text direction.
|
||||
private TextDirectionHeuristic mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
|
||||
|
||||
// The break strategy for this measured text.
|
||||
private @Layout.BreakStrategy int mBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY;
|
||||
|
||||
// The hyphenation frequency for this measured text.
|
||||
private @Layout.HyphenationFrequency int mHyphenationFrequency =
|
||||
Layout.HYPHENATION_FREQUENCY_NORMAL;
|
||||
|
||||
/**
|
||||
* Builder constructor.
|
||||
*
|
||||
* @param paint the paint to be used for drawing
|
||||
*/
|
||||
public Builder(@NonNull TextPaint paint) {
|
||||
mPaint = paint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the line break strategy.
|
||||
*
|
||||
* The default value is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}.
|
||||
*
|
||||
* @param strategy the break strategy
|
||||
* @return this builder, useful for chaining
|
||||
* @see StaticLayout.Builder#setBreakStrategy
|
||||
* @see android.widget.TextView#setBreakStrategy
|
||||
*/
|
||||
public Builder setBreakStrategy(@Layout.BreakStrategy int strategy) {
|
||||
mBreakStrategy = strategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the hyphenation frequency.
|
||||
*
|
||||
* The default value is {@link Layout#HYPHENATION_FREQUENCY_NORMAL}.
|
||||
*
|
||||
* @param frequency the hyphenation frequency
|
||||
* @return this builder, useful for chaining
|
||||
* @see StaticLayout.Builder#setHyphenationFrequency
|
||||
* @see android.widget.TextView#setHyphenationFrequency
|
||||
*/
|
||||
public Builder setHyphenationFrequency(@Layout.HyphenationFrequency int frequency) {
|
||||
mHyphenationFrequency = frequency;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text direction heuristic.
|
||||
*
|
||||
* The default value is {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
|
||||
*
|
||||
* @param textDir the text direction heuristic for resolving bidi behavior
|
||||
* @return this builder, useful for chaining
|
||||
* @see StaticLayout.Builder#setTextDirection
|
||||
*/
|
||||
public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
|
||||
mTextDir = textDir;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the {@link Params}.
|
||||
*
|
||||
* @return the layout parameter
|
||||
*/
|
||||
public @NonNull Params build() {
|
||||
return new Params(mPaint, mTextDir, mBreakStrategy, mHyphenationFrequency);
|
||||
}
|
||||
}
|
||||
|
||||
// This is public hidden for internal use.
|
||||
// For the external developers, use Builder instead.
|
||||
/** @hide */
|
||||
public Params(@NonNull TextPaint paint, @NonNull TextDirectionHeuristic textDir,
|
||||
@Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) {
|
||||
mPaint = paint;
|
||||
mTextDir = textDir;
|
||||
mBreakStrategy = strategy;
|
||||
mHyphenationFrequency = frequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link TextPaint} for this text.
|
||||
*
|
||||
* @return A {@link TextPaint}
|
||||
*/
|
||||
public @NonNull TextPaint getTextPaint() {
|
||||
return mPaint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link TextDirectionHeuristic} for this text.
|
||||
*
|
||||
* @return A {@link TextDirectionHeuristic}
|
||||
*/
|
||||
public @NonNull TextDirectionHeuristic getTextDirection() {
|
||||
return mTextDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the break strategy for this text.
|
||||
*
|
||||
* @return A line break strategy
|
||||
*/
|
||||
public @Layout.BreakStrategy int getBreakStrategy() {
|
||||
return mBreakStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hyphenation frequency for this text.
|
||||
*
|
||||
* @return A hyphenation frequency
|
||||
*/
|
||||
public @Layout.HyphenationFrequency int getHyphenationFrequency() {
|
||||
return mHyphenationFrequency;
|
||||
}
|
||||
|
||||
private boolean isSameTextMetricsInternal(@NonNull TextPaint paint,
|
||||
@NonNull TextDirectionHeuristic textDir, @Layout.BreakStrategy int strategy,
|
||||
@Layout.HyphenationFrequency int frequency) {
|
||||
return mTextDir == textDir
|
||||
&& mBreakStrategy == strategy
|
||||
&& mHyphenationFrequency == frequency
|
||||
&& mPaint.equalsForTextMeasurement(paint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the same text layout.
|
||||
*
|
||||
* @return true if this and the given param result in the same text layout
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || !(o instanceof Params)) {
|
||||
return false;
|
||||
}
|
||||
Params param = (Params) o;
|
||||
return isSameTextMetricsInternal(param.mPaint, param.mTextDir, param.mBreakStrategy,
|
||||
param.mHyphenationFrequency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// TODO: implement MinikinPaint::hashCode and use it to keep consistency with equals.
|
||||
return Objects.hash(mPaint.getTextSize(), mPaint.getTextScaleX(), mPaint.getTextSkewX(),
|
||||
mPaint.getLetterSpacing(), mPaint.getWordSpacing(), mPaint.getFlags(),
|
||||
mPaint.getTextLocales(), mPaint.getTypeface(),
|
||||
mPaint.getFontVariationSettings(), mPaint.isElegantTextHeight(), mTextDir,
|
||||
mBreakStrategy, mHyphenationFrequency);
|
||||
}
|
||||
};
|
||||
|
||||
// The original text.
|
||||
private final @NonNull SpannedString mText;
|
||||
|
||||
// The inclusive start offset of the measuring target.
|
||||
private final @IntRange(from = 0) int mStart;
|
||||
|
||||
// The exclusive end offset of the measuring target.
|
||||
private final @IntRange(from = 0) int mEnd;
|
||||
|
||||
private final @NonNull Params mParams;
|
||||
|
||||
// The measured paragraph texts.
|
||||
private final @NonNull MeasuredParagraph[] mMeasuredParagraphs;
|
||||
|
||||
// The sorted paragraph end offsets.
|
||||
private final @NonNull int[] mParagraphBreakPoints;
|
||||
|
||||
/**
|
||||
* Create a new {@link PrecomputedText} which will pre-compute text measurement and glyph
|
||||
* positioning information.
|
||||
* <p>
|
||||
* This can be expensive, so computing this on a background thread before your text will be
|
||||
* presented can save work on the UI thread.
|
||||
* </p>
|
||||
*
|
||||
* @param text the text to be measured
|
||||
* @param param parameters that define how text will be precomputed
|
||||
* @return A {@link PrecomputedText}
|
||||
*/
|
||||
public static PrecomputedText create(@NonNull CharSequence text, @NonNull Params param) {
|
||||
return createInternal(text, param, 0, text.length(), true /* compute full Layout */);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public static PrecomputedText createWidthOnly(@NonNull CharSequence text, @NonNull Params param,
|
||||
@IntRange(from = 0) int start, @IntRange(from = 0) int end) {
|
||||
return createInternal(text, param, start, end, false /* compute width only */);
|
||||
}
|
||||
|
||||
private static PrecomputedText createInternal(@NonNull CharSequence text, @NonNull Params param,
|
||||
@IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean computeLayout) {
|
||||
Preconditions.checkNotNull(text);
|
||||
Preconditions.checkNotNull(param);
|
||||
final boolean needHyphenation = param.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
|
||||
&& param.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE;
|
||||
|
||||
final IntArray paragraphEnds = new IntArray();
|
||||
final ArrayList<MeasuredParagraph> measuredTexts = new ArrayList<>();
|
||||
|
||||
int paraEnd = 0;
|
||||
for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
|
||||
paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
|
||||
if (paraEnd < 0) {
|
||||
// No LINE_FEED(U+000A) character found. Use end of the text as the paragraph
|
||||
// end.
|
||||
paraEnd = end;
|
||||
} else {
|
||||
paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph.
|
||||
}
|
||||
|
||||
paragraphEnds.add(paraEnd);
|
||||
measuredTexts.add(MeasuredParagraph.buildForStaticLayout(
|
||||
param.getTextPaint(), text, paraStart, paraEnd, param.getTextDirection(),
|
||||
needHyphenation, computeLayout, null /* no recycle */));
|
||||
}
|
||||
|
||||
return new PrecomputedText(text, start, end, param,
|
||||
measuredTexts.toArray(new MeasuredParagraph[measuredTexts.size()]),
|
||||
paragraphEnds.toArray());
|
||||
}
|
||||
|
||||
// Use PrecomputedText.create instead.
|
||||
private PrecomputedText(@NonNull CharSequence text, @IntRange(from = 0) int start,
|
||||
@IntRange(from = 0) int end, @NonNull Params param,
|
||||
@NonNull MeasuredParagraph[] measuredTexts, @NonNull int[] paragraphBreakPoints) {
|
||||
mText = new SpannedString(text);
|
||||
mStart = start;
|
||||
mEnd = end;
|
||||
mParams = param;
|
||||
mMeasuredParagraphs = measuredTexts;
|
||||
mParagraphBreakPoints = paragraphBreakPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying text.
|
||||
*/
|
||||
public @NonNull CharSequence getText() {
|
||||
return mText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the inclusive start offset of measured region.
|
||||
* @hide
|
||||
*/
|
||||
public @IntRange(from = 0) int getStart() {
|
||||
return mStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the exclusive end offset of measured region.
|
||||
* @hide
|
||||
*/
|
||||
public @IntRange(from = 0) int getEnd() {
|
||||
return mEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the layout parameters used to measure this text.
|
||||
*/
|
||||
public @NonNull Params getParams() {
|
||||
return mParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the count of paragraphs.
|
||||
*/
|
||||
public @IntRange(from = 0) int getParagraphCount() {
|
||||
return mParagraphBreakPoints.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paragraph start offset of the text.
|
||||
*/
|
||||
public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) {
|
||||
Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
|
||||
return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paragraph end offset of the text.
|
||||
*/
|
||||
public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) {
|
||||
Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
|
||||
return mParagraphBreakPoints[paraIndex];
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public @NonNull MeasuredParagraph getMeasuredParagraph(@IntRange(from = 0) int paraIndex) {
|
||||
return mMeasuredParagraphs[paraIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given TextPaint gives the same result of text layout for this text.
|
||||
* @hide
|
||||
*/
|
||||
public boolean canUseMeasuredResult(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
|
||||
@NonNull TextDirectionHeuristic textDir, @NonNull TextPaint paint,
|
||||
@Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) {
|
||||
final TextPaint mtPaint = mParams.getTextPaint();
|
||||
return mStart == start
|
||||
&& mEnd == end
|
||||
&& mParams.isSameTextMetricsInternal(paint, textDir, strategy, frequency);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public int findParaIndex(@IntRange(from = 0) int pos) {
|
||||
// TODO: Maybe good to remove paragraph concept from PrecomputedText and add substring
|
||||
// layout support to StaticLayout.
|
||||
for (int i = 0; i < mParagraphBreakPoints.length; ++i) {
|
||||
if (pos < mParagraphBreakPoints[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new IndexOutOfBoundsException(
|
||||
"pos must be less than " + mParagraphBreakPoints[mParagraphBreakPoints.length - 1]
|
||||
+ ", gave " + pos);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public float getWidth(@IntRange(from = 0) int start, @IntRange(from = 0) int end) {
|
||||
final int paraIndex = findParaIndex(start);
|
||||
final int paraStart = getParagraphStart(paraIndex);
|
||||
final int paraEnd = getParagraphEnd(paraIndex);
|
||||
if (start < paraStart || paraEnd < end) {
|
||||
throw new RuntimeException("Cannot measured across the paragraph:"
|
||||
+ "para: (" + paraStart + ", " + paraEnd + "), "
|
||||
+ "request: (" + start + ", " + end + ")");
|
||||
}
|
||||
return getMeasuredParagraph(paraIndex).getWidth(start - paraStart, end - paraStart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of native PrecomputedText memory usage.
|
||||
*
|
||||
* Note that this is not guaranteed to be accurate. Must be used only for testing purposes.
|
||||
* @hide
|
||||
*/
|
||||
public int getMemoryUsage() {
|
||||
int r = 0;
|
||||
for (int i = 0; i < getParagraphCount(); ++i) {
|
||||
r += getMeasuredParagraph(i).getMemoryUsage();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Spanned overrides
|
||||
//
|
||||
// Just proxy for underlying mText if appropriate.
|
||||
|
||||
@Override
|
||||
public <T> T[] getSpans(int start, int end, Class<T> type) {
|
||||
return mText.getSpans(start, end, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpanStart(Object tag) {
|
||||
return mText.getSpanStart(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpanEnd(Object tag) {
|
||||
return mText.getSpanEnd(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpanFlags(Object tag) {
|
||||
return mText.getSpanFlags(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextSpanTransition(int start, int limit, Class type) {
|
||||
return mText.nextSpanTransition(start, limit, type);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// CharSequence overrides.
|
||||
//
|
||||
// Just proxy for underlying mText.
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return mText.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int index) {
|
||||
return mText.charAt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int start, int end) {
|
||||
return PrecomputedText.create(mText.subSequence(start, end), mParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mText.toString();
|
||||
}
|
||||
}
|
||||
@@ -651,42 +651,25 @@ public class StaticLayout extends Layout {
|
||||
b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
|
||||
indents, mLeftPaddings, mRightPaddings);
|
||||
|
||||
MeasuredText measured = null;
|
||||
PrecomputedText measured = null;
|
||||
final Spanned spanned;
|
||||
final boolean canUseMeasuredText;
|
||||
if (source instanceof MeasuredText) {
|
||||
measured = (MeasuredText) source;
|
||||
|
||||
if (bufStart != measured.getStart() || bufEnd != measured.getEnd()) {
|
||||
// The buffer position has changed. Re-measure here.
|
||||
canUseMeasuredText = false;
|
||||
} else if (b.mBreakStrategy != measured.getBreakStrategy()
|
||||
|| b.mHyphenationFrequency != measured.getHyphenationFrequency()) {
|
||||
// The computed hyphenation pieces may not be able to used. Re-measure it.
|
||||
canUseMeasuredText = false;
|
||||
} else {
|
||||
// We can use measured information.
|
||||
canUseMeasuredText = true;
|
||||
if (source instanceof PrecomputedText) {
|
||||
measured = (PrecomputedText) source;
|
||||
if (!measured.canUseMeasuredResult(bufStart, bufEnd, textDir, paint, b.mBreakStrategy,
|
||||
b.mHyphenationFrequency)) {
|
||||
// Some parameters are different from the ones when measured text is created.
|
||||
measured = null;
|
||||
}
|
||||
} else {
|
||||
canUseMeasuredText = false;
|
||||
}
|
||||
|
||||
if (!canUseMeasuredText) {
|
||||
measured = new MeasuredText.Builder(source, paint)
|
||||
.setRange(bufStart, bufEnd)
|
||||
.setTextDirection(textDir)
|
||||
.setBreakStrategy(b.mBreakStrategy)
|
||||
.setHyphenationFrequency(b.mHyphenationFrequency)
|
||||
.build(false /* full layout is not necessary for line breaking */);
|
||||
if (measured == null) {
|
||||
final PrecomputedText.Params param = new PrecomputedText.Params(paint, textDir,
|
||||
b.mBreakStrategy, b.mHyphenationFrequency);
|
||||
measured = PrecomputedText.createWidthOnly(source, param, bufStart, bufEnd);
|
||||
spanned = (source instanceof Spanned) ? (Spanned) source : null;
|
||||
} else {
|
||||
final CharSequence original = measured.getText();
|
||||
spanned = (original instanceof Spanned) ? (Spanned) original : null;
|
||||
// Overwrite with the one when measured.
|
||||
// TODO: Give an option for developer not to overwrite and measure again here?
|
||||
textDir = measured.getTextDir();
|
||||
paint = measured.getPaint();
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -60,7 +60,7 @@ public class TextLine {
|
||||
private char[] mChars;
|
||||
private boolean mCharsValid;
|
||||
private Spanned mSpanned;
|
||||
private MeasuredText mMeasured;
|
||||
private PrecomputedText mComputed;
|
||||
|
||||
// Additional width of whitespace for justification. This value is per whitespace, thus
|
||||
// the line width will increase by mAddedWidth x (number of stretchable whitespaces).
|
||||
@@ -119,7 +119,7 @@ public class TextLine {
|
||||
tl.mSpanned = null;
|
||||
tl.mTabs = null;
|
||||
tl.mChars = null;
|
||||
tl.mMeasured = null;
|
||||
tl.mComputed = null;
|
||||
|
||||
tl.mMetricAffectingSpanSpanSet.recycle();
|
||||
tl.mCharacterStyleSpanSet.recycle();
|
||||
@@ -170,12 +170,9 @@ public class TextLine {
|
||||
hasReplacement = mReplacementSpanSpanSet.numberOfSpans > 0;
|
||||
}
|
||||
|
||||
mMeasured = null;
|
||||
if (text instanceof MeasuredText) {
|
||||
MeasuredText mt = (MeasuredText) text;
|
||||
if (mt.canUseMeasuredResult(paint)) {
|
||||
mMeasured = mt;
|
||||
}
|
||||
mComputed = null;
|
||||
if (text instanceof PrecomputedText) {
|
||||
mComputed = (PrecomputedText) text;
|
||||
}
|
||||
|
||||
mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
|
||||
@@ -746,12 +743,12 @@ public class TextLine {
|
||||
return wp.getRunAdvance(mChars, start, end, contextStart, contextEnd, runIsRtl, offset);
|
||||
} else {
|
||||
final int delta = mStart;
|
||||
if (mMeasured == null) {
|
||||
if (mComputed == null) {
|
||||
// TODO: Enable measured getRunAdvance for ReplacementSpan and RTL text.
|
||||
return wp.getRunAdvance(mText, delta + start, delta + end,
|
||||
delta + contextStart, delta + contextEnd, runIsRtl, delta + offset);
|
||||
} else {
|
||||
return mMeasured.getWidth(start + delta, end + delta);
|
||||
return mComputed.getWidth(start + delta, end + delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.TemporaryBuffer;
|
||||
import android.text.GraphicsOperations;
|
||||
import android.text.MeasuredText;
|
||||
import android.text.PrecomputedText;
|
||||
import android.text.SpannableString;
|
||||
import android.text.SpannedString;
|
||||
import android.text.TextUtils;
|
||||
@@ -507,8 +507,8 @@ public class RecordingCanvas extends Canvas {
|
||||
TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
|
||||
long measuredTextPtr = 0;
|
||||
int measuredTextOffset = 0;
|
||||
if (text instanceof MeasuredText) {
|
||||
MeasuredText mt = (MeasuredText) text;
|
||||
if (text instanceof PrecomputedText) {
|
||||
PrecomputedText mt = (PrecomputedText) text;
|
||||
int paraIndex = mt.findParaIndex(start);
|
||||
if (end <= mt.getParagraphEnd(paraIndex)) {
|
||||
// Only support if the target is in the same paragraph.
|
||||
@@ -641,7 +641,7 @@ public class RecordingCanvas extends Canvas {
|
||||
@FastNative
|
||||
private static native void nDrawTextRun(long nativeCanvas, char[] text, int start, int count,
|
||||
int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint,
|
||||
long nativeMeasuredText, int measuredTextOffset);
|
||||
long nativePrecomputedText, int measuredTextOffset);
|
||||
|
||||
@FastNative
|
||||
private static native void nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count,
|
||||
|
||||
@@ -80,8 +80,8 @@ import android.text.GraphicsOperations;
|
||||
import android.text.InputFilter;
|
||||
import android.text.InputType;
|
||||
import android.text.Layout;
|
||||
import android.text.MeasuredText;
|
||||
import android.text.ParcelableSpan;
|
||||
import android.text.PrecomputedText;
|
||||
import android.text.Selection;
|
||||
import android.text.SpanWatcher;
|
||||
import android.text.Spannable;
|
||||
@@ -4091,6 +4091,35 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
return mHyphenationFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}.
|
||||
*
|
||||
* @return a current {@link PrecomputedText.Params}
|
||||
* @see PrecomputedText
|
||||
*/
|
||||
public @NonNull PrecomputedText.Params getTextMetricsParams() {
|
||||
return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(),
|
||||
mBreakStrategy, mHyphenationFrequency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the text layout parameter.
|
||||
*
|
||||
* Update the TextView parameters to be compatible with {@link PrecomputedText.Params}.
|
||||
* @see PrecomputedText
|
||||
*/
|
||||
public void setTextMetricsParams(@NonNull PrecomputedText.Params params) {
|
||||
mTextPaint.set(params.getTextPaint());
|
||||
mTextDir = params.getTextDirection();
|
||||
mBreakStrategy = params.getBreakStrategy();
|
||||
mHyphenationFrequency = params.getHyphenationFrequency();
|
||||
if (mLayout != null) {
|
||||
nullLayouts();
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the
|
||||
* last line is too short for justification, the last line will be displayed with the
|
||||
@@ -5584,7 +5613,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
if (imm != null) imm.restartInput(this);
|
||||
} else if (type == BufferType.SPANNABLE || mMovement != null) {
|
||||
text = mSpannableFactory.newSpannable(text);
|
||||
} else if (!(text instanceof MeasuredText || text instanceof CharWrapper)) {
|
||||
} else if (!(text instanceof PrecomputedText || text instanceof CharWrapper)) {
|
||||
text = TextUtils.stringOrSpannedString(text);
|
||||
}
|
||||
|
||||
@@ -11712,6 +11741,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current {@link TextDirectionHeuristic}.
|
||||
*
|
||||
* @return the current {@link TextDirectionHeuristic}.
|
||||
* @hide
|
||||
*/
|
||||
protected TextDirectionHeuristic getTextDirectionHeuristic() {
|
||||
|
||||
@@ -1008,6 +1008,23 @@ namespace PaintGlue {
|
||||
return paint->getLooper() && paint->getLooper()->asABlurShadow(nullptr);
|
||||
}
|
||||
|
||||
static jboolean equalsForTextMeasurement(jlong lPaint, jlong rPaint) {
|
||||
if (lPaint == rPaint) {
|
||||
return true;
|
||||
}
|
||||
Paint* leftPaint = reinterpret_cast<Paint*>(lPaint);
|
||||
Paint* rightPaint = reinterpret_cast<Paint*>(rPaint);
|
||||
|
||||
const Typeface* leftTypeface = Typeface::resolveDefault(leftPaint->getAndroidTypeface());
|
||||
const Typeface* rightTypeface = Typeface::resolveDefault(rightPaint->getAndroidTypeface());
|
||||
minikin::MinikinPaint leftMinikinPaint
|
||||
= MinikinUtils::prepareMinikinPaint(leftPaint, leftTypeface);
|
||||
minikin::MinikinPaint rightMinikinPaint
|
||||
= MinikinUtils::prepareMinikinPaint(rightPaint, rightTypeface);
|
||||
|
||||
return leftMinikinPaint == rightMinikinPaint;
|
||||
}
|
||||
|
||||
}; // namespace PaintGlue
|
||||
|
||||
static const JNINativeMethod methods[] = {
|
||||
@@ -1107,7 +1124,8 @@ static const JNINativeMethod methods[] = {
|
||||
{"nGetStrikeThruPosition","(J)F", (void*) PaintGlue::getStrikeThruPosition},
|
||||
{"nGetStrikeThruThickness","(J)F", (void*) PaintGlue::getStrikeThruThickness},
|
||||
{"nSetShadowLayer", "(JFFFI)V", (void*)PaintGlue::setShadowLayer},
|
||||
{"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer}
|
||||
{"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer},
|
||||
{"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement},
|
||||
};
|
||||
|
||||
int register_android_graphics_Paint(JNIEnv* env) {
|
||||
|
||||
@@ -22,7 +22,7 @@ import android.annotation.Nullable;
|
||||
import android.annotation.Size;
|
||||
import android.graphics.Canvas.VertexMode;
|
||||
import android.text.GraphicsOperations;
|
||||
import android.text.MeasuredText;
|
||||
import android.text.PrecomputedText;
|
||||
import android.text.SpannableString;
|
||||
import android.text.SpannedString;
|
||||
import android.text.TextUtils;
|
||||
@@ -487,8 +487,8 @@ public abstract class BaseCanvas {
|
||||
TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
|
||||
long measuredTextPtr = 0;
|
||||
int measuredTextOffset = 0;
|
||||
if (text instanceof MeasuredText) {
|
||||
MeasuredText mt = (MeasuredText) text;
|
||||
if (text instanceof PrecomputedText) {
|
||||
PrecomputedText mt = (PrecomputedText) text;
|
||||
int paraIndex = mt.findParaIndex(start);
|
||||
if (end <= mt.getParagraphEnd(paraIndex)) {
|
||||
// Only suppor the same paragraph.
|
||||
@@ -647,7 +647,7 @@ public abstract class BaseCanvas {
|
||||
|
||||
private static native void nDrawTextRun(long nativeCanvas, char[] text, int start, int count,
|
||||
int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint,
|
||||
long nativeMeasuredText, int measuredTextOffset);
|
||||
long nativePrecomputedText, int measuredTextOffset);
|
||||
|
||||
private static native void nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count,
|
||||
long nativePath, float hOffset, float vOffset, int bidiFlags, long nativePaint);
|
||||
|
||||
@@ -2835,6 +2835,16 @@ public class Paint {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true of the passed {@link Paint} will have the same effect on text measurement
|
||||
*
|
||||
* @param other A {@link Paint} object.
|
||||
* @return true if the other {@link Paint} has the same effect on text measurement.
|
||||
*/
|
||||
public boolean equalsForTextMeasurement(@NonNull Paint other) {
|
||||
return nEqualsForTextMeasurement(mNativePaint, other.mNativePaint);
|
||||
}
|
||||
|
||||
// regular JNI
|
||||
private static native long nGetNativeFinalizer();
|
||||
private static native long nInit();
|
||||
@@ -3002,4 +3012,6 @@ public class Paint {
|
||||
private static native float nGetStrikeThruThickness(long paintPtr);
|
||||
@CriticalNative
|
||||
private static native void nSetTextSize(long paintPtr, float textSize);
|
||||
@CriticalNative
|
||||
private static native boolean nEqualsForTextMeasurement(long leftPaintPtr, long rightPaintPtr);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user