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

Commit ca62784f authored by chenjean's avatar chenjean
Browse files

fix(HCT): White outline with white text when text following an wahitespace

Goal:
Ensure the accurate rendering of the background rectangle color(e.g. white text with balck background) for text following an whitespace.

Root Cause:
The text color is updated during the whitespace character processing in determineContrastingBackgroundColor, but the bgPaint update is skipped due to the 'skip draw background on whitespace' condition in onCharacterBounds. Consequently, subsequent text color change checks return "no text color change", resulting in the return of an incorrect bgPaint color.

Solution:
Implement synchronized updates for both the text color and bgPaint.

Bug: 401070918
Flag: com.android.graphics.hwui.flags.high_contrast_text_small_text_rect
Test: manually test on whitespace case (e.g. whitespace + text)
Test: atest core/tests/coretests/src/android/text/LayoutTest.java
Test: atest cts/tests/tests/uirendering/src/android/uirendering/cts/testclasses/TextViewHighContrastTextTests.kt
Change-Id: Icf43fc57755f07b33d47324f5436894ce7dbcfde
parent 09558664
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -1074,15 +1074,15 @@ public abstract class Layout {
                    public void onCharacterBounds(int index, int lineNum, float left, float top,
                            float right, float bottom) {

                        var newBackground = determineContrastingBackgroundColor(index);
                        var hasBgColorChanged = newBackground != bgPaint.getColor();

                        // Skip processing if the character is a space or a tap to avoid
                        // rendering an abrupt, empty rectangle.
                        if (TextLine.isLineEndSpace(mText.charAt(index))) {
                            return;
                        }

                        var newBackground = determineContrastingBackgroundColor(index);
                        var hasBgColorChanged = newBackground != bgPaint.getColor();

                        // To avoid highlighting emoji sequences, we use Extended_Pictgraphs as a
                        // heuristic. Highlighting is skipped based on code points, not glyph type
                        // (text vs. color), so emojis with default text presentation are
+58 −44
Original line number Diff line number Diff line
@@ -1029,51 +1029,16 @@ public class LayoutTest {

    @Test
    @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
    public void highContrastTextEnabled_testWhitespaceText_DrawsBackgroundsWithAdjacentLetters() {
        mTextPaint.setColor(Color.BLACK);
        SpannableString spannedText = new SpannableString("Test\tTap and Space");

        // Set the entire text to white initially
        spannedText.setSpan(
                new ForegroundColorSpan(Color.WHITE),
                /* start= */ 0,
                /* end= */ spannedText.length(),
                Spanned.SPAN_INCLUSIVE_EXCLUSIVE
        );

        // Find the whitespace character and set its color to black
        for (int i = 0; i < spannedText.length(); i++) {
            if (Character.isWhitespace(spannedText.charAt(i))) {
                spannedText.setSpan(
                        new ForegroundColorSpan(Color.BLACK),
                        i,
                        i + 1,
                        Spanned.SPAN_INCLUSIVE_EXCLUSIVE
                );
    public void highContrastTextEnabled_testWhiteSpaceWithinText_drawsSameBackgroundswithText() {
        SpannableString spannedText = new SpannableString("Hello\tWorld !");
        testSpannableStringAppliesAllColorsCorrectly(spannedText);
    }
        }

        Layout layout = new StaticLayout(spannedText, mTextPaint, mWidth,
                mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);

        MockCanvas c = new MockCanvas(/* width= */ 256, /* height= */ 256);
        c.setHighContrastTextEnabled(true);
        layout.draw(
                c,
                /* highlightPaths= */ null,
                /* highlightPaints= */ null,
                /* selectionPath= */ null,
                /* selectionPaint= */ null,
                /* cursorOffsetVertical= */ 0
        );

        List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
        for (int i = 0; i < drawCommands.size(); i++) {
            MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
            if (drawCommand.rect != null) {
                expect.that(removeAlpha(drawCommand.paint.getColor())).isEqualTo(Color.BLACK);
            }
        }
    @Test
    @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
    public void highContrastTextEnabled_testWhiteSpaceAtStart_drawsCorrectBackgroundsOnText() {
        SpannableString spannedText = new SpannableString(" HelloWorld!");
        testSpannableStringAppliesAllColorsCorrectly(spannedText);
    }

    @Test
@@ -1331,5 +1296,54 @@ public class LayoutTest {
                "",
                new boolean[]{false});
    }

    private void testSpannableStringAppliesAllColorsCorrectly(SpannableString spannedText) {
        for (int textColor : new int[] {Color.WHITE, Color.BLACK}) {
            final int contrastingColor = textColor == Color.WHITE ? Color.BLACK : Color.WHITE;
            // Set the paint color to the contrasting color to verify the high contrast text
            // background rect color is correct.
            mTextPaint.setColor(contrastingColor);

            // Set the entire text to test color initially
            spannedText.setSpan(
                    new ForegroundColorSpan(textColor),
                    /* start= */ 0,
                    /* end= */ spannedText.length(),
                    Spanned.SPAN_INCLUSIVE_EXCLUSIVE
            );

            Layout layout = new StaticLayout(spannedText, mTextPaint, mWidth,
                    mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);

            MockCanvas c = new MockCanvas(/* width= */ 256, /* height= */ 256);
            c.setHighContrastTextEnabled(true);
            layout.draw(
                    c,
                    /* highlightPaths= */ null,
                    /* highlightPaints= */ null,
                    /* selectionPath= */ null,
                    /* selectionPaint= */ null,
                    /* cursorOffsetVertical= */ 0
            );

            int numBackgroundsFound = 0;
            List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
            for (int i = 0; i < drawCommands.size(); i++) {
                MockCanvas.DrawCommand drawCommand = drawCommands.get(i);

                if (drawCommand.rect != null) {
                    numBackgroundsFound++;
                    // Verifies the background color of the high-contrast rectangle drawn behind
                    // the text. In high-contrast mode, the background color should contrast with
                    // the text color. 'contrastingColor' represents the expected background color,
                    // which is the inverse of the text color (e.g., if text is white, background
                    // is black, and vice versa).
                    expect.that(removeAlpha(drawCommand.paint.getColor()))
                            .isEqualTo(contrastingColor);
                }
            }
            expect.that(numBackgroundsFound).isLessThan(spannedText.length());
        }
    }
}