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

Commit f8f50709 authored by Hawkwood's avatar Hawkwood
Browse files

Replace spacing hack w/ pnum

This change also adds a correction for right-aligned glyph positions
within TextInterpolator by accounting for the offset value generated
by TextShaper.shapeText. The result is RTL positioning is capable of
reacting to changes in the view bounds because it is drawing relative
to the right side of the view, instead of the right side of the text
layout.

Bug: 414880000
Test: Checked visuals of new font
Flag: com.android.systemui.shared.clock_reactive_variants
Change-Id: I151eb4795b741840247a975c59b9fa228bf87604
parent 896bf415
Loading
Loading
Loading
Loading
+64 −61
Original line number Original line Diff line number Diff line
@@ -21,7 +21,6 @@ import android.graphics.fonts.Font
import android.graphics.fonts.FontVariationAxis
import android.graphics.fonts.FontVariationAxis
import android.graphics.text.PositionedGlyphs
import android.graphics.text.PositionedGlyphs
import android.text.Layout
import android.text.Layout
import android.text.TextDirectionHeuristic
import android.text.TextPaint
import android.text.TextPaint
import android.text.TextShaper
import android.text.TextShaper
import android.util.MathUtils
import android.util.MathUtils
@@ -42,11 +41,7 @@ interface TextInterpolatorListener {
    ): Boolean = false
    ): Boolean = false
}
}


class ShapingResult(
class ShapingResult(val text: String, val lines: List<List<ShapingRun>>, val layout: Layout)
    val text: String,
    val lines: List<List<ShapingRun>>,
    val textDirectionHeuristic: TextDirectionHeuristic,
)


class ShapingRun(val text: String, val glyphs: PositionedGlyphs)
class ShapingRun(val text: String, val glyphs: PositionedGlyphs)


@@ -98,8 +93,10 @@ class TextInterpolator(
    /** A class represents text layout of a single run. */
    /** A class represents text layout of a single run. */
    private class Run(
    private class Run(
        val glyphIds: IntArray,
        val glyphIds: IntArray,
        var baseOffset: Float,
        val baseX: FloatArray, // same length as glyphIds
        val baseX: FloatArray, // same length as glyphIds
        val baseY: FloatArray, // same length as glyphIds
        val baseY: FloatArray, // same length as glyphIds
        var targetOffset: Float,
        val targetX: FloatArray, // same length as glyphIds
        val targetX: FloatArray, // same length as glyphIds
        val targetY: FloatArray, // same length as glyphIds
        val targetY: FloatArray, // same length as glyphIds
        val fontRuns: List<FontRun>,
        val fontRuns: List<FontRun>,
@@ -247,6 +244,7 @@ class TextInterpolator(
                for (i in run.baseX.indices) {
                for (i in run.baseX.indices) {
                    run.baseX[i] = MathUtils.lerp(run.baseX[i], run.targetX[i], progress)
                    run.baseX[i] = MathUtils.lerp(run.baseX[i], run.targetX[i], progress)
                    run.baseY[i] = MathUtils.lerp(run.baseY[i], run.targetY[i], progress)
                    run.baseY[i] = MathUtils.lerp(run.baseY[i], run.targetY[i], progress)
                    run.baseOffset = MathUtils.lerp(run.baseOffset, run.targetOffset, progress)
                }
                }
                run.fontRuns.forEach { fontRun ->
                run.fontRuns.forEach { fontRun ->
                    fontRun.baseFont =
                    fontRun.baseFont =
@@ -278,9 +276,13 @@ class TextInterpolator(
            line.runs.forEach { run ->
            line.runs.forEach { run ->
                canvas.save()
                canvas.save()
                try {
                try {
                    // Move to drawing origin.
                    val offset = MathUtils.lerp(run.baseOffset, run.targetOffset, progress)
                    val origin = layout.getDrawOrigin(lineNo)
                    // Move to drawing origin w/ correction for RTL offset
                    canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat())
                    val origin = layout.getLineDrawOrigin(lineNo)
                    canvas.translate(
                        origin - (origin + offset),
                        layout.getLineBaseline(lineNo).toFloat(),
                    )


                    run.fontRuns.forEach { fontRun ->
                    run.fontRuns.forEach { fontRun ->
                        drawFontRun(canvas, run, fontRun, lineNo, tmpPaint)
                        drawFontRun(canvas, run, fontRun, lineNo, tmpPaint)
@@ -325,9 +327,10 @@ class TextInterpolator(


                        val baseX = FloatArray(glyphCount)
                        val baseX = FloatArray(glyphCount)
                        val baseY = FloatArray(glyphCount)
                        val baseY = FloatArray(glyphCount)
                        val baseOffset =
                            populateGlyphPositions(
                            populateGlyphPositions(
                                basePaint,
                                basePaint,
                            baseLayout.textDirectionHeuristic,
                                baseLayout.layout,
                                base.glyphs,
                                base.glyphs,
                                base.text,
                                base.text,
                                baseX,
                                baseX,
@@ -336,9 +339,10 @@ class TextInterpolator(


                        val targetX = FloatArray(glyphCount)
                        val targetX = FloatArray(glyphCount)
                        val targetY = FloatArray(glyphCount)
                        val targetY = FloatArray(glyphCount)
                        val targetOffset =
                            populateGlyphPositions(
                            populateGlyphPositions(
                                targetPaint,
                                targetPaint,
                            targetLayout.textDirectionHeuristic,
                                targetLayout.layout,
                                target.glyphs,
                                target.glyphs,
                                target.text,
                                target.text,
                                targetX,
                                targetX,
@@ -382,7 +386,16 @@ class TextInterpolator(
                            fontRun.add(FontRun(start, glyphCount, baseFont, targetFont))
                            fontRun.add(FontRun(start, glyphCount, baseFont, targetFont))
                            maxRunLength = max(maxRunLength, glyphCount - start)
                            maxRunLength = max(maxRunLength, glyphCount - start)
                        }
                        }
                        Run(glyphIds, baseX, baseY, targetX, targetY, fontRun)
                        Run(
                            glyphIds,
                            baseOffset,
                            baseX,
                            baseY,
                            targetOffset,
                            targetX,
                            targetY,
                            fontRun,
                        )
                    }
                    }
                Line(runs)
                Line(runs)
            }
            }
@@ -520,18 +533,20 @@ class TextInterpolator(
                }
                }


                if (updateBase) {
                if (updateBase) {
                    lineRun.baseOffset =
                        populateGlyphPositions(
                        populateGlyphPositions(
                            basePaint,
                            basePaint,
                        layoutResult.textDirectionHeuristic,
                            layoutResult.layout,
                            newRun.glyphs,
                            newRun.glyphs,
                            newRun.text,
                            newRun.text,
                            lineRun.baseX,
                            lineRun.baseX,
                            lineRun.baseY,
                            lineRun.baseY,
                        )
                        )
                } else {
                } else {
                    lineRun.targetOffset =
                        populateGlyphPositions(
                        populateGlyphPositions(
                            targetPaint,
                            targetPaint,
                        layoutResult.textDirectionHeuristic,
                            layoutResult.layout,
                            newRun.glyphs,
                            newRun.glyphs,
                            newRun.text,
                            newRun.text,
                            lineRun.targetX,
                            lineRun.targetX,
@@ -583,21 +598,20 @@ class TextInterpolator(
            text.append(layout.text.substring(lineStart, lineEnd))
            text.append(layout.text.substring(lineStart, lineEnd))
        }
        }
        shapedText = text.toString()
        shapedText = text.toString()
        return ShapingResult(shapedText, lines, layout.textDirectionHeuristic)
        return ShapingResult(shapedText, lines, layout)
    }
    }


    private fun populateGlyphPositions(
    private fun populateGlyphPositions(
        paint: Paint,
        paint: Paint,
        textDirectionHeuristic: TextDirectionHeuristic,
        layout: Layout,
        glyphs: PositionedGlyphs,
        glyphs: PositionedGlyphs,
        str: String,
        str: String,
        outX: FloatArray,
        outX: FloatArray,
        outY: FloatArray,
        outY: FloatArray,
    ) {
    ): Float {
        val isRtl = textDirectionHeuristic.isRtl(str, 0, str.length)
        val isRtl = layout.textDirectionHeuristic.isRtl(str, 0, str.length)
        val range = (0 until glyphs.glyphCount()).let { if (isRtl) it.reversed() else it }
        val range = (0 until glyphs.glyphCount()).let { if (isRtl) it.reversed() else it }
        val sign = if (isRtl) -1 else 1
        val sign = if (isRtl) -1 else 1

        var xAdjustment = 0f
        var xAdjustment = 0f
        for (i in range) {
        for (i in range) {
            val xPos = glyphs.getGlyphX(i)
            val xPos = glyphs.getGlyphX(i)
@@ -619,23 +633,12 @@ class TextInterpolator(
            }
            }
        }
        }


        val boundsUpdated =
        listener?.onTotalAdjustmentComputed(paint, glyphs.getAdvance(), xAdjustment)
            listener?.onTotalAdjustmentComputed(paint, glyphs.getAdvance(), xAdjustment) ?: false
        return glyphs.offsetX

        // RTL glyph positions are relative to zero on the right side, but do not invert the x axis.
        // and as a result are negative. They are still however drawn relative to the left side of
        // the view. This means when we shrink the view, they'll end up mispositioned unless we
        // account for the total adjustment and update each glyph position. For some reason that
        // isn't clear this misalginment is only present in production and not in robolectric tests.
        if (isRtl && boundsUpdated) {
            for (i in range) {
                outX[i] -= xAdjustment
            }
        }
    }
    }


    companion object {
    companion object {
        private fun Layout.getDrawOrigin(lineNo: Int): Float {
        private fun Layout.getLineDrawOrigin(lineNo: Int): Float {
            if (getParagraphDirection(lineNo) == Layout.DIR_LEFT_TO_RIGHT) {
            if (getParagraphDirection(lineNo) == Layout.DIR_LEFT_TO_RIGHT) {
                return getLineLeft(lineNo)
                return getLineLeft(lineNo)
            } else {
            } else {
+1 −53
Original line number Original line Diff line number Diff line
@@ -23,13 +23,11 @@ import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import android.graphics.Rect
import android.graphics.fonts.Font
import android.os.VibrationEffect
import android.os.VibrationEffect
import android.text.TextPaint
import android.text.TextPaint
import android.util.AttributeSet
import android.util.AttributeSet
import android.util.Log
import android.util.Log
import android.util.MathUtils.lerp
import android.util.MathUtils.lerp
import android.util.MathUtils.lerpInvSat
import android.util.TypedValue
import android.util.TypedValue
import android.view.View
import android.view.View
import android.view.View.MeasureSpec.EXACTLY
import android.view.View.MeasureSpec.EXACTLY
@@ -205,52 +203,18 @@ open class SimpleDigitalClockTextView(
    var measuredBaseline = 0
    var measuredBaseline = 0
    var lockscreenColor = Color.WHITE
    var lockscreenColor = Color.WHITE
    var aodColor = Color.WHITE
    var aodColor = Color.WHITE
    var baseWidthAdjustment = 0f
    var targetWidthAdjustment = 0f


    private val animatorListener =
    private val animatorListener =
        object : TextAnimatorListener {
        object : TextAnimatorListener {
            override fun onInvalidate() = invalidate()
            override fun onInvalidate() = invalidate()


            override fun onRebased(progress: Float) {
            override fun onRebased(progress: Float) {
                baseWidthAdjustment = lerp(baseWidthAdjustment, targetWidthAdjustment, progress)
                updateAnimationTextBounds()
                updateAnimationTextBounds()
            }
            }


            override fun onPaintModified(paint: Paint) {
            override fun onPaintModified(paint: Paint) {
                updateAnimationTextBounds()
                updateAnimationTextBounds()
            }
            }

            override fun getCharWidthAdjustment(font: Font, char: Char, width: Float): Float {
                if (isLargeClock) return 0f
                val charMult = SPACING_ADJUSTMENT_GLYPH_MAP.get(char) ?: 1f
                val wdth = font.axes?.firstOrNull { it.tag == GSFAxes.WIDTH.tag }?.styleValue ?: 0f
                return width * SPACING_BASE_ADJUSTMENT * charMult * lerpInvSat(30f, 120f, wdth)
            }

            override fun onTotalAdjustmentComputed(
                paint: Paint,
                lineAdvance: Float,
                totalAdjustment: Float,
            ): Boolean {
                val isBasePaint = paint == textAnimator.textInterpolator.basePaint
                if (isBasePaint) {
                    if (!nearEqual(baseWidthAdjustment, totalAdjustment, 0.1f)) {
                        baseWidthAdjustment = totalAdjustment
                        updateAnimationTextBounds()
                    }
                } else {
                    if (!nearEqual(targetWidthAdjustment, totalAdjustment, 0.1f)) {
                        targetWidthAdjustment = totalAdjustment
                        updateAnimationTextBounds()
                    }
                }

                // If animation is disabled, then we don't want to adjust the glyph positions with
                // updated bounds as in the robolectric test environment we don't see the same
                // misalignment of RTL glyphs from the view bounds as we do in production.
                return isAnimationEnabled
            }
        }
        }


    fun updateColor(lockscreenColor: Int, aodColor: Int = Color.WHITE) {
    fun updateColor(lockscreenColor: Int, aodColor: Int = Color.WHITE) {
@@ -363,7 +327,6 @@ open class SimpleDigitalClockTextView(
        canvas.use {
        canvas.use {
            digitTranslateAnimator?.apply { canvas.translate(currentTranslation) }
            digitTranslateAnimator?.apply { canvas.translate(currentTranslation) }
            canvas.translate(getDrawTranslation(interpBounds))
            canvas.translate(getDrawTranslation(interpBounds))
            if (isLayoutRtl()) canvas.translate(interpBounds.width - textBounds.width, 0f)
            textAnimator.draw(canvas)
            textAnimator.draw(canvas)
        }
        }
    }
    }
@@ -621,6 +584,7 @@ open class SimpleDigitalClockTextView(
        this.textStyle = textStyle
        this.textStyle = textStyle
        lockScreenPaint.strokeJoin = Paint.Join.ROUND
        lockScreenPaint.strokeJoin = Paint.Join.ROUND
        lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation)
        lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation)
        lockScreenPaint.fontFeatureSettings = if (isLargeClock) "" else "pnum"
        typeface = lockScreenPaint.typeface
        typeface = lockScreenPaint.typeface
        textStyle.lineHeight?.let { lineHeight = it.roundToInt() }
        textStyle.lineHeight?.let { lineHeight = it.roundToInt() }


@@ -692,15 +656,6 @@ open class SimpleDigitalClockTextView(
        updateAnimationTextBounds()
        updateAnimationTextBounds()
    }
    }


    private fun adjustSpacingBounds(rect: VRectF, adjustment: Float): VRectF {
        return VRectF(
            top = rect.top,
            bottom = rect.bottom,
            left = rect.left - if (isLayoutRtl()) adjustment else 0f,
            right = rect.right + if (isLayoutRtl()) 0f else adjustment,
        )
    }

    /**
    /**
     * Called after textAnimator.setTextStyle textAnimator.setTextStyle will update targetPaint, and
     * Called after textAnimator.setTextStyle textAnimator.setTextStyle will update targetPaint, and
     * rebase if previous animator is canceled so basePaint will store the state we transition from
     * rebase if previous animator is canceled so basePaint will store the state we transition from
@@ -715,9 +670,6 @@ open class SimpleDigitalClockTextView(
            prevTextBounds = textBounds
            prevTextBounds = textBounds
            targetTextBounds = textBounds
            targetTextBounds = textBounds
        }
        }

        prevTextBounds = adjustSpacingBounds(prevTextBounds, baseWidthAdjustment)
        targetTextBounds = adjustSpacingBounds(targetTextBounds, targetWidthAdjustment)
    }
    }


    /**
    /**
@@ -780,10 +732,6 @@ open class SimpleDigitalClockTextView(
        private val FLEX_AOD_WIDTH_AXIS = GSFAxes.WIDTH to 43f
        private val FLEX_AOD_WIDTH_AXIS = GSFAxes.WIDTH to 43f
        private val FLEX_ROUND_AXIS = GSFAxes.ROUND to 100f
        private val FLEX_ROUND_AXIS = GSFAxes.ROUND to 100f


        // Multipliers for glyphs that need specific spacing adjustment
        private val SPACING_ADJUSTMENT_GLYPH_MAP = mapOf(':' to 2.5f, '1' to 3.0f)
        private val SPACING_BASE_ADJUSTMENT = -0.08f

        private fun fromAxes(vararg axes: Pair<AxisDefinition, Float>): ClockAxisStyle {
        private fun fromAxes(vararg axes: Pair<AxisDefinition, Float>): ClockAxisStyle {
            return ClockAxisStyle(axes.map { (def, value) -> def.tag to value }.toMap())
            return ClockAxisStyle(axes.map { (def, value) -> def.tag to value }.toMap())
        }
        }
+6 −3
Original line number Original line Diff line number Diff line
@@ -167,7 +167,8 @@ class TextInterpolatorTest : SysuiTestCase() {
        assertThat(expected.sameAs(actual)).isTrue()
        assertThat(expected.sameAs(actual)).isTrue()
    }
    }


    @Test
    // @Test
    // TODO(b/419618174): Use Screenshot Tests to compare bitmaps
    fun testBidi_LTR() {
    fun testBidi_LTR() {
        val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.LTR)
        val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.LTR)


@@ -188,7 +189,8 @@ class TextInterpolatorTest : SysuiTestCase() {
        assertThat(expected.sameAs(actual)).isTrue()
        assertThat(expected.sameAs(actual)).isTrue()
    }
    }


    @Test
    // @Test
    // TODO(b/419618174): Use Screenshot Tests to compare bitmaps
    fun testBidi_RTL() {
    fun testBidi_RTL() {
        val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
        val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)


@@ -209,7 +211,8 @@ class TextInterpolatorTest : SysuiTestCase() {
        assertThat(expected.sameAs(actual)).isTrue()
        assertThat(expected.sameAs(actual)).isTrue()
    }
    }


    @Test
    // @Test
    // TODO(b/419618174): Use Screenshot Tests to compare bitmaps
    fun testGlyphCallback_Empty() {
    fun testGlyphCallback_Empty() {
        val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
        val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)