Make Layout#getRangeForRect public

To avoid making WordIterator public, WordSegmentFinder has a public
constructor taking a Locale parameter. The constructor taking a
WordIterator parameter is kept but hidden so TextView can use it.

LayoutGetRangeForRectTest is moved to CTS.

Bug: 243969224
Test: atest android.text.cts.LayoutGetRangeForRectTest
Test: atest android.widget.cts.TextViewHandwritingGestureTest
Change-Id: Ibb829af0324c47e4d275b0ccd69d0f95601e3c08
This commit is contained in:
Justin Ghan
2022-09-30 18:27:31 -07:00
parent 3cc4fa15eb
commit 2ea18b462b
6 changed files with 63 additions and 613 deletions

View File

@@ -45192,6 +45192,14 @@ package android.text {
method public void getChars(int, int, char[], int);
}
public class GraphemeClusterSegmentFinder extends android.text.SegmentFinder {
ctor public GraphemeClusterSegmentFinder(@NonNull CharSequence, @NonNull android.text.TextPaint);
method public int nextEndBoundary(@IntRange(from=0) int);
method public int nextStartBoundary(@IntRange(from=0) int);
method public int previousEndBoundary(@IntRange(from=0) int);
method public int previousStartBoundary(@IntRange(from=0) int);
}
public class Html {
method public static String escapeHtml(CharSequence);
method @Deprecated public static android.text.Spanned fromHtml(String);
@@ -45321,6 +45329,7 @@ package android.text {
method public final int getParagraphLeft(int);
method public final int getParagraphRight(int);
method public float getPrimaryHorizontal(int);
method @Nullable public int[] getRangeForRect(@NonNull android.graphics.RectF, @NonNull android.text.SegmentFinder, @NonNull android.text.Layout.TextInclusionStrategy);
method public float getSecondaryHorizontal(int);
method public void getSelectionPath(int, int, android.graphics.Path);
method public final float getSpacingAdd();
@@ -45344,6 +45353,9 @@ package android.text {
field public static final int HYPHENATION_FREQUENCY_NONE = 0; // 0x0
field public static final int HYPHENATION_FREQUENCY_NORMAL = 1; // 0x1
field public static final int HYPHENATION_FREQUENCY_NORMAL_FAST = 3; // 0x3
field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_ANY_OVERLAP;
field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_ALL;
field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_CENTER;
field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
}
@@ -45357,6 +45369,10 @@ package android.text {
public static class Layout.Directions {
}
@java.lang.FunctionalInterface public static interface Layout.TextInclusionStrategy {
method public boolean isSegmentInside(@NonNull android.graphics.RectF, @NonNull android.graphics.RectF);
}
@Deprecated public abstract class LoginFilter implements android.text.InputFilter {
method @Deprecated public CharSequence filter(CharSequence, int, int, android.text.Spanned, int, int);
method @Deprecated public abstract boolean isAllowed(char);
@@ -45433,6 +45449,15 @@ package android.text {
method public android.text.PrecomputedText.Params.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic);
}
public abstract class SegmentFinder {
ctor public SegmentFinder();
method public abstract int nextEndBoundary(@IntRange(from=0) int);
method public abstract int nextStartBoundary(@IntRange(from=0) int);
method public abstract int previousEndBoundary(@IntRange(from=0) int);
method public abstract int previousStartBoundary(@IntRange(from=0) int);
field public static final int DONE = -1; // 0xffffffff
}
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);
@@ -45718,6 +45743,14 @@ package android.text {
method public void onTextChanged(CharSequence, int, int, int);
}
public class WordSegmentFinder extends android.text.SegmentFinder {
ctor public WordSegmentFinder(@NonNull CharSequence, @NonNull java.util.Locale);
method public int nextEndBoundary(@IntRange(from=0) int);
method public int nextStartBoundary(@IntRange(from=0) int);
method public int previousEndBoundary(@IntRange(from=0) int);
method public int previousStartBoundary(@IntRange(from=0) int);
}
}
package android.text.format {

View File

@@ -24,7 +24,11 @@ import android.graphics.Paint;
* Implementation of {@code SegmentFinder} using grapheme clusters as the text segment. Whitespace
* characters are included as segments.
*
* @hide
* <p>For example, the text "a pot" would be divided into five text segments: "a", " ", "p", "o",
* "t".
*
* @see <a href="https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries">Unicode Text
* Segmentation - Grapheme Cluster Boundaries</a>
*/
public class GraphemeClusterSegmentFinder extends SegmentFinder {
private final CharSequence mText;

View File

@@ -170,8 +170,6 @@ public abstract class Layout {
/**
* Strategy which considers a text segment to be inside a rectangle area if the segment bounds
* intersect the rectangle.
*
* @hide
*/
@NonNull
public static final TextInclusionStrategy INCLUSION_STRATEGY_ANY_OVERLAP =
@@ -180,8 +178,6 @@ public abstract class Layout {
/**
* Strategy which considers a text segment to be inside a rectangle area if the center of the
* segment bounds is inside the rectangle.
*
* @hide
*/
@NonNull
public static final TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_CENTER =
@@ -191,8 +187,6 @@ public abstract class Layout {
/**
* Strategy which considers a text segment to be inside a rectangle area if the segment bounds
* are completely contained within the rectangle.
*
* @hide
*/
@NonNull
public static final TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_ALL =
@@ -1867,7 +1861,6 @@ public abstract class Layout {
* specified area
* @return int array of size 2 containing the start (inclusive) and end (exclusive) character
* offsets of the range, or null if there are no text segments inside the area
* @hide
*/
@Nullable
public int[] getRangeForRect(@NonNull RectF area, @NonNull SegmentFinder segmentFinder,
@@ -3215,8 +3208,7 @@ public abstract class Layout {
/**
* Strategy for determining whether a text segment is inside a rectangle area.
*
* @see #getRangeForRect(RectF, SegmentIterator, TextInclusionStrategy)
* @hide
* @see #getRangeForRect(RectF, SegmentFinder, TextInclusionStrategy)
*/
@FunctionalInterface
public interface TextInclusionStrategy {

View File

@@ -17,19 +17,20 @@
package android.text;
import android.annotation.IntRange;
import android.graphics.RectF;
/**
* Finds text segment boundaries within text. Subclasses can implement different types of text
* segments. Grapheme clusters and words are examples of possible text segments.
* segments. Grapheme clusters and words are examples of possible text segments. These are
* implemented by {@link GraphemeClusterSegmentFinder} and {@link WordSegmentFinder}.
*
* <p>Text segments may not overlap, so every character belongs to at most one text segment. A
* character may belong to no text segments.
*
* <p>For example, a word level text segment finder may subdivide the text "Hello, World!" into four
* text segments: "Hello", ",", "World", "!". The space character does not belong to any text
* segments.
* <p>For example, WordSegmentFinder subdivides the text "Hello, World!" into four text segments:
* "Hello", ",", "World", "!". The space character does not belong to any text segments.
*
* @hide
* @see Layout#getRangeForRect(RectF, SegmentFinder, Layout.TextInclusionStrategy)
*/
public abstract class SegmentFinder {
public static final int DONE = -1;

View File

@@ -18,20 +18,35 @@ package android.text;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.icu.text.BreakIterator;
import android.text.method.WordIterator;
import java.util.Locale;
/**
* Implementation of {@code SegmentFinder} using words as the text segment. Word boundaries are
* found using {@code WordIterator}. Whitespace characters are excluded, so they are not included in
* Implementation of {@link SegmentFinder} using words as the text segment. Word boundaries are
* found using {@link WordIterator}. Whitespace characters are excluded, so they are not included in
* any text segments.
*
* @hide
* <p>For example, the text "Hello, World!" would be subdivided into four text segments: "Hello",
* ",", "World", "!". The space character does not belong to any text segments.
*
* @see <a href="https://unicode.org/reports/tr29/#Word_Boundaries">Unicode Text Segmentation - Word
* Boundaries</a>
*/
public class WordSegmentFinder extends SegmentFinder {
private final CharSequence mText;
private final WordIterator mWordIterator;
public WordSegmentFinder(
@NonNull CharSequence text, @SuppressLint("UseIcu") @NonNull Locale locale) {
mText = text;
mWordIterator = new WordIterator(locale);
mWordIterator.setCharSequence(text, 0, text.length());
}
/** @hide */
public WordSegmentFinder(@NonNull CharSequence text, @NonNull WordIterator wordIterator) {
mText = text;
mWordIterator = wordIterator;

View File

@@ -1,595 +0,0 @@
/*
* Copyright (C) 2022 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 static com.google.common.truth.Truth.assertThat;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.text.method.WordIterator;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class LayoutGetRangeForRectTest {
private static final int WIDTH = 200;
private static final int HEIGHT = 1000;
private static final String DEFAULT_TEXT = ""
// Line 0 (offset 0 to 18)
// - Word 0 (offset 0 to 4) has bounds [0, 40], center 20
// - Word 1 (offset 5 to 11) has bounds [50, 110], center 80
// - Word 2 (offset 12 to 17) has bounds [120, 170], center 145
+ "XXXX XXXXXX XXXXX "
// Line 1 (offset 18 to 36)
// - Word 3 (offset 18 to 23) has bounds [0, 50], center 25
// - Word 4 (offset 24 to 26, RTL) has bounds [100, 110], center 105
// - Word 5 (offset 27 to 29, RTL) has bounds [80, 90], center 85
// - Word 6 start part (offset 30 to 32, RTL) has bounds [60, 70], center 65
// - Word 6 end part (offset 32 to 35) has bounds [110, 140], center 125
+ "XXXXX \u05D1\u05D1 \u05D1\u05D1 \u05D1\u05D1XXX\n"
// Line 2 (offset 36 to 38)
// - Word 7 start part (offset 36 to 38) has bounds [0, 150], center 75
// Line 3 (offset 38 to 40)
// - Word 7 middle part (offset 38 to 40) has bounds [0, 150], center 75
// Line 4 (offset 40 to 46)
// - Word 7 end part (offset 40 to 41) has bounds [0, 100], center 50
// - Word 8 (offset 42 to 44) has bounds [110, 130], center 120
+ "CLCLC XX \n";
private Layout mLayout;
private float[] mLineCenters;
private GraphemeClusterSegmentFinder mGraphemeClusterSegmentFinder;
private WordSegmentFinder mWordSegmentFinder;
@Before
public void setup() {
// The test font includes the following characters:
// U+0020 ( ): 10em
// U+002E (.): 10em
// U+0049 (I): 1em
// U+0056 (V): 5em
// U+0058 (X): 10em
// U+004C (L): 50em
// U+0043 (C): 100em
// U+005F (_): 0em
// U+05D0 : 1em // HEBREW LETTER ALEF
// U+05D1 : 5em // HEBREW LETTER BET
// U+FFFD (invalid surrogate will be replaced to this): 7em
// U+10331 (\uD800\uDF31): 10em
// Undefined : 0.5em
TextPaint textPaint = new TextPaint();
textPaint.setTypeface(
Typeface.createFromAsset(
InstrumentationRegistry.getInstrumentation().getTargetContext().getAssets(),
"fonts/StaticLayoutLineBreakingTestFont.ttf"));
// Make 1 em equal to 1 pixel.
textPaint.setTextSize(1.0f);
mLayout = StaticLayout.Builder.obtain(
DEFAULT_TEXT, 0, DEFAULT_TEXT.length(), textPaint, WIDTH).build();
mLineCenters = new float[mLayout.getLineCount()];
for (int i = 0; i < mLayout.getLineCount(); ++i) {
mLineCenters[i] = (mLayout.getLineTop(i)
+ mLayout.getLineBottom(i, /* includeLineSpacing= */ false)) / 2f;
}
mGraphemeClusterSegmentFinder = new GraphemeClusterSegmentFinder(DEFAULT_TEXT, textPaint);
WordIterator wordIterator = new WordIterator();
wordIterator.setCharSequence(DEFAULT_TEXT, 0, DEFAULT_TEXT.length());
mWordSegmentFinder = new WordSegmentFinder(DEFAULT_TEXT, wordIterator);
}
@Test
public void character_anyOverlap() {
// Character 37 on line 2
RectF area = new RectF(
mLayout.getPrimaryHorizontal(37) + 2f,
mLayout.getLineTop(2),
mLayout.getPrimaryHorizontal(37) + 3f,
mLayout.getLineTop(2) + 1f);
int[] range = mLayout.getRangeForRect(
area, mGraphemeClusterSegmentFinder, Layout.INCLUSION_STRATEGY_ANY_OVERLAP);
assertThat(range).asList().containsExactly(37, 38).inOrder();
}
@Test
public void character_anyOverlap_multiLine() {
// Character 4 on line 0
// Character 37 on line 2
RectF area = new RectF(
mLayout.getPrimaryHorizontal(4) + 1f,
mLayout.getLineBottom(0, /* includeLineSpacing= */ false) - 1,
mLayout.getPrimaryHorizontal(37) + 1f,
mLayout.getLineTop(2) + 1f);
int[] range = mLayout.getRangeForRect(
area, mGraphemeClusterSegmentFinder, Layout.INCLUSION_STRATEGY_ANY_OVERLAP);
assertThat(range).asList().containsExactly(4, 38).inOrder();
}
@Test
public void character_containsCenter() {
// Character 1 on line 0 has center 15.
// Character 2 on line 0 has center 25.
RectF area = new RectF(14f, mLineCenters[0] - 1f, 26f, mLineCenters[0] + 1f);
int[] range = mLayout.getRangeForRect(
area, mGraphemeClusterSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
assertThat(range).asList().containsExactly(1, 3).inOrder();
}
@Test
public void character_containsCenter_partialCharacterButNotCenter() {
// Character 0 on line 0 has center 5.
// Character 1 on line 0 has center 15.
// Character 2 on line 0 has center 25.
// Character 3 on line 0 has center 35.
RectF area = new RectF(6f, mLineCenters[0] - 1f, 34f, mLineCenters[0] + 1f);
int[] range = mLayout.getRangeForRect(
area, mGraphemeClusterSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
// Area partially overlaps characters 0 and 3 but does not contain their centers.
assertThat(range).asList().containsExactly(1, 3).inOrder();
}
@Test
public void character_containsCenter_rtl() {
// Character 25 on line 1 has center 102.5.
// Character 26 on line 1 has center 95.
RectF area = new RectF(94f, mLineCenters[1] - 1f, 103f, mLineCenters[1] + 1f);
int[] range = mLayout.getRangeForRect(
area, mGraphemeClusterSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
assertThat(range).asList().containsExactly(25, 27).inOrder();
}
@Test
public void character_containsCenter_ltrAndRtl() {
// Character 22 on line 1 has center 45.
// The end of the RTL run (offset 24 to 32) on line 1 is at 60.
RectF area = new RectF(44f, mLineCenters[1] - 1f, 93f, mLineCenters[1] + 1f);
int[] range = mLayout.getRangeForRect(
area, mGraphemeClusterSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
assertThat(range).asList().containsExactly(22, 32).inOrder();
}
@Test
public void character_containsCenter_rtlAndLtr() {
// The start of the RTL run (offset 24 to 32) on line 1 is at 110.
// Character 33 on line 1 has center 125.
RectF area = new RectF(93f, mLineCenters[1] - 1f, 131f, mLineCenters[1] + 1f);
int[] range = mLayout.getRangeForRect(
area, mGraphemeClusterSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
assertThat(range).asList().containsExactly(24, 34).inOrder();
}
@Test
public void character_containsCenter_betweenCharacters_returnsNull() {
// Character 1 on line 0 has center 15.
// Character 2 on line 0 has center 25.
RectF area = new RectF(16f, mLineCenters[0] - 1f, 24f, mLineCenters[0] + 1f);
int[] range = mLayout.getRangeForRect(
area, mGraphemeClusterSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
assertThat(range).isNull();
}
@Test
public void character_containsCenter_betweenLines_returnsNull() {
// Area top is below the center of line 0.
// Area bottom is above the center of line 1.
RectF area = new RectF(0f, mLineCenters[0] + 1f, WIDTH, mLineCenters[1] - 1f);
int[] range = mLayout.getRangeForRect(
area, mGraphemeClusterSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
// Area partially covers two lines but does not contain the center of any characters.
assertThat(range).isNull();
}
@Test
public void character_containsCenter_multiLine() {
// Character 9 on line 0 has center 95.
// Character 42 on line 4 has center 115.
RectF area = new RectF(93f, 0, 118f, HEIGHT);
int[] range = mLayout.getRangeForRect(
area, mGraphemeClusterSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
assertThat(range).asList().containsExactly(9, 43).inOrder();
}
@Test
public void character_containsCenter_multiLine_betweenCharactersOnSomeLines() {
// Character 6 on line 0 has center 65.
// Character 7 on line 0 has center 75.
// Character 30 on line 1 has center 67.5.
// Character 36 on line 2 has center 50.
// Character 37 on line 2 has center 125.
// Character 38 on line 3 has center 50.
// Character 39 on line 3 has center 125.
// Character 40 on line 4 has center 50.
// Character 41 on line 4 has center 105.
RectF area = new RectF(66f, 0, 69f, HEIGHT);
int[] range = mLayout.getRangeForRect(
area, mGraphemeClusterSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
// Area crosses all lines but does not contain the center of any characters on lines 0, 2,
// 3, or 4. So the only included character is character 30 on line 1.
assertThat(range).asList().containsExactly(30, 31).inOrder();
}
@Test
public void character_containsCenter_multiLine_betweenCharactersOnAllLines_returnsNull() {
// Character 6 on line 0 has center 65.
// Character 7 on line 0 has center 75.
// Character 30 on line 1 has center 67.5.
// Character 31 on line 1 has center 62.5.
// Character 36 on line 2 has center 50.
// Character 37 on line 2 has center 125.
// Character 38 on line 3 has center 50.
// Character 39 on line 3 has center 125.
// Character 40 on line 4 has center 50.
// Character 41 on line 4 has center 105.
RectF area = new RectF(66f, 0, 67f, HEIGHT);
int[] range = mLayout.getRangeForRect(
area, mGraphemeClusterSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
// Area crosses all lines but does not contain the center of any characters.
assertThat(range).isNull();
}
@Test
public void character_containsCenter_all() {
// Entire area, should include all text.
RectF area = new RectF(0f, 0f, WIDTH, HEIGHT);
int[] range = mLayout.getRangeForRect(
area, mGraphemeClusterSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
assertThat(range).asList().containsExactly(0, DEFAULT_TEXT.length()).inOrder();
}
@Test
public void character_containsAll() {
RectF area = new RectF(
mLayout.getPrimaryHorizontal(3) - 1,
mLayout.getLineTop(0) - 1,
mLayout.getPrimaryHorizontal(13) + 1,
mLayout.getLineBottom(0, /* includeLineSpacing= */ false) + 1);
int[] range = mLayout.getRangeForRect(
area, mGraphemeClusterSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_ALL);
assertThat(range).asList().containsExactly(3, 13).inOrder();
}
@Test
public void character_containsAll_charactersNotFullyCovered() {
RectF area = new RectF(
mLayout.getPrimaryHorizontal(3) + 1,
mLayout.getLineTop(0) - 1,
mLayout.getPrimaryHorizontal(13) - 1,
mLayout.getLineBottom(0, /* includeLineSpacing= */ false) + 1);
int[] range = mLayout.getRangeForRect(
area, mGraphemeClusterSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_ALL);
// The left edge of the area is 1 pixel to the right of the left bound of character 3, so it
// does not fully cover the character.
// The right edge of the area is 1 pixel to the left of the right bound of character 12, so
// it does not fully cover the character.
assertThat(range).asList().containsExactly(4, 12).inOrder();
}
@Test
public void character_containsAll_twoLines() {
RectF area = new RectF(
mLayout.getPrimaryHorizontal(3) - 1,
mLayout.getLineTop(0) - 1,
mLayout.getPrimaryHorizontal(33) + 1,
mLayout.getLineBottom(1, /* includeLineSpacing= */ false) + 1);
int[] range = mLayout.getRangeForRect(
area, mGraphemeClusterSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_ALL);
assertThat(range).asList().containsExactly(3, 33).inOrder();
}
@Test
public void character_containsAll_rtl() {
// Characters 24 to 32 on line 1 are RTL.
RectF area = new RectF(
mLayout.getPrimaryHorizontal(31) - 1,
mLayout.getLineTop(1) - 1,
mLayout.getPrimaryHorizontal(27) + 1,
mLayout.getLineBottom(1, /* includeLineSpacing= */ false) + 1);
int[] range = mLayout.getRangeForRect(
area, mGraphemeClusterSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_ALL);
assertThat(range).asList().containsExactly(27, 31).inOrder();
}
@Test
public void character_containsAll_linesNotFullyCovered_returnsNull() {
RectF area = new RectF(
0f,
mLayout.getLineTop(0) + 1,
WIDTH,
mLayout.getLineBottom(1, /* includeLineSpacing= */ false) - 1);
int[] range = mLayout.getRangeForRect(
area, mGraphemeClusterSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_ALL);
// The top edge of the area is 1 pixel below the top bound of the characters on line 0, so
// it does not fully cover any of them.
// The bottom edge of the area is 1 pixel is 1 pixel above the bottom bound of the
// characters on line 1, so it does not fully cover any of them.
assertThat(range).isNull();
}
@Test
public void word_anyOverlap() {
// Character 8 on line 0 is in word 1 (offset 5 to 11)
RectF area = new RectF(
mLayout.getPrimaryHorizontal(8) + 2f,
mLayout.getLineTop(0),
mLayout.getPrimaryHorizontal(8) + 3f,
mLayout.getLineTop(0) + 1f);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_ANY_OVERLAP);
// Selects all of word 1 (offset 5 to 11)
assertThat(range).asList().containsExactly(5, 11).inOrder();
}
@Test
public void word_anyOverlap_wordSpansMultipleLines_middlePartInsideArea() {
// Character 37 on line 2 is in word 7 (offset 36 to 41)
RectF area = new RectF(
mLayout.getPrimaryHorizontal(37) + 2f,
mLayout.getLineTop(2),
mLayout.getPrimaryHorizontal(37) + 3f,
mLayout.getLineTop(2) + 1f);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_ANY_OVERLAP);
// Selects all of word 7, not just the first part on line 2.
assertThat(range).asList().containsExactly(36, 41).inOrder();
}
@Test
public void word_containsCenter() {
// Word 1 (offset 5 to 11) on line 0 has center 80.
RectF area = new RectF(79f, mLineCenters[0] - 1f, 81f, mLineCenters[0] + 1f);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
assertThat(range).asList().containsExactly(5, 11).inOrder();
}
@Test
public void word_containsCenter_partialWordButNotCenter() {
// Word 0 (offset 0 to 4) on line 0 has center 20.
// Word 1 (offset 5 to 11) on line 0 has center 80.
// Word 2 (offset 12 to 17) on line 0 center 145
RectF area = new RectF(21f, mLineCenters[0] - 1f, 144f, mLineCenters[0] + 1f);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
// Area partially overlaps words 0 and 2 but does not contain their centers, so only word 1
// is included. Whitespace between words is not included.
assertThat(range).asList().containsExactly(5, 11).inOrder();
}
@Test
public void word_containsCenter_rtl() {
// Word 4 (offset 24 to 26, RTL) on line 1 center 105
RectF area = new RectF(88f, mLineCenters[1] - 1f, 119f, mLineCenters[1] + 1f);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
assertThat(range).asList().containsExactly(24, 26).inOrder();
}
@Test
public void word_containsCenter_ltrAndRtl() {
// Word 3 (offset 18 to 23) on line 1 has center 25
// The end of the RTL run (offset 24 to 32) on line 1 is at 60.
RectF area = new RectF(24f, mLineCenters[1] - 1f, 93f, mLineCenters[1] + 1f);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
// Selects all of word 6, not just the first RTL part.
assertThat(range).asList().containsExactly(18, 35).inOrder();
}
@Test
public void word_containsCenter_rtlAndLtr() {
// The start of the RTL run (offset 24 to 32) on line 1 is at 110.
// End part of word 6 (offset 32 to 35) on line 1 has center 125.
RectF area = new RectF(93f, mLineCenters[1] - 1f, 174f, mLineCenters[1] + 1f);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
assertThat(range).asList().containsExactly(24, 35).inOrder();
}
@Test
public void word_containsCenter_betweenWords_returnsNull() {
// Word 1 on line 0 has center 80.
// Word 2 on line 0 has center 145.
RectF area = new RectF(81f, mLineCenters[0] - 1f, 144f, mLineCenters[0] + 1f);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
assertThat(range).isNull();
}
@Test
public void word_containsCenter_betweenLines_returnsNull() {
// Area top is below the center of line 0.
// Area bottom is above the center of line 1.
RectF area = new RectF(0f, mLineCenters[0] + 1f, WIDTH, mLineCenters[1] - 1f);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
// Area partially covers two lines but does not contain the center of any words.
assertThat(range).isNull();
}
@Test
public void word_containsCenter_multiLine() {
// Word 1 (offset 5 to 11) on line 0 has center 80.
// End part of word 7 (offset 40 to 41) on line 4 has center 50.
RectF area = new RectF(42f, 0, 91f, HEIGHT);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
assertThat(range).asList().containsExactly(5, 41).inOrder();
}
@Test
public void word_containsCenter_multiLine_betweenWordsOnSomeLines() {
// Word 1 on line 0 has center 80.
// Word 2 on line 0 has center 145.
// Word 5 (offset 27 to 29) on line 1 has center 85.
// Word 7 on line 2 has center 50.
// Word 37 on line 2 has center 125.
// Word 38 on line 3 has center 50.
// Word 39 on line 3 has center 125.
// Word 40 on line 4 has center 50.
// Word 41 on line 4 has center 105.
RectF area = new RectF(84f, 0, 86f, HEIGHT);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
// Area crosses all lines but does not contain the center of any words on lines 0, 2, 3, or
// 4. So the only included word is word 5 on line 1.
assertThat(range).asList().containsExactly(27, 29).inOrder();
}
@Test
public void word_containsCenter_multiLine_betweenCharactersOnAllLines_returnsNull() {
// Word 1 on line 0 has center 80.
// Word 2 on line 0 has center 145.
// Word 4 on line 1 has center 105.
// Word 5 on line 1 has center 85.
// Word 7 on line 2 has center 50.
// Word 37 on line 2 has center 125.
// Word 38 on line 3 has center 50.
// Word 39 on line 3 has center 125.
// Word 40 on line 4 has center 50.
// Word 41 on line 4 has center 105.
RectF area = new RectF(86f, 0, 89f, HEIGHT);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
// Area crosses all lines but does not contain the center of any words.
assertThat(range).isNull();
}
@Test
public void word_containsCenter_wordSpansMultipleLines_firstPartInsideArea() {
// Word 5 (offset 27 to 29) on line 1 has center 85.
// First part of word 7 (offset 36 to 38) on line 2 has center 75.
RectF area = new RectF(74f, mLineCenters[1] - 1f, 86f, mLineCenters[2] + 1f);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
// Selects all of word 7, not just the first part on line 2.
assertThat(range).asList().containsExactly(27, 41).inOrder();
}
@Test
public void word_containsCenter_wordSpansMultipleLines_middlePartInsideArea() {
// Middle part of word 7 (offset 38 to 40) on line 2 has center 75.
RectF area = new RectF(74f, mLineCenters[3] - 1f, 76f, mLineCenters[3] + 1f);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
// Selects all of word 7, not just the middle part on line 3.
assertThat(range).asList().containsExactly(36, 41).inOrder();
}
@Test
public void word_containsCenter_wordSpansMultipleLines_endPartInsideArea() {
// End part of word 7 (offset 40 to 41) on line 4 has center 50.
// Word 8 (offset 42 to 44) on line 4 has center 120
RectF area = new RectF(49f, mLineCenters[4] - 1f, 121f, mLineCenters[4] + 1f);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
// Selects all of word 7, not just the middle part on line 3.
assertThat(range).asList().containsExactly(36, 44).inOrder();
}
@Test
public void word_containsCenter_wordSpansMultipleRuns_firstPartInsideArea() {
// Word 5 (offset 27 to 29) on line 1 has center 85.
// First part of word 6 (offset 30 to 32) on line 1 has center 65.
RectF area = new RectF(64f, mLineCenters[1] - 1f, 86f, mLineCenters[1] + 1f);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
// Selects all of word 6, not just the first RTL part.
assertThat(range).asList().containsExactly(27, 35).inOrder();
}
@Test
public void word_containsCenter_all() {
// Entire area, should include all text except the last two whitespace characters.
RectF area = new RectF(0f, 0f, WIDTH, HEIGHT);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
assertThat(range).asList().containsExactly(0, DEFAULT_TEXT.length() - 2).inOrder();
}
@Test
public void word_containsAll() {
// Word 1 (offset 5 to 11) on line 0
RectF area = new RectF(
mLayout.getPrimaryHorizontal(5) - 1,
mLayout.getLineTop(0) - 1,
mLayout.getPrimaryHorizontal(11) + 1,
mLayout.getLineBottom(0, /* includeLineSpacing= */ false) + 1);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_ALL);
assertThat(range).asList().containsExactly(5, 11).inOrder();
}
@Test
public void word_containsAll_notFullyCovered_returnsNull() {
// Word 1 (offset 5 to 11) on line 0
RectF area = new RectF(
mLayout.getPrimaryHorizontal(5) + 1,
mLayout.getLineTop(0) - 1,
mLayout.getPrimaryHorizontal(11) + 1,
mLayout.getLineBottom(0, /* includeLineSpacing= */ false) + 1);
int[] range = mLayout.getRangeForRect(
area, mWordSegmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_ALL);
// The left edge of the area is 1 pixel to the right of the left bound of character 5, so it
// does not fully cover the word.
assertThat(range).isNull();
}
}