• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.text;
18 
19 import android.annotation.IntRange;
20 import android.annotation.NonNull;
21 import android.graphics.TemporaryBuffer;
22 import android.graphics.text.GraphemeBreak;
23 
24 /**
25  * Implementation of {@code SegmentFinder} using grapheme clusters as the text segment. Whitespace
26  * characters are included as segments.
27  *
28  * <p>For example, the text "a pot" would be divided into five text segments: "a", " ", "p", "o",
29  * "t".
30  *
31  * @see <a href="https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries">Unicode Text
32  *     Segmentation - Grapheme Cluster Boundaries</a>
33  */
34 @android.ravenwood.annotation.RavenwoodKeepWholeClass
35 public class GraphemeClusterSegmentFinder extends SegmentFinder {
36     private static AutoGrowArray.FloatArray sTempAdvances = null;
37     private final boolean[] mIsGraphemeBreak;
38 
39     /**
40      * Constructs a GraphemeClusterSegmentFinder instance for the specified text which uses the
41      * provided TextPaint to determine grapheme cluster boundaries.
42      *
43      * @param text text to be segmented
44      * @param textPaint TextPaint used to draw the text
45      */
GraphemeClusterSegmentFinder( @onNull CharSequence text, @NonNull TextPaint textPaint)46     public GraphemeClusterSegmentFinder(
47             @NonNull CharSequence text, @NonNull TextPaint textPaint) {
48 
49         if (sTempAdvances == null) {
50             sTempAdvances = new AutoGrowArray.FloatArray(text.length());
51         } else if (sTempAdvances.size() < text.length()) {
52             sTempAdvances.resize(text.length());
53         }
54 
55         mIsGraphemeBreak = new boolean[text.length()];
56         float[] advances = sTempAdvances.getRawArray();
57         char[] chars = TemporaryBuffer.obtain(text.length());
58 
59         TextUtils.getChars(text, 0, text.length(), chars, 0);
60 
61         textPaint.getTextWidths(chars, 0, text.length(), advances);
62 
63         GraphemeBreak.isGraphemeBreak(advances, chars, /* start= */ 0, /* end= */ text.length(),
64                 mIsGraphemeBreak);
65         TemporaryBuffer.recycle(chars);
66     }
67 
previousBoundary(@ntRangefrom = 0) int offset)68     private int previousBoundary(@IntRange(from = 0) int offset) {
69         if (offset <= 0) return DONE;
70         do {
71             --offset;
72         } while (offset > 0 && !mIsGraphemeBreak[offset]);
73         return offset;
74     }
75 
nextBoundary(@ntRangefrom = 0) int offset)76     private int nextBoundary(@IntRange(from = 0) int offset) {
77         if (offset >= mIsGraphemeBreak.length) return DONE;
78         do {
79             ++offset;
80         } while (offset < mIsGraphemeBreak.length && !mIsGraphemeBreak[offset]);
81         return offset;
82     }
83 
84     @Override
previousStartBoundary(@ntRangefrom = 0) int offset)85     public int previousStartBoundary(@IntRange(from = 0) int offset) {
86         return previousBoundary(offset);
87     }
88 
89     @Override
previousEndBoundary(@ntRangefrom = 0) int offset)90     public int previousEndBoundary(@IntRange(from = 0) int offset) {
91         if (offset == 0) return DONE;
92         int boundary = previousBoundary(offset);
93         // Check that there is another cursor position before, otherwise this is not a valid
94         // end boundary.
95         if (boundary == DONE || previousBoundary(boundary) == DONE) {
96             return DONE;
97         }
98         return boundary;
99     }
100 
101     @Override
nextStartBoundary(@ntRangefrom = 0) int offset)102     public int nextStartBoundary(@IntRange(from = 0) int offset) {
103         if (offset == mIsGraphemeBreak.length) return DONE;
104         int boundary = nextBoundary(offset);
105         // Check that there is another cursor position after, otherwise this is not a valid
106         // start boundary.
107         if (boundary == DONE || nextBoundary(boundary) == DONE) {
108             return DONE;
109         }
110         return boundary;
111     }
112 
113     @Override
nextEndBoundary(@ntRangefrom = 0) int offset)114     public int nextEndBoundary(@IntRange(from = 0) int offset) {
115         return nextBoundary(offset);
116     }
117 }
118