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

Commit 3bec1dae authored by Hawkwood Glazier's avatar Hawkwood Glazier Committed by Android (Google) Code Review
Browse files

Merge "Replace spacing hack w/ pnum" into main

parents e4786400 f8f50709
Loading
Loading
Loading
Loading
+64 −61
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import android.graphics.fonts.Font
import android.graphics.fonts.FontVariationAxis
import android.graphics.text.PositionedGlyphs
import android.text.Layout
import android.text.TextDirectionHeuristic
import android.text.TextPaint
import android.text.TextShaper
import android.util.MathUtils
@@ -42,11 +41,7 @@ interface TextInterpolatorListener {
    ): Boolean = false
}

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

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

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

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

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

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

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

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

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

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

        // 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
            }
        }
        listener?.onTotalAdjustmentComputed(paint, glyphs.getAdvance(), xAdjustment)
        return glyphs.offsetX
    }

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

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

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

            override fun onPaintModified(paint: Paint) {
                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) {
@@ -363,7 +327,6 @@ open class SimpleDigitalClockTextView(
        canvas.use {
            digitTranslateAnimator?.apply { canvas.translate(currentTranslation) }
            canvas.translate(getDrawTranslation(interpBounds))
            if (isLayoutRtl()) canvas.translate(interpBounds.width - textBounds.width, 0f)
            textAnimator.draw(canvas)
        }
    }
@@ -621,6 +584,7 @@ open class SimpleDigitalClockTextView(
        this.textStyle = textStyle
        lockScreenPaint.strokeJoin = Paint.Join.ROUND
        lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation)
        lockScreenPaint.fontFeatureSettings = if (isLargeClock) "" else "pnum"
        typeface = lockScreenPaint.typeface
        textStyle.lineHeight?.let { lineHeight = it.roundToInt() }

@@ -692,15 +656,6 @@ open class SimpleDigitalClockTextView(
        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
     * rebase if previous animator is canceled so basePaint will store the state we transition from
@@ -715,9 +670,6 @@ open class SimpleDigitalClockTextView(
            prevTextBounds = 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_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 {
            return ClockAxisStyle(axes.map { (def, value) -> def.tag to value }.toMap())
        }
+6 −3
Original line number Diff line number Diff line
@@ -167,7 +167,8 @@ class TextInterpolatorTest : SysuiTestCase() {
        assertThat(expected.sameAs(actual)).isTrue()
    }

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

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

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

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

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