script-astra/Android/Sdk/sources/android-35/android/text/GraphemeClusterSegmentFinder.java

117 lines
4.0 KiB
Java
Raw Normal View History

2025-01-20 15:15:20 +00:00
/*
* 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 android.annotation.IntRange;
import android.annotation.NonNull;
import android.graphics.TemporaryBuffer;
import android.graphics.text.GraphemeBreak;
/**
* Implementation of {@code SegmentFinder} using grapheme clusters as the text segment. Whitespace
* characters are included as segments.
*
* <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 static AutoGrowArray.FloatArray sTempAdvances = null;
private final boolean[] mIsGraphemeBreak;
/**
* Constructs a GraphemeClusterSegmentFinder instance for the specified text which uses the
* provided TextPaint to determine grapheme cluster boundaries.
*
* @param text text to be segmented
* @param textPaint TextPaint used to draw the text
*/
public GraphemeClusterSegmentFinder(
@NonNull CharSequence text, @NonNull TextPaint textPaint) {
if (sTempAdvances == null) {
sTempAdvances = new AutoGrowArray.FloatArray(text.length());
} else if (sTempAdvances.size() < text.length()) {
sTempAdvances.resize(text.length());
}
mIsGraphemeBreak = new boolean[text.length()];
float[] advances = sTempAdvances.getRawArray();
char[] chars = TemporaryBuffer.obtain(text.length());
TextUtils.getChars(text, 0, text.length(), chars, 0);
textPaint.getTextWidths(chars, 0, text.length(), advances);
GraphemeBreak.isGraphemeBreak(advances, chars, /* start= */ 0, /* end= */ text.length(),
mIsGraphemeBreak);
TemporaryBuffer.recycle(chars);
}
private int previousBoundary(@IntRange(from = 0) int offset) {
if (offset <= 0) return DONE;
do {
--offset;
} while (offset > 0 && !mIsGraphemeBreak[offset]);
return offset;
}
private int nextBoundary(@IntRange(from = 0) int offset) {
if (offset >= mIsGraphemeBreak.length) return DONE;
do {
++offset;
} while (offset < mIsGraphemeBreak.length && !mIsGraphemeBreak[offset]);
return offset;
}
@Override
public int previousStartBoundary(@IntRange(from = 0) int offset) {
return previousBoundary(offset);
}
@Override
public int previousEndBoundary(@IntRange(from = 0) int offset) {
if (offset == 0) return DONE;
int boundary = previousBoundary(offset);
// Check that there is another cursor position before, otherwise this is not a valid
// end boundary.
if (boundary == DONE || previousBoundary(boundary) == DONE) {
return DONE;
}
return boundary;
}
@Override
public int nextStartBoundary(@IntRange(from = 0) int offset) {
if (offset == mIsGraphemeBreak.length) return DONE;
int boundary = nextBoundary(offset);
// Check that there is another cursor position after, otherwise this is not a valid
// start boundary.
if (boundary == DONE || nextBoundary(boundary) == DONE) {
return DONE;
}
return boundary;
}
@Override
public int nextEndBoundary(@IntRange(from = 0) int offset) {
return nextBoundary(offset);
}
}