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

Commit c8abba49 authored by Haoyu Zhang's avatar Haoyu Zhang
Browse files

Introduce TextLine#measureAllbounds

This new method computes the horizontal character bounds by making
use of the character advances returned from native layer. It'll be
used in TextView to populate character bounds, and it's much efficent
compared to calling getPrimaryHorizontal for each character.

Bug: 233922052
Test: atest android.text.TextLineTest
Change-Id: Icdd53e61e2d1513b2231affb19bb00ea5d938d48
parent 625a9709
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -14928,6 +14928,8 @@ package android.graphics {
    method public android.graphics.PathEffect getPathEffect();
    method public float getRunAdvance(char[], int, int, int, int, boolean, int);
    method public float getRunAdvance(CharSequence, int, int, int, int, boolean, int);
    method public float getRunCharacterAdvance(@NonNull char[], int, int, int, int, boolean, int, @Nullable float[], int);
    method public float getRunCharacterAdvance(@NonNull CharSequence, int, int, int, int, boolean, int, @Nullable float[], int);
    method public android.graphics.Shader getShader();
    method @ColorInt public int getShadowLayerColor();
    method @ColorLong public long getShadowLayerColorLong();
+169 −31
Original line number Diff line number Diff line
@@ -408,14 +408,14 @@ public class TextLine {
                    final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;

                    if (targetIsInThisSegment && sameDirection) {
                        return h + measureRun(segStart, offset, j, runIsRtl, fmi);
                        return h + measureRun(segStart, offset, j, runIsRtl, fmi, null, 0);
                    }

                    final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi);
                    final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi, null, 0);
                    h += sameDirection ? segmentWidth : -segmentWidth;

                    if (targetIsInThisSegment) {
                        return h + measureRun(segStart, offset, j, runIsRtl, null);
                        return h + measureRun(segStart, offset, j, runIsRtl, null, null, 0);
                    }

                    if (j != runLimit) {  // charAt(j) == TAB_CHAR
@@ -436,6 +436,116 @@ public class TextLine {
        return h;
    }

    /**
     * Return the signed horizontal bounds of the characters in the line.
     *
     * The length of the returned array equals to 2 * mLen. The left bound of the i th character
     * is stored at index 2 * i. And the right bound of the i th character is stored at index
     * (2 * i + 1).
     *
     * Check the following examples. LX(e.g. L0, L1, ...) denotes a character which has LTR BiDi
     * property. On the other hand, RX(e.g. R0, R1, ...) denotes a character which has RTL BiDi
     * property. Assuming all character has 1em width.
     *
     * Example 1: All LTR chars within LTR context
     *   Input Text (logical)  :   L0 L1 L2 L3
     *   Input Text (visual)   :   L0 L1 L2 L3
     *   Output :  [0em, 1em, 1em, 2em, 2em, 3em, 3em, 4em]
     *
     * Example 2: All RTL chars within RTL context.
     *   Input Text (logical)  :   R0 R1 R2 R3
     *   Input Text (visual)   :   R3 R2 R1 R0
     *   Output :  [-1em, 0em, -2em, -1em, -3em, -2em, -4em, -3em]

     *
     * Example 3: BiDi chars within LTR context.
     *   Input Text (logical)  :   L0 L1 R2 R3 L4 L5
     *   Input Text (visual)   :   L0 L1 R3 R2 L4 L5
     *   Output :  [0em, 1em, 1em, 2em, 3em, 4em, 2em, 3em, 4em, 5em, 5em, 6em]

     *
     * Example 4: BiDi chars within RTL context.
     *   Input Text (logical)  :   L0 L1 R2 R3 L4 L5
     *   Input Text (visual)   :   L4 L5 R3 R2 L0 L1
     *   Output :  [-2em, -1em, -1em, 0em, -3em, -2em, -4em, -3em, -6em, -5em, -5em, -4em]
     *
     * @param bounds the array to receive the character bounds data. Its length should be at least
     *               2 times of the line length.
     * @param advances the array to receive the character advance data, nullable. If provided, its
     *                 length should be equal or larger than the line length.
     *
     * @throws IllegalArgumentException if the given {@code bounds} is null.
     * @throws IndexOutOfBoundsException if the given {@code bounds} or {@code advances} doesn't
     * have enough space to hold the result.
     */
    public void measureAllBounds(@NonNull float[] bounds, @Nullable float[] advances) {
        if (bounds == null) {
            throw new IllegalArgumentException("bounds can't be null");
        }
        if (bounds.length < 2 * mLen) {
            throw new IndexOutOfBoundsException("bounds doesn't have enough space to receive the "
                    + "result, needed: " + (2 * mLen) + " had: " + bounds.length);
        }
        if (advances == null) {
            advances = new float[mLen];
        }
        if (advances.length < mLen) {
            throw new IndexOutOfBoundsException("advance doesn't have enough space to receive the "
                    + "result, needed: " + mLen + " had: " + advances.length);
        }
        float h = 0;
        for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
            final int runStart = mDirections.getRunStart(runIndex);
            if (runStart > mLen) break;
            final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
            final boolean runIsRtl = mDirections.isRunRtl(runIndex);

            int segStart = runStart;
            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
                if (j == runLimit || charAt(j) == TAB_CHAR) {
                    final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;

                    final float segmentWidth =
                            measureRun(segStart, j, j, runIsRtl, null, advances, segStart);

                    final float oldh = h;
                    h += sameDirection ? segmentWidth : -segmentWidth;
                    float currh = sameDirection ? oldh : h;
                    for (int offset = segStart; offset < j && offset < mLen; ++offset) {
                        if (runIsRtl) {
                            bounds[2 * offset + 1] = currh;
                            currh -= advances[offset];
                            bounds[2 * offset] = currh;
                        } else {
                            bounds[2 * offset] = currh;
                            currh += advances[offset];
                            bounds[2 * offset + 1] = currh;
                        }
                    }

                    if (j != runLimit) {  // charAt(j) == TAB_CHAR
                        final float leftX;
                        final float rightX;
                        if (runIsRtl) {
                            rightX = h;
                            h = mDir * nextTab(h * mDir);
                            leftX = h;
                        } else {
                            leftX = h;
                            h = mDir * nextTab(h * mDir);
                            rightX = h;
                        }
                        bounds[2 * j] = leftX;
                        bounds[2 * j + 1] = rightX;
                        advances[j] = rightX - leftX;
                    }

                    segStart = j + 1;
                }
            }
        }
    }

    /**
     * @see #measure(int, boolean, FontMetricsInt)
     * @return The measure results for all possible offsets
@@ -464,15 +574,15 @@ public class TextLine {
                if (j == runLimit || charAt(j) == TAB_CHAR) {
                    final  float oldh = h;
                    final boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
                    final float w = measureRun(segStart, j, j, runIsRtl, fmi);
                    final float w = measureRun(segStart, j, j, runIsRtl, fmi, null, 0);
                    h += advance ? w : -w;

                    final float baseh = advance ? oldh : h;
                    FontMetricsInt crtfmi = advance ? fmi : null;
                    for (int offset = segStart; offset <= j && offset <= mLen; ++offset) {
                        if (target[offset] >= segStart && target[offset] < j) {
                            measurement[offset] =
                                    baseh + measureRun(segStart, offset, j, runIsRtl, crtfmi);
                            measurement[offset] = baseh
                                    + measureRun(segStart, offset, j, runIsRtl, crtfmi, null, 0);
                        }
                    }

@@ -518,14 +628,14 @@ public class TextLine {
            boolean needWidth) {

        if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
            float w = -measureRun(start, limit, limit, runIsRtl, null);
            float w = -measureRun(start, limit, limit, runIsRtl, null, null, 0);
            handleRun(start, limit, limit, runIsRtl, c, null, x + w, top,
                    y, bottom, null, false);
                    y, bottom, null, false, null, 0);
            return w;
        }

        return handleRun(start, limit, limit, runIsRtl, c, null, x, top,
                y, bottom, null, needWidth);
                y, bottom, null, needWidth, null, 0);
    }

    /**
@@ -538,12 +648,15 @@ public class TextLine {
     * @param runIsRtl true if the run is right-to-left
     * @param fmi receives metrics information about the requested
     * run, can be null.
     * @param advances receives the advance information about the requested run, can be null.
     * @param advancesIndex the start index to fill in the advance information.
     * @return the signed width from the start of the run to the leading edge
     * of the character at offset, based on the run (not paragraph) direction
     */
    private float measureRun(int start, int offset, int limit, boolean runIsRtl,
            FontMetricsInt fmi) {
        return handleRun(start, offset, limit, runIsRtl, null, null, 0, 0, 0, 0, fmi, true);
            @Nullable FontMetricsInt fmi, @Nullable float[] advances, int advancesIndex) {
        return handleRun(start, offset, limit, runIsRtl, null, null, 0, 0, 0, 0, fmi, true,
                advances, advancesIndex);
    }

    /**
@@ -562,13 +675,14 @@ public class TextLine {
            int limit, boolean runIsRtl, float x, boolean needWidth) {

        if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
            float w = -measureRun(start, limit, limit, runIsRtl, null);
            handleRun(start, limit, limit, runIsRtl, null, consumer, x + w, 0, 0, 0, null, false);
            float w = -measureRun(start, limit, limit, runIsRtl, null, null, 0);
            handleRun(start, limit, limit, runIsRtl, null, consumer, x + w, 0, 0, 0, null,
                    false, null, 0);
            return w;
        }

        return handleRun(start, limit, limit, runIsRtl, null, consumer, x, 0, 0, 0, null,
                needWidth);
                needWidth, null, 0);
    }


@@ -908,14 +1022,16 @@ public class TextLine {
    }

    private float getRunAdvance(TextPaint wp, int start, int end, int contextStart, int contextEnd,
            boolean runIsRtl, int offset) {
            boolean runIsRtl, int offset, @Nullable float[] advances, int advancesIndex) {
        if (mCharsValid) {
            return wp.getRunAdvance(mChars, start, end, contextStart, contextEnd, runIsRtl, offset);
            return wp.getRunCharacterAdvance(mChars, start, end, contextStart, contextEnd,
                    runIsRtl, offset, advances, advancesIndex);
        } else {
            final int delta = mStart;
            if (mComputed == null) {
                return wp.getRunAdvance(mText, delta + start, delta + end,
                        delta + contextStart, delta + contextEnd, runIsRtl, delta + offset);
            if (mComputed == null || advances != null) {
                return wp.getRunCharacterAdvance(mText, delta + start, delta + end,
                        delta + contextStart, delta + contextEnd, runIsRtl,
                        delta + offset, advances, advancesIndex);
            } else {
                return mComputed.getWidth(start + delta, end + delta);
            }
@@ -940,6 +1056,8 @@ public class TextLine {
     * @param needWidth true if the width of the run is needed
     * @param offset the offset for the purpose of measuring
     * @param decorations the list of locations and paremeters for drawing decorations
     * @param advances receives the advance information about the requested run, can be null.
     * @param advancesIndex the start index to fill in the advance information.
     * @return the signed width of the run based on the run direction; only
     * valid if needWidth is true
     */
@@ -947,7 +1065,8 @@ public class TextLine {
            int contextStart, int contextEnd, boolean runIsRtl,
            Canvas c, TextShaper.GlyphsConsumer consumer, float x, int top, int y, int bottom,
            FontMetricsInt fmi, boolean needWidth, int offset,
            @Nullable ArrayList<DecorationInfo> decorations) {
            @Nullable ArrayList<DecorationInfo> decorations,
            @Nullable float[] advances, int advancesIndex) {

        if (mIsJustifying) {
            wp.setWordSpacing(mAddedWidthForJustify);
@@ -967,7 +1086,8 @@ public class TextLine {
        final int numDecorations = decorations == null ? 0 : decorations.size();
        if (needWidth || ((c != null || consumer != null) && (wp.bgColor != 0
                || numDecorations != 0 || runIsRtl))) {
            totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset);
            totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset,
                    advances, advancesIndex);
        }

        final float leftX, rightX;
@@ -1009,10 +1129,10 @@ public class TextLine {

                    final int decorationStart = Math.max(info.start, start);
                    final int decorationEnd = Math.min(info.end, offset);
                    float decorationStartAdvance = getRunAdvance(
                            wp, start, end, contextStart, contextEnd, runIsRtl, decorationStart);
                    float decorationEndAdvance = getRunAdvance(
                            wp, start, end, contextStart, contextEnd, runIsRtl, decorationEnd);
                    float decorationStartAdvance = getRunAdvance(wp, start, end, contextStart,
                            contextEnd, runIsRtl, decorationStart, null, 0);
                    float decorationEndAdvance = getRunAdvance(wp, start, end, contextStart,
                            contextEnd, runIsRtl, decorationEnd, null, 0);
                    final float decorationXLeft, decorationXRight;
                    if (runIsRtl) {
                        decorationXLeft = rightX - decorationEndAdvance;
@@ -1179,19 +1299,27 @@ public class TextLine {
     * @param bottom the bottom of the line
     * @param fmi receives metrics information, can be null
     * @param needWidth true if the width is required
     * @param advances receives the advance information about the requested run, can be null.
     * @param advancesIndex the start index to fill in the advance information.
     * @return the signed width of the run based on the run direction; only
     * valid if needWidth is true
     */
    private float handleRun(int start, int measureLimit,
            int limit, boolean runIsRtl, Canvas c,
            TextShaper.GlyphsConsumer consumer, float x, int top, int y,
            int bottom, FontMetricsInt fmi, boolean needWidth) {
            int bottom, FontMetricsInt fmi, boolean needWidth,
            @Nullable float[] advances, int advancesIndex) {

        if (measureLimit < start || measureLimit > limit) {
            throw new IndexOutOfBoundsException("measureLimit (" + measureLimit + ") is out of "
                    + "start (" + start + ") and limit (" + limit + ") bounds");
        }

        if (advances != null && advances.length - advancesIndex < measureLimit - start) {
            throw new IndexOutOfBoundsException("advances doesn't have enough space to receive the "
                    + "result");
        }

        // Case of an empty line, make sure we update fmi according to mPaint
        if (start == measureLimit) {
            final TextPaint wp = mWorkPaint;
@@ -1218,7 +1346,7 @@ public class TextLine {
            wp.setStartHyphenEdit(adjustStartHyphenEdit(start, wp.getStartHyphenEdit()));
            wp.setEndHyphenEdit(adjustEndHyphenEdit(limit, wp.getEndHyphenEdit()));
            return handleText(wp, start, limit, start, limit, runIsRtl, c, consumer, x, top,
                    y, bottom, fmi, needWidth, measureLimit, null);
                    y, bottom, fmi, needWidth, measureLimit, null, advances, advancesIndex);
        }

        // Shaping needs to take into account context up to metric boundaries,
@@ -1257,8 +1385,16 @@ public class TextLine {
            }

            if (replacement != null) {
                x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y,
                        bottom, fmi, needWidth || mlimit < measureLimit);
                final float width = handleReplacement(replacement, wp, i, mlimit, runIsRtl, c,
                        x, top, y, bottom, fmi, needWidth || mlimit < measureLimit);
                x += width;
                if (advances != null) {
                    // For replacement, the entire width is assigned to the first character.
                    advances[advancesIndex + i - start] = runIsRtl ? -width : width;
                    for (int j = i + 1; j < mlimit; ++j) {
                        advances[advancesIndex + j - start] = 0.0f;
                    }
                }
                continue;
            }

@@ -1300,7 +1436,8 @@ public class TextLine {
                            adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit()));
                    x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c,
                            consumer, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
                            Math.min(activeEnd, mlimit), mDecorations);
                            Math.min(activeEnd, mlimit), mDecorations,
                            advances, advancesIndex + activeStart - start);

                    activeStart = j;
                    activePaint.set(wp);
@@ -1327,7 +1464,8 @@ public class TextLine {
                    adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit()));
            x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, consumer, x,
                    top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
                    Math.min(activeEnd, mlimit), mDecorations);
                    Math.min(activeEnd, mlimit), mDecorations,
                    advances, advancesIndex + activeStart - start);
        }

        return x - originalX;
+265 −3

File changed.

Preview size limit exceeded, changes collapsed.

+125 −0
Original line number Diff line number Diff line
@@ -3136,6 +3136,128 @@ public class Paint {
        return result;
    }


    /**
     * Measure the advance of each character within a run of text and also return the cursor
     * position within the run.
     *
     * @see #getRunAdvance(char[], int, int, int, int, boolean, int) for more details.
     *
     * @param text the text to measure. Cannot be null.
     * @param start the index of the start of the range to measure
     * @param end the index + 1 of the end of the range to measure
     * @param contextStart the index of the start of the shaping context
     * @param contextEnd the index + 1 of the end of the shaping context
     * @param isRtl whether the run is in RTL direction
     * @param offset index of caret position
     * @param advances the array that receives the computed character advances
     * @param advancesIndex the start index from which the advances array is filled
     * @return width measurement between start and offset
     * @throws IndexOutOfBoundsException if a) contextStart or contextEnd is out of array's range
     * or contextStart is larger than contextEnd,
     * b) start or end is not within the range [contextStart, contextEnd), or start is larger than
     * end,
     * c) offset is not within the range [start, end),
     * d) advances.length - advanceIndex is smaller than the length of the run, which equals to
     * end - start.
     *
     */
    public float getRunCharacterAdvance(@NonNull char[] text, int start, int end, int contextStart,
            int contextEnd, boolean isRtl, int offset,
            @Nullable float[] advances, int advancesIndex) {
        if (text == null) {
            throw new IllegalArgumentException("text cannot be null");
        }
        if (contextStart < 0 || contextEnd > text.length) {
            throw new IndexOutOfBoundsException("Invalid Context Range: " + contextStart + ", "
                    + contextEnd + " must be in 0, " + text.length);
        }

        if (start < contextStart || contextEnd < end) {
            throw new IndexOutOfBoundsException("Invalid start/end range: " + start + ", " + end
                    + " must be in " + contextStart + ", " + contextEnd);
        }

        if (offset < start || end < offset) {
            throw new IndexOutOfBoundsException("Invalid offset position: " + offset
                    + " must be in " + start + ", " + end);
        }

        if (advances != null && advances.length < advancesIndex - start + end) {
            throw new IndexOutOfBoundsException("Given array doesn't have enough space to receive "
                    + "the result, advances.length: " + advances.length + " advanceIndex: "
                    + advancesIndex + " needed space: " + (offset - start));
        }

        if (end == start) {
            return 0.0f;
        }

        return nGetRunCharacterAdvance(mNativePaint, text, start, end, contextStart, contextEnd,
                isRtl, offset, advances, advancesIndex);
    }

    /**
     * @see #getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)
     *
     * @param text the text to measure. Cannot be null.
     * @param start the index of the start of the range to measure
     * @param end the index + 1 of the end of the range to measure
     * @param contextStart the index of the start of the shaping context
     * @param contextEnd the index + 1 of the end of the shaping context
     * @param isRtl whether the run is in RTL direction
     * @param offset index of caret position
     * @param advances the array that receives the computed character advances
     * @param advancesIndex the start index from which the advances array is filled
     * @return width measurement between start and offset
     * @throws IndexOutOfBoundsException if a) contextStart or contextEnd is out of array's range
     * or contextStart is larger than contextEnd,
     * b) start or end is not within the range [contextStart, contextEnd), or end is larger than
     * start,
     * c) offset is not within the range [start, end),
     * d) advances.length - advanceIndex is smaller than the run length, which equals to
     * end - start.
     */
    public float getRunCharacterAdvance(@NonNull CharSequence text, int start, int end,
            int contextStart, int contextEnd, boolean isRtl, int offset,
            @Nullable float[] advances, int advancesIndex) {
        if (text == null) {
            throw new IllegalArgumentException("text cannot be null");
        }
        if (contextStart < 0 || contextEnd > text.length()) {
            throw new IndexOutOfBoundsException("Invalid Context Range: " + contextStart + ", "
                    + contextEnd + " must be in 0, " + text.length());
        }

        if (start < contextStart || contextEnd < end) {
            throw new IndexOutOfBoundsException("Invalid start/end range: " + start + ", " + end
                    + " must be in " + contextStart + ", " + contextEnd);
        }

        if (offset < start || end < offset) {
            throw new IndexOutOfBoundsException("Invalid offset position: " + offset
                    + " must be in " + start + ", " + end);
        }

        if (advances != null && advances.length < advancesIndex - start + end) {
            throw new IndexOutOfBoundsException("Given array doesn't have enough space to receive "
                    + "the result, advances.length: " + advances.length + " advanceIndex: "
                    + advancesIndex + " needed space: " + (offset - start));
        }

        if (end == start) {
            return 0.0f;
        }

        char[] buf = TemporaryBuffer.obtain(contextEnd - contextStart);
        TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
        final float result = getRunCharacterAdvance(buf, start - contextStart, end - contextStart,
                0, contextEnd - contextStart, isRtl, offset - contextStart,
                advances, advancesIndex);
        TemporaryBuffer.recycle(buf);
        return result;
    }

    /**
     * Get the character offset within the string whose position is closest to the specified
     * horizontal position.
@@ -3247,6 +3369,9 @@ public class Paint {
    private static native boolean nHasGlyph(long paintPtr, int bidiFlags, String string);
    private static native float nGetRunAdvance(long paintPtr, char[] text, int start, int end,
            int contextStart, int contextEnd, boolean isRtl, int offset);
    private static native float nGetRunCharacterAdvance(long paintPtr, char[] text, int start,
            int end, int contextStart, int contextEnd, boolean isRtl, int offset, float[] advances,
            int advancesIndex);
    private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end,
            int contextStart, int contextEnd, boolean isRtl, float advance);
    private static native void nGetFontMetricsIntForText(long paintPtr, char[] text,
+137 −111

File changed.

Preview size limit exceeded, changes collapsed.