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

Commit 203ffaa1 authored by Roozbeh Pournader's avatar Roozbeh Pournader
Browse files

Don't let UnderlineSpans affect text width

Previously, text that was partially annotated with an UnderlineSpan
could have a different width than text without it, since underlined
text was separated into a separate piece of text and was measured
separately, causing kerning at the span boundaries to disappear.

Change-Id: I118de8524c500fb6a4b05b1bf65fe93dc67f204c
Bug: 32907446
Test: cts-tradefed run cts-dev --module CtsTextTestCases
Test: manual
parent d9f25618
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -98,7 +98,18 @@ public class SpanSet<E> {
     * Similar to {@link Spanned#nextSpanTransition(int, int, Class)}
     */
    int getNextTransition(int start, int limit) {
        return getNextTransitionSkipping(null, start, limit);
    }

    /**
     * Similar to {@link #getNextTransition(int, int)}, but skipping over spans with the exact class
     * provided.
     */
    int getNextTransitionSkipping(Class skip, int start, int limit) {
        for (int i = 0; i < numberOfSpans; i++) {
            if (spans[i].getClass() == skip) {
                continue;
            }
            final int spanStart = spanStarts[i];
            final int spanEnd = spanEnds[i];
            if (spanStart > start && spanStart < limit) limit = spanStart;
+65 −28
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ import android.text.Layout.TabStops;
import android.text.style.CharacterStyle;
import android.text.style.MetricAffectingSpan;
import android.text.style.ReplacementSpan;
import android.text.style.UnderlineSpan;
import android.util.IntArray;
import android.util.Log;

import com.android.internal.util.ArrayUtils;
@@ -66,6 +68,8 @@ class TextLine {
    private final SpanSet<ReplacementSpan> mReplacementSpanSpanSet =
            new SpanSet<ReplacementSpan>(ReplacementSpan.class);

    private final IntArray mUnderlines = new IntArray();

    private static final TextLine[] sCached = new TextLine[3];

    /**
@@ -695,6 +699,37 @@ class TextLine {
        fmi.leading = Math.max(fmi.leading, previousLeading);
    }

    private static void drawUnderline(TextPaint wp, Canvas c, int color, float thickness,
            float xstart, float xend, int baseline) {
        // kStdUnderline_Offset = 1/9, defined in SkTextFormatParams.h
        final float underlineTop = baseline + wp.baselineShift + (1.0f / 9.0f) * wp.getTextSize();

        final int previousColor = wp.getColor();
        final Paint.Style previousStyle = wp.getStyle();
        final boolean previousAntiAlias = wp.isAntiAlias();

        wp.setStyle(Paint.Style.FILL);
        wp.setAntiAlias(true);

        wp.setColor(color);
        c.drawRect(xstart, underlineTop, xend, underlineTop + thickness, wp);

        wp.setStyle(previousStyle);
        wp.setColor(previousColor);
        wp.setAntiAlias(previousAntiAlias);
    }

    private float getRunAdvance(TextPaint wp, int start, int end, int contextStart, int contextEnd,
            boolean runIsRtl, int offset) {
        if (mCharsValid) {
            return wp.getRunAdvance(mChars, start, end, contextStart, contextEnd, runIsRtl, offset);
        } else {
            final int delta = mStart;
            return wp.getRunAdvance(mText, delta + start, delta + end,
                    delta + contextStart, delta + contextEnd, runIsRtl, delta + offset);
        }
    }

    /**
     * Utility function for measuring and rendering text.  The text must
     * not include a tab.
@@ -734,14 +769,7 @@ class TextLine {
        float ret = 0;

        if (needWidth || (c != null && (wp.bgColor != 0 || wp.underlineColor != 0 || runIsRtl))) {
            if (mCharsValid) {
                ret = wp.getRunAdvance(mChars, start, end, contextStart, contextEnd,
                        runIsRtl, offset);
            } else {
                int delta = mStart;
                ret = wp.getRunAdvance(mText, delta + start, delta + end,
                        delta + contextStart, delta + contextEnd, runIsRtl, delta + offset);
            }
            ret = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset);
        }

        if (c != null) {
@@ -762,22 +790,23 @@ class TextLine {
            }

            if (wp.underlineColor != 0) {
                // kStdUnderline_Offset = 1/9, defined in SkTextFormatParams.h
                float underlineTop = y + wp.baselineShift + (1.0f / 9.0f) * wp.getTextSize();

                int previousColor = wp.getColor();
                Paint.Style previousStyle = wp.getStyle();
                boolean previousAntiAlias = wp.isAntiAlias();

                wp.setStyle(Paint.Style.FILL);
                wp.setAntiAlias(true);

                wp.setColor(wp.underlineColor);
                c.drawRect(x, underlineTop, x + ret, underlineTop + wp.underlineThickness, wp);
                drawUnderline(wp, c, wp.underlineColor, wp.underlineThickness, x, x + ret, y);
            }

                wp.setStyle(previousStyle);
                wp.setColor(previousColor);
                wp.setAntiAlias(previousAntiAlias);
            final int numUnderlines = mUnderlines.size();
            if (numUnderlines != 0) {
                // kStdUnderline_Thickness = 1/18, defined in SkTextFormatParams.h
                final float thickness = (1.0f / 18.0f) * wp.getTextSize();
                for (int i = 0; i < numUnderlines; i += 2) {
                    final int underlineStart = Math.max(mUnderlines.get(i), start);
                    final int underlineEnd = Math.min(mUnderlines.get(i + 1), offset);
                    final float underlineXStart = getRunAdvance(
                            wp, start, end, contextStart, contextEnd, runIsRtl, underlineStart);
                    final float underlineXEnd = getRunAdvance(
                            wp, start, end, contextStart, contextEnd, runIsRtl, underlineEnd);
                    drawUnderline(wp, c, wp.getColor(), thickness,
                            underlineXStart, underlineXEnd, y);
                }
            }

            drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
@@ -950,20 +979,28 @@ class TextLine {
                continue;
            }

            mUnderlines.clear();
            for (int j = i, jnext; j < mlimit; j = jnext) {
                jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + inext) -
                        mStart;
                jnext = mCharacterStyleSpanSet.getNextTransitionSkipping(
                            UnderlineSpan.class, mStart + j, mStart + inext
                        ) - mStart;
                int offset = Math.min(jnext, mlimit);

                wp.set(mPaint);
                for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
                    final int spanStart = mCharacterStyleSpanSet.spanStarts[k];
                    final int spanEnd = mCharacterStyleSpanSet.spanEnds[k];
                    // Intentionally using >= and <= as explained above
                    if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + offset) ||
                            (mCharacterStyleSpanSet.spanEnds[k] <= mStart + j)) continue;
                    if ((spanStart >= mStart + offset) || (spanEnd <= mStart + j)) continue;

                    CharacterStyle span = mCharacterStyleSpanSet.spans[k];
                    if (span.getClass() == UnderlineSpan.class) {
                        mUnderlines.add(spanStart);
                        mUnderlines.add(spanEnd);
                    } else {
                        span.updateDrawState(wp);
                    }
                }

                wp.setHyphenEdit(adjustHyphenEdit(j, jnext, wp.getHyphenEdit()));