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

Commit 26fac210 authored by Jean Chen's avatar Jean Chen Committed by Android (Google) Code Review
Browse files

Merge "fix(HCT): White Outline with white text when text following an emoji" into main

parents eb9830b0 bbff6dae
Loading
Loading
Loading
Loading
+8 −5
Original line number Original line Diff line number Diff line
@@ -1066,6 +1066,7 @@ public abstract class Layout {
                lastLine,
                lastLine,
                new CharacterBoundsListener() {
                new CharacterBoundsListener() {
                    int mLastLineNum = -1;
                    int mLastLineNum = -1;
                    int mNumCharactersToSkip = 0;
                    final RectF mLineBackground = new RectF();
                    final RectF mLineBackground = new RectF();


                    @ColorInt int mLastColor = originalTextColor;
                    @ColorInt int mLastColor = originalTextColor;
@@ -1073,16 +1074,14 @@ public abstract class Layout {
                    @Override
                    @Override
                    public void onCharacterBounds(int index, int lineNum, float left, float top,
                    public void onCharacterBounds(int index, int lineNum, float left, float top,
                            float right, float bottom) {
                            float right, float bottom) {

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


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

                        // To avoid highlighting emoji sequences, we use Extended_Pictgraphs as a
                        // To avoid highlighting emoji sequences, we use Extended_Pictgraphs as a
                        // heuristic. Highlighting is skipped based on code points, not glyph type
                        // heuristic. Highlighting is skipped based on code points, not glyph type
                        // (text vs. color), so emojis with default text presentation are
                        // (text vs. color), so emojis with default text presentation are
@@ -1094,9 +1093,13 @@ public abstract class Layout {
                        var isEmoji = Character.isEmojiComponent(codePoint)
                        var isEmoji = Character.isEmojiComponent(codePoint)
                                || Character.isExtendedPictographic(codePoint);
                                || Character.isExtendedPictographic(codePoint);
                        if (isEmoji && !isStandardNumber(index)) {
                        if (isEmoji && !isStandardNumber(index)) {
                            mNumCharactersToSkip = Character.charCount(codePoint) - 1;
                            return;
                            return;
                        }
                        }


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

                        if (lineNum != mLastLineNum || hasBgColorChanged) {
                        if (lineNum != mLastLineNum || hasBgColorChanged) {
                            // Draw what we have so far, then reset the rect and update its color
                            // Draw what we have so far, then reset the rect and update its color
                            drawRect();
                            drawRect();
+1 −1
Original line number Original line Diff line number Diff line
@@ -72,7 +72,7 @@ public class SpanColors {
        mWorkPaint.setColor(finalColor);
        mWorkPaint.setColor(finalColor);
        for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
        for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
            if ((index >= mCharacterStyleSpanSet.spanStarts[k])
            if ((index >= mCharacterStyleSpanSet.spanStarts[k])
                    && (index <= mCharacterStyleSpanSet.spanEnds[k])) {
                    && (index < mCharacterStyleSpanSet.spanEnds[k])) {
                final CharacterStyle span = mCharacterStyleSpanSet.spans[k];
                final CharacterStyle span = mCharacterStyleSpanSet.spans[k];
                span.updateDrawState(mWorkPaint);
                span.updateDrawState(mWorkPaint);


+70 −4
Original line number Original line Diff line number Diff line
@@ -948,10 +948,11 @@ public class LayoutTest {


       Text  |   Background
       Text  |   Background
     ========================
     ========================
        al   |    BW
        a    |    BW
        l    |    WW
        w    |    WW
        w    |    WW
        ei   |    WW
        ei\t |    WW
        \t;  |    WW
        ;    |    BB
        s    |    BB
        s    |    BB
        df   |    BB
        df   |    BB
        s    |    BB
        s    |    BB
@@ -1012,7 +1013,7 @@ public class LayoutTest {
        expect.that(removeAlpha(backgroundCommands.get(3).paint.getColor()))
        expect.that(removeAlpha(backgroundCommands.get(3).paint.getColor()))
                .isEqualTo(Color.WHITE);
                .isEqualTo(Color.WHITE);
        expect.that(removeAlpha(backgroundCommands.get(4).paint.getColor()))
        expect.that(removeAlpha(backgroundCommands.get(4).paint.getColor()))
                .isEqualTo(Color.WHITE);
                .isEqualTo(Color.BLACK);
        expect.that(removeAlpha(backgroundCommands.get(5).paint.getColor()))
        expect.that(removeAlpha(backgroundCommands.get(5).paint.getColor()))
                .isEqualTo(Color.BLACK);
                .isEqualTo(Color.BLACK);
        expect.that(removeAlpha(backgroundCommands.get(6).paint.getColor()))
        expect.that(removeAlpha(backgroundCommands.get(6).paint.getColor()))
@@ -1041,6 +1042,71 @@ public class LayoutTest {
        testSpannableStringAppliesAllColorsCorrectly(spannedText);
        testSpannableStringAppliesAllColorsCorrectly(spannedText);
    }
    }


    @Test
    @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
    public void highContrastTextEnabled_testSingleEmoji_drawsSameBackgrounds() {
        SpannableString spannedText = new SpannableString(" 😀 ");
        testSpannableStringAppliesAllColorsCorrectly(spannedText);
    }

    @Test
    @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
    public void highContrastTextEnabled_testTwoEmojis_drawsSameBackgrounds() {
        SpannableString spannedText = new SpannableString(" 😀😀 ");
        testSpannableStringAppliesAllColorsCorrectly(spannedText);
    }

    @Test
    @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
    public void highContrastTextEnabled_testSingleTextBetweenEmoji_drawsCorrectBackgroundsOnText() {
        // TODO(b/405847642): Find a way to verify emojis at the beginning and end of a string are
        // rendered without rectangles.
        SpannableString spannedText = new SpannableString("😀!😀");
        testSpannableStringAppliesAllColorsCorrectly(spannedText);
    }

    @Test
    @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
    public void highContrastTextEnabled_testEmojiWithinText_drawsSameBackgroundswithText() {
        SpannableString spannedText = new SpannableString("Hello😀World");
        testSpannableStringAppliesAllColorsCorrectly(spannedText);
    }

    @Test
    @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
    public void highContrastTextEnabled_testEmojiWithinTextWithSpace_drawsSameBackgrounds() {
        SpannableString spannedText = new SpannableString("Hello 😀 World");
        testSpannableStringAppliesAllColorsCorrectly(spannedText);
    }

    @Test
    @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
    public void highContrastTextEnabled_testEmojiAtStart_drawsCorrectBackgroundsOnText() {
        SpannableString spannedText = new SpannableString("😀HelloWorld");
        testSpannableStringAppliesAllColorsCorrectly(spannedText);
    }

    @Test
    @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
    public void highContrastTextEnabled_testLeadingEmojiWithSpace_drawsCorrectBackgroundsOnText() {
        SpannableString spannedText = new SpannableString("😀 HelloWorld");
        testSpannableStringAppliesAllColorsCorrectly(spannedText);
    }

    @Test
    @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
    public void highContrastTextEnabled_testEmojiAtEnd_drawsCorrectBackgroundsOnText() {
        SpannableString spannedText = new SpannableString("HelloWorld😀");
        testSpannableStringAppliesAllColorsCorrectly(spannedText);
    }

    @Test
    @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
    public void highContrastTextEnabled_testEmojiAtEndWithSpace_drawsCorrectBackgroundsOnText() {
        SpannableString spannedText = new SpannableString("HelloWorld 😀");
        testSpannableStringAppliesAllColorsCorrectly(spannedText);
    }

    @Test
    @Test
    @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
    @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
    public void highContrastTextEnabled_testRoundedRectSize_belowMinimum_usesMinimumValue() {
    public void highContrastTextEnabled_testRoundedRectSize_belowMinimum_usesMinimumValue() {
+99 −0
Original line number Original line Diff line number Diff line
@@ -22,6 +22,7 @@ import android.graphics.Color;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.Presubmit;
import android.text.style.CharacterStyle;
import android.text.style.ForegroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.ImageSpan;
import android.text.style.ImageSpan;
import android.text.style.UnderlineSpan;
import android.text.style.UnderlineSpan;
@@ -41,6 +42,7 @@ public class SpanColorsTest {
    private final TextPaint mWorkPaint = new TextPaint();
    private final TextPaint mWorkPaint = new TextPaint();
    private SpanColors mSpanColors;
    private SpanColors mSpanColors;
    private SpannableString mSpannedText;
    private SpannableString mSpannedText;
    private SpannableString mSpannedTextWithEmoji;


    @Before
    @Before
    public void setup() {
    public void setup() {
@@ -55,6 +57,13 @@ public class SpanColorsTest {
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        mSpannedText.setSpan(new ForegroundColorSpan(Color.BLUE), 12, 16,
        mSpannedText.setSpan(new ForegroundColorSpan(Color.BLUE), 12, 16,
                Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
                Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        mSpannedTextWithEmoji = new SpannableString("Hello 🫱🏻‍🫲🏾world!");
        mSpannedTextWithEmoji.setSpan(new ForegroundColorSpan(Color.RED), 0, 5,
                Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        mSpannedTextWithEmoji.setSpan(new ForegroundColorSpan(Color.GREEN),
                mSpannedTextWithEmoji.length() - 2, mSpannedTextWithEmoji.length(),
                Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
    }
    }


    @Test
    @Test
@@ -72,9 +81,99 @@ public class SpanColorsTest {
    @Test
    @Test
    public void testMultipleColorSpans() {
    public void testMultipleColorSpans() {
        mSpanColors.init(mWorkPaint, mSpannedText, 0, mSpannedText.length());
        mSpanColors.init(mWorkPaint, mSpannedText, 0, mSpannedText.length());
        assertThat(mSpanColors.getColorAt(0)).isEqualTo(Color.RED);
        assertThat(mSpanColors.getColorAt(1)).isEqualTo(Color.RED);
        assertThat(mSpanColors.getColorAt(2)).isEqualTo(Color.RED);
        assertThat(mSpanColors.getColorAt(2)).isEqualTo(Color.RED);
        assertThat(mSpanColors.getColorAt(3)).isEqualTo(Color.RED);
        assertThat(mSpanColors.getColorAt(4)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(5)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(5)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(6)).isEqualTo(Color.GREEN);
        assertThat(mSpanColors.getColorAt(7)).isEqualTo(Color.GREEN);
        assertThat(mSpanColors.getColorAt(8)).isEqualTo(Color.GREEN);
        assertThat(mSpanColors.getColorAt(8)).isEqualTo(Color.GREEN);
        assertThat(mSpanColors.getColorAt(9)).isEqualTo(Color.GREEN);
        assertThat(mSpanColors.getColorAt(10)).isEqualTo(Color.GREEN);
        assertThat(mSpanColors.getColorAt(11)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(12)).isEqualTo(Color.BLUE);
        assertThat(mSpanColors.getColorAt(13)).isEqualTo(Color.BLUE);
        assertThat(mSpanColors.getColorAt(13)).isEqualTo(Color.BLUE);
        assertThat(mSpanColors.getColorAt(14)).isEqualTo(Color.BLUE);
        assertThat(mSpanColors.getColorAt(15)).isEqualTo(Color.BLUE);
        assertThat(mSpanColors.getColorAt(16)).isEqualTo(SpanColors.NO_COLOR_FOUND);
    }

    @Test
    public void testSingleColorSpanWithEmoji() {
        mSpanColors.init(mWorkPaint, mSpannedTextWithEmoji, 1, 4);
        assertThat(mSpanColors.getColorAt(3)).isEqualTo(Color.RED);
    }

    @Test
    public void testMultipleColorSpansWithEmoji() {
        mSpanColors.init(mWorkPaint, mSpannedTextWithEmoji, 0, mSpannedText.length());

        assertThat(mSpanColors.getColorAt(0)).isEqualTo(Color.RED);
        assertThat(mSpanColors.getColorAt(1)).isEqualTo(Color.RED);
        assertThat(mSpanColors.getColorAt(2)).isEqualTo(Color.RED);
        assertThat(mSpanColors.getColorAt(3)).isEqualTo(Color.RED);
        assertThat(mSpanColors.getColorAt(4)).isEqualTo(Color.RED);
        assertThat(mSpanColors.getColorAt(5)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(6)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(7)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(8)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(9)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(10)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(11)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(12)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(13)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(14)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(15)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(16)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(17)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(18)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(19)).isEqualTo(Color.GREEN);
        assertThat(mSpanColors.getColorAt(20)).isEqualTo(Color.GREEN);
    }

    @Test
    public void testSingleColorSpanWithEmojiAndCharacterStyle() {
        mSpannedTextWithEmoji.setSpan(new CharacterStyle() {
            @Override
            public void updateDrawState(TextPaint ds) {
                ds.setColor(Color.BLACK);
            }
        }, 1, 11, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        mSpanColors.init(mWorkPaint, mSpannedTextWithEmoji, 1, 11);
        assertThat(mSpanColors.getColorAt(6)).isEqualTo(Color.BLACK);
    }

    @Test
    public void testMultipleColorSpansWithEmojiAndCharacterStyle() {
        mSpannedTextWithEmoji.setSpan(new CharacterStyle() {
            @Override
            public void updateDrawState(TextPaint ds) {
                ds.setColor(Color.BLACK);
            }
        }, 1, 11, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        mSpanColors.init(mWorkPaint, mSpannedTextWithEmoji, 0, mSpannedTextWithEmoji.length());
        assertThat(mSpanColors.getColorAt(0)).isEqualTo(Color.RED);
        assertThat(mSpanColors.getColorAt(1)).isEqualTo(Color.BLACK);
        assertThat(mSpanColors.getColorAt(2)).isEqualTo(Color.BLACK);
        assertThat(mSpanColors.getColorAt(3)).isEqualTo(Color.BLACK);
        assertThat(mSpanColors.getColorAt(4)).isEqualTo(Color.BLACK);
        assertThat(mSpanColors.getColorAt(5)).isEqualTo(Color.BLACK);
        assertThat(mSpanColors.getColorAt(6)).isEqualTo(Color.BLACK);
        assertThat(mSpanColors.getColorAt(7)).isEqualTo(Color.BLACK);
        assertThat(mSpanColors.getColorAt(8)).isEqualTo(Color.BLACK);
        assertThat(mSpanColors.getColorAt(9)).isEqualTo(Color.BLACK);
        assertThat(mSpanColors.getColorAt(10)).isEqualTo(Color.BLACK);
        assertThat(mSpanColors.getColorAt(11)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(12)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(13)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(14)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(15)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(16)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(17)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(18)).isEqualTo(SpanColors.NO_COLOR_FOUND);
        assertThat(mSpanColors.getColorAt(19)).isEqualTo(Color.GREEN);
        assertThat(mSpanColors.getColorAt(20)).isEqualTo(Color.GREEN);
    }
    }
}
}