Loading core/java/android/text/GraphemeClusterSegmentFinder.java +47 −24 Original line number Diff line number Diff line Loading @@ -18,7 +18,8 @@ package android.text; import android.annotation.IntRange; import android.annotation.NonNull; import android.graphics.Paint; import android.graphics.TemporaryBuffer; import android.graphics.text.GraphemeBreak; /** * Implementation of {@code SegmentFinder} using grapheme clusters as the text segment. Whitespace Loading @@ -31,8 +32,8 @@ import android.graphics.Paint; * Segmentation - Grapheme Cluster Boundaries</a> */ public class GraphemeClusterSegmentFinder extends SegmentFinder { private final CharSequence mText; private final TextPaint mTextPaint; private static AutoGrowArray.FloatArray sTempAdvances = null; private final boolean[] mIsGraphemeBreak; /** * Constructs a GraphemeClusterSegmentFinder instance for the specified text which uses the Loading @@ -43,51 +44,73 @@ public class GraphemeClusterSegmentFinder extends SegmentFinder { */ public GraphemeClusterSegmentFinder( @NonNull CharSequence text, @NonNull TextPaint textPaint) { mText = text; mTextPaint = 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) { if (offset == 0) return DONE; int boundary = mTextPaint.getTextRunCursor( mText, 0, mText.length(), false, offset, Paint.CURSOR_BEFORE); return boundary == -1 ? DONE : boundary; return previousBoundary(offset); } @Override public int previousEndBoundary(@IntRange(from = 0) int offset) { if (offset == 0) return DONE; int boundary = mTextPaint.getTextRunCursor( mText, 0, mText.length(), false, offset, Paint.CURSOR_BEFORE); int boundary = previousBoundary(offset); // Check that there is another cursor position before, otherwise this is not a valid // end boundary. if (mTextPaint.getTextRunCursor( mText, 0, mText.length(), false, boundary, Paint.CURSOR_BEFORE) == -1) { if (boundary == DONE || previousBoundary(boundary) == DONE) { return DONE; } return boundary == -1 ? DONE : boundary; return boundary; } @Override public int nextStartBoundary(@IntRange(from = 0) int offset) { if (offset == mText.length()) return DONE; int boundary = mTextPaint.getTextRunCursor( mText, 0, mText.length(), false, offset, Paint.CURSOR_AFTER); 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 (mTextPaint.getTextRunCursor( mText, 0, mText.length(), false, boundary, Paint.CURSOR_AFTER) == -1) { if (boundary == DONE || nextBoundary(boundary) == DONE) { return DONE; } return boundary == -1 ? DONE : boundary; return boundary; } @Override public int nextEndBoundary(@IntRange(from = 0) int offset) { if (offset == mText.length()) return DONE; int boundary = mTextPaint.getTextRunCursor( mText, 0, mText.length(), false, offset, Paint.CURSOR_AFTER); return boundary == -1 ? DONE : boundary; return nextBoundary(offset); } } graphics/java/android/graphics/text/GraphemeBreak.java 0 → 100644 +59 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.graphics.text; /** @hide */ public class GraphemeBreak { private GraphemeBreak() { } /** * Util method that checks if the offsets in given range are grapheme break. * * @param advances the advances of characters in the given text. It contains the font * information used by the algorithm to determine the grapheme break. It's useful * when some character is missing in the font. For example, if the smile emoji * "0xD83D 0xDE0A" is not found in the font and is displayed as 2 characters. * We can't treat it as a single grapheme cluster. * @param text the text to be processed. * @param start the start offset of the queried range, inclusive. * @param end the end offset of the queried range, exclusive. * @param isGraphemeBreak the array to receive the result. The i-th element of the * array will be set to true if the offset (start + i) is a grapheme * break; otherwise, it will be set to false. */ public static void isGraphemeBreak(float[] advances, char[] text, int start, int end, boolean[] isGraphemeBreak) { if (start > end) { throw new IllegalArgumentException("start is greater than end: start = " + start + " end = " + end); } if (advances.length < end) { throw new IllegalArgumentException("the length of advances is less than end" + "advances.length = " + advances.length + " end = " + end); } if (isGraphemeBreak.length < end - start) { throw new IndexOutOfBoundsException("isGraphemeBreak doesn't have enough space to " + "receive the result, isGraphemeBreak.length: " + isGraphemeBreak.length + " needed space: " + (end - start)); } nIsGraphemeBreak(advances, text, start, end, isGraphemeBreak); } private static native void nIsGraphemeBreak(float[] advances, char[] text, int start, int end, boolean[] isGraphemeBreak); } libs/hwui/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -375,6 +375,7 @@ cc_defaults { "jni/text/LineBreaker.cpp", "jni/text/MeasuredText.cpp", "jni/text/TextShaper.cpp", "jni/text/GraphemeBreak.cpp", ], header_libs: [ Loading libs/hwui/apex/LayoutlibLoader.cpp +3 −0 Original line number Diff line number Diff line Loading @@ -66,6 +66,7 @@ extern int register_android_graphics_fonts_FontFamily(JNIEnv* env); extern int register_android_graphics_text_LineBreaker(JNIEnv* env); extern int register_android_graphics_text_MeasuredText(JNIEnv* env); extern int register_android_graphics_text_TextShaper(JNIEnv* env); extern int register_android_graphics_text_GraphemeBreak(JNIEnv* env); extern int register_android_util_PathParser(JNIEnv* env); extern int register_android_view_DisplayListCanvas(JNIEnv* env); Loading Loading @@ -125,6 +126,8 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { {"android.graphics.text.MeasuredText", REG_JNI(register_android_graphics_text_MeasuredText)}, {"android.graphics.text.TextRunShaper", REG_JNI(register_android_graphics_text_TextShaper)}, {"android.graphics.text.GraphemeBreak", REG_JNI(register_android_graphics_text_GraphemeBreak)}, {"android.util.PathParser", REG_JNI(register_android_util_PathParser)}, }; Loading libs/hwui/apex/jni_runtime.cpp +2 −0 Original line number Diff line number Diff line Loading @@ -77,6 +77,7 @@ extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env); extern int register_android_graphics_text_MeasuredText(JNIEnv* env); extern int register_android_graphics_text_LineBreaker(JNIEnv *env); extern int register_android_graphics_text_TextShaper(JNIEnv *env); extern int register_android_graphics_text_GraphemeBreak(JNIEnv* env); extern int register_android_graphics_MeshSpecification(JNIEnv* env); extern int register_android_graphics_Mesh(JNIEnv* env); Loading Loading @@ -148,6 +149,7 @@ extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env); REG_JNI(register_android_graphics_text_MeasuredText), REG_JNI(register_android_graphics_text_LineBreaker), REG_JNI(register_android_graphics_text_TextShaper), REG_JNI(register_android_graphics_text_GraphemeBreak), REG_JNI(register_android_graphics_MeshSpecification), REG_JNI(register_android_graphics_Mesh), Loading Loading
core/java/android/text/GraphemeClusterSegmentFinder.java +47 −24 Original line number Diff line number Diff line Loading @@ -18,7 +18,8 @@ package android.text; import android.annotation.IntRange; import android.annotation.NonNull; import android.graphics.Paint; import android.graphics.TemporaryBuffer; import android.graphics.text.GraphemeBreak; /** * Implementation of {@code SegmentFinder} using grapheme clusters as the text segment. Whitespace Loading @@ -31,8 +32,8 @@ import android.graphics.Paint; * Segmentation - Grapheme Cluster Boundaries</a> */ public class GraphemeClusterSegmentFinder extends SegmentFinder { private final CharSequence mText; private final TextPaint mTextPaint; private static AutoGrowArray.FloatArray sTempAdvances = null; private final boolean[] mIsGraphemeBreak; /** * Constructs a GraphemeClusterSegmentFinder instance for the specified text which uses the Loading @@ -43,51 +44,73 @@ public class GraphemeClusterSegmentFinder extends SegmentFinder { */ public GraphemeClusterSegmentFinder( @NonNull CharSequence text, @NonNull TextPaint textPaint) { mText = text; mTextPaint = 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) { if (offset == 0) return DONE; int boundary = mTextPaint.getTextRunCursor( mText, 0, mText.length(), false, offset, Paint.CURSOR_BEFORE); return boundary == -1 ? DONE : boundary; return previousBoundary(offset); } @Override public int previousEndBoundary(@IntRange(from = 0) int offset) { if (offset == 0) return DONE; int boundary = mTextPaint.getTextRunCursor( mText, 0, mText.length(), false, offset, Paint.CURSOR_BEFORE); int boundary = previousBoundary(offset); // Check that there is another cursor position before, otherwise this is not a valid // end boundary. if (mTextPaint.getTextRunCursor( mText, 0, mText.length(), false, boundary, Paint.CURSOR_BEFORE) == -1) { if (boundary == DONE || previousBoundary(boundary) == DONE) { return DONE; } return boundary == -1 ? DONE : boundary; return boundary; } @Override public int nextStartBoundary(@IntRange(from = 0) int offset) { if (offset == mText.length()) return DONE; int boundary = mTextPaint.getTextRunCursor( mText, 0, mText.length(), false, offset, Paint.CURSOR_AFTER); 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 (mTextPaint.getTextRunCursor( mText, 0, mText.length(), false, boundary, Paint.CURSOR_AFTER) == -1) { if (boundary == DONE || nextBoundary(boundary) == DONE) { return DONE; } return boundary == -1 ? DONE : boundary; return boundary; } @Override public int nextEndBoundary(@IntRange(from = 0) int offset) { if (offset == mText.length()) return DONE; int boundary = mTextPaint.getTextRunCursor( mText, 0, mText.length(), false, offset, Paint.CURSOR_AFTER); return boundary == -1 ? DONE : boundary; return nextBoundary(offset); } }
graphics/java/android/graphics/text/GraphemeBreak.java 0 → 100644 +59 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.graphics.text; /** @hide */ public class GraphemeBreak { private GraphemeBreak() { } /** * Util method that checks if the offsets in given range are grapheme break. * * @param advances the advances of characters in the given text. It contains the font * information used by the algorithm to determine the grapheme break. It's useful * when some character is missing in the font. For example, if the smile emoji * "0xD83D 0xDE0A" is not found in the font and is displayed as 2 characters. * We can't treat it as a single grapheme cluster. * @param text the text to be processed. * @param start the start offset of the queried range, inclusive. * @param end the end offset of the queried range, exclusive. * @param isGraphemeBreak the array to receive the result. The i-th element of the * array will be set to true if the offset (start + i) is a grapheme * break; otherwise, it will be set to false. */ public static void isGraphemeBreak(float[] advances, char[] text, int start, int end, boolean[] isGraphemeBreak) { if (start > end) { throw new IllegalArgumentException("start is greater than end: start = " + start + " end = " + end); } if (advances.length < end) { throw new IllegalArgumentException("the length of advances is less than end" + "advances.length = " + advances.length + " end = " + end); } if (isGraphemeBreak.length < end - start) { throw new IndexOutOfBoundsException("isGraphemeBreak doesn't have enough space to " + "receive the result, isGraphemeBreak.length: " + isGraphemeBreak.length + " needed space: " + (end - start)); } nIsGraphemeBreak(advances, text, start, end, isGraphemeBreak); } private static native void nIsGraphemeBreak(float[] advances, char[] text, int start, int end, boolean[] isGraphemeBreak); }
libs/hwui/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -375,6 +375,7 @@ cc_defaults { "jni/text/LineBreaker.cpp", "jni/text/MeasuredText.cpp", "jni/text/TextShaper.cpp", "jni/text/GraphemeBreak.cpp", ], header_libs: [ Loading
libs/hwui/apex/LayoutlibLoader.cpp +3 −0 Original line number Diff line number Diff line Loading @@ -66,6 +66,7 @@ extern int register_android_graphics_fonts_FontFamily(JNIEnv* env); extern int register_android_graphics_text_LineBreaker(JNIEnv* env); extern int register_android_graphics_text_MeasuredText(JNIEnv* env); extern int register_android_graphics_text_TextShaper(JNIEnv* env); extern int register_android_graphics_text_GraphemeBreak(JNIEnv* env); extern int register_android_util_PathParser(JNIEnv* env); extern int register_android_view_DisplayListCanvas(JNIEnv* env); Loading Loading @@ -125,6 +126,8 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { {"android.graphics.text.MeasuredText", REG_JNI(register_android_graphics_text_MeasuredText)}, {"android.graphics.text.TextRunShaper", REG_JNI(register_android_graphics_text_TextShaper)}, {"android.graphics.text.GraphemeBreak", REG_JNI(register_android_graphics_text_GraphemeBreak)}, {"android.util.PathParser", REG_JNI(register_android_util_PathParser)}, }; Loading
libs/hwui/apex/jni_runtime.cpp +2 −0 Original line number Diff line number Diff line Loading @@ -77,6 +77,7 @@ extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env); extern int register_android_graphics_text_MeasuredText(JNIEnv* env); extern int register_android_graphics_text_LineBreaker(JNIEnv *env); extern int register_android_graphics_text_TextShaper(JNIEnv *env); extern int register_android_graphics_text_GraphemeBreak(JNIEnv* env); extern int register_android_graphics_MeshSpecification(JNIEnv* env); extern int register_android_graphics_Mesh(JNIEnv* env); Loading Loading @@ -148,6 +149,7 @@ extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env); REG_JNI(register_android_graphics_text_MeasuredText), REG_JNI(register_android_graphics_text_LineBreaker), REG_JNI(register_android_graphics_text_TextShaper), REG_JNI(register_android_graphics_text_GraphemeBreak), REG_JNI(register_android_graphics_MeshSpecification), REG_JNI(register_android_graphics_Mesh), Loading