Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ee75e04f authored by Haoyu Zhang's avatar Haoyu Zhang Committed by Android (Google) Code Review
Browse files

Merge "Optimize GraphemeClusterSegmentFinder performance" into udc-dev

parents 65391c6b 043d23a8
Loading
Loading
Loading
Loading
+47 −24
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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);
    }
}
+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);
}
+1 −0
Original line number Diff line number Diff line
@@ -375,6 +375,7 @@ cc_defaults {
        "jni/text/LineBreaker.cpp",
        "jni/text/MeasuredText.cpp",
        "jni/text/TextShaper.cpp",
        "jni/text/GraphemeBreak.cpp",
    ],

    header_libs: [
+3 −0
Original line number Diff line number Diff line
@@ -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);
@@ -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)},
};

+2 −0
Original line number Diff line number Diff line
@@ -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);

@@ -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