From beafa1f9d2845ee9b5ca352087de03ed0afe7db7 Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Thu, 1 Feb 2018 21:39:24 -0800 Subject: [PATCH] Reorganize MeasuredText API (2nd) This is 2nd attempt of I7db9e2ca4db68a16648cfb8fcf63555f501304c2 This CL changes the MeasuredText API: - Rename MeasuredText to PrecomputedText. - Introduce PrecomputedText.Param which holds all text layout parameters. - Add API to get PrecomputedText.Param from TextView. - Remove MeasuredText.Builder and add PrecomputedText.create method instead. - Remove setRange from MeasuredText since it is not for normal use case. (It can not be used for TextView) Bug: 67504091 Bug: 72861572 Test: bit FrameworksCoreTests:android.text. Test: atest CtsWidgetTestCases:EditTextTest \ CtsWidgetTestCases:TextViewFadingEdgeTest \ FrameworksCoreTests:TextViewFallbackLineSpacingTest \ FrameworksCoreTests:TextViewTest FrameworksCoreTests:TypefaceTest \ CtsGraphicsTestCases:TypefaceTest CtsWidgetTestCases:TextViewTest \ CtsTextTestCases Change-Id: Ie73bce52c6c673cda58973ddad04627a7cf2e5e9 --- ...va => PrecomputedTextMemoryUsageTest.java} | 56 +- ...Test.java => PrecomputedTextPerfTest.java} | 84 +-- .../android/text/StaticLayoutPerfTest.java | 86 +-- api/current.txt | 65 +-- core/java/android/text/BoringLayout.java | 4 +- core/java/android/text/MeasuredText.java | 427 --------------- core/java/android/text/PrecomputedText.java | 501 ++++++++++++++++++ core/java/android/text/StaticLayout.java | 39 +- core/java/android/text/TextLine.java | 17 +- core/java/android/view/RecordingCanvas.java | 8 +- core/java/android/widget/TextView.java | 36 +- core/jni/android/graphics/Paint.cpp | 20 +- .../java/android/graphics/BaseCanvas.java | 8 +- graphics/java/android/graphics/Paint.java | 12 + 14 files changed, 757 insertions(+), 606 deletions(-) rename apct-tests/perftests/core/src/android/text/{MeasuredTextMemoryUsageTest.java => PrecomputedTextMemoryUsageTest.java} (73%) rename apct-tests/perftests/core/src/android/text/{MeasuredTextPerfTest.java => PrecomputedTextPerfTest.java} (66%) delete mode 100644 core/java/android/text/MeasuredText.java create mode 100644 core/java/android/text/PrecomputedText.java diff --git a/apct-tests/perftests/core/src/android/text/MeasuredTextMemoryUsageTest.java b/apct-tests/perftests/core/src/android/text/PrecomputedTextMemoryUsageTest.java similarity index 73% rename from apct-tests/perftests/core/src/android/text/MeasuredTextMemoryUsageTest.java rename to apct-tests/perftests/core/src/android/text/PrecomputedTextMemoryUsageTest.java index fc6302ea93942..73e17242ae785 100644 --- a/apct-tests/perftests/core/src/android/text/MeasuredTextMemoryUsageTest.java +++ b/apct-tests/perftests/core/src/android/text/PrecomputedTextMemoryUsageTest.java @@ -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"); } diff --git a/apct-tests/perftests/core/src/android/text/MeasuredTextPerfTest.java b/apct-tests/perftests/core/src/android/text/PrecomputedTextPerfTest.java similarity index 66% rename from apct-tests/perftests/core/src/android/text/MeasuredTextPerfTest.java rename to apct-tests/perftests/core/src/android/text/PrecomputedTextPerfTest.java index 98f2bd5e57365..1cd0ae13069bb 100644 --- a/apct-tests/perftests/core/src/android/text/MeasuredTextPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/PrecomputedTextPerfTest.java @@ -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); } } } diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java index 231aaf2ca0749..e1a38a0956d70 100644 --- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java @@ -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); diff --git a/api/current.txt b/api/current.txt index a70363d89c1d7..5f48f960fea62 100644 --- a/api/current.txt +++ b/api/current.txt @@ -13839,6 +13839,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(); @@ -43241,36 +43242,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[] getSpans(int, int, java.lang.Class); - 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 { } @@ -43282,6 +43253,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[] getSpans(int, int, java.lang.Class); + 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); @@ -53665,6 +53668,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(); @@ -53794,6 +53798,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); diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java index 6fa5312be5cce..ae59bfaf896f1 100644 --- a/core/java/android/text/BoringLayout.java +++ b/core/java/android/text/BoringLayout.java @@ -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())); diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java deleted file mode 100644 index bb7a9e0b7906f..0000000000000 --- a/core/java/android/text/MeasuredText.java +++ /dev/null @@ -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 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[] getSpans(int start, int end, Class 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(); - } -} diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java new file mode 100644 index 0000000000000..c211e2dba3c0c --- /dev/null +++ b/core/java/android/text/PrecomputedText.java @@ -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. + * + *
+ * An example usage is:
+ * 
+ *  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);
+ *          });
+ *      });
+ *  }
+ * 
+ * 
+ * + * 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. + *

+ * This can be expensive, so computing this on a background thread before your text will be + * presented can save work on the UI thread. + *

+ * + * @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 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[] getSpans(int start, int end, Class 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(); + } +} diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index e62f4216f33ac..299bde239fcfe 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -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 { diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 55367dcce47e2..117a77d329979 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -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); } } } diff --git a/core/java/android/view/RecordingCanvas.java b/core/java/android/view/RecordingCanvas.java index fbb862be54ef5..fc7d828de12ef 100644 --- a/core/java/android/view/RecordingCanvas.java +++ b/core/java/android/view/RecordingCanvas.java @@ -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, diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 1e02c3062d97c..8aae0d865cd23 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -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() { diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 482d028a67f35..2c05d0b976fc8 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -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(lPaint); + Paint* rightPaint = reinterpret_cast(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) { diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index eacb727099ed6..07df0454362cb 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -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); diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index ed147e9e2ec7b..42dac38affba1 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -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); }