Loading packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt +143 −122 Original line number Diff line number Diff line Loading @@ -19,8 +19,9 @@ import android.graphics.Canvas import android.graphics.Paint import android.graphics.fonts.Font import android.graphics.text.PositionedGlyphs import android.graphics.text.TextRunShaper import android.text.Layout import android.text.TextPaint import android.text.TextShaper import android.util.MathUtils import com.android.internal.graphics.ColorUtils import java.lang.Math.max Loading Loading @@ -57,10 +58,10 @@ class TextInterpolator( */ val targetPaint = createDefaultPaint(layout.paint, lines) private fun createDefaultPaint(paint: Paint, lines: Int): ArrayList<Paint> { val paintList = ArrayList<Paint>() private fun createDefaultPaint(paint: TextPaint, lines: Int): ArrayList<TextPaint> { val paintList = ArrayList<TextPaint>() for (i in 0 until lines) paintList.add(Paint(paint)) paintList.add(TextPaint(paint)) return paintList } Loading @@ -79,9 +80,9 @@ class TextInterpolator( } /** * A class represents text layout of a single line. * A class represents text layout of a single run. */ private class Line( private class Run( val glyphIds: IntArray, val baseX: FloatArray, // same length as glyphIds val baseY: FloatArray, // same length as glyphIds Loading @@ -90,11 +91,18 @@ class TextInterpolator( val fontRuns: List<FontRun> ) /** * A class represents text layout of a single line. */ private class Line( val runs: List<Run> ) private var lines = listOf<Line>() private val fontInterpolator = FontInterpolator() // Recycling object for glyph drawing. Will be extended for the longest font run if needed. private val tmpDrawPaints = ArrayList<Paint>() private val tmpDrawPaints = ArrayList<TextPaint>() private var tmpPositionArray = FloatArray(20) /** Loading Loading @@ -215,23 +223,25 @@ class TextInterpolator( } lines.forEach { line -> for (i in line.baseX.indices) { line.baseX[i] = MathUtils.lerp(line.baseX[i], line.targetX[i], progress) line.baseY[i] = MathUtils.lerp(line.baseY[i], line.targetY[i], progress) line.runs.forEach { run -> 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) } line.fontRuns.forEach { run.fontRuns.forEach { it.baseFont = fontInterpolator.lerp(it.baseFont, it.targetFont, progress) } } } progress = 0f } companion object { fun updatePaint(toUpdate: ArrayList<Paint>, newValues: ArrayList<Paint>) { fun updatePaint(toUpdate: ArrayList<TextPaint>, newValues: ArrayList<TextPaint>) { toUpdate.clear() for (paint in newValues) toUpdate.add(Paint(paint)) toUpdate.add(TextPaint(paint)) } } Loading @@ -243,23 +253,25 @@ class TextInterpolator( fun draw(canvas: Canvas) { lerp(basePaint, targetPaint, progress, tmpDrawPaints) lines.forEachIndexed { lineNo, line -> line.runs.forEach { run -> canvas.save() try { // Move to drawing origin. val origin = layout.getDrawOrigin(lineNo) canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat()) line.fontRuns.forEach { run -> run.fontRuns.forEach { fontRun -> if (lineNo >= tmpDrawPaints.size) drawFontRun(canvas, line, run, tmpDrawPaints[0]) drawFontRun(canvas, run, fontRun, tmpDrawPaints[0]) else drawFontRun(canvas, line, run, tmpDrawPaints[lineNo]) drawFontRun(canvas, run, fontRun, tmpDrawPaints[lineNo]) } } finally { canvas.restore() } } } } // Shape text with current paint parameters. private fun shapeText(layout: Layout) { Loading @@ -271,7 +283,9 @@ class TextInterpolator( } var maxRunLength = 0 lines = baseLayout.zip(targetLayout) { base, target -> lines = baseLayout.zip(targetLayout) { baseLine, targetLine -> val runs = baseLine.zip(targetLine) { base, target -> require(base.glyphCount() == target.glyphCount()) { "Inconsistent glyph count at line ${lines.size}" } Loading Loading @@ -328,7 +342,9 @@ class TextInterpolator( fontRun.add(FontRun(start, glyphCount, baseFont, targetFont)) maxRunLength = max(maxRunLength, glyphCount - start) } Line(glyphIds, baseX, baseY, targetX, targetY, fontRun) Run(glyphIds, baseX, baseY, targetX, targetY, fontRun) } Line(runs) } // Update float array used for drawing. Loading @@ -338,7 +354,7 @@ class TextInterpolator( } // Draws single font run. private fun drawFontRun(c: Canvas, line: Line, run: FontRun, paint: Paint) { private fun drawFontRun(c: Canvas, line: Run, run: FontRun, paint: Paint) { var arrayIndex = 0 for (i in run.start until run.end) { tmpPositionArray[arrayIndex++] = Loading @@ -358,7 +374,7 @@ class TextInterpolator( } private fun updatePositionsAndFonts( layoutResult: List<PositionedGlyphs>, layoutResult: List<List<PositionedGlyphs>>, updateBase: Boolean ) { // Update target positions with newly calculated text layout. Loading @@ -366,15 +382,16 @@ class TextInterpolator( "The new layout result has different line count." } lines.zip(layoutResult) { line, newGlyphs -> require(newGlyphs.glyphCount() == line.glyphIds.size) { lines.zip(layoutResult) { line, runs -> line.runs.zip(runs) { lineRun, newGlyphs -> require(newGlyphs.glyphCount() == lineRun.glyphIds.size) { "The new layout has different glyph count." } line.fontRuns.forEach { run -> lineRun.fontRuns.forEach { run -> val newFont = newGlyphs.getFont(run.start) for (i in run.start until run.end) { require(newGlyphs.getGlyphId(run.start) == line.glyphIds[run.start]) { require(newGlyphs.getGlyphId(run.start) == lineRun.glyphIds[run.start]) { "The new layout has different glyph ID at ${run.start}" } require(newFont === newGlyphs.getFont(i)) { Loading @@ -383,10 +400,11 @@ class TextInterpolator( } } // The passing base font and target font is already interpolatable, so just check // new font can be interpolatable with base font. // The passing base font and target font is already interpolatable, so just // check new font can be interpolatable with base font. require(FontInterpolator.canInterpolate(newFont, run.baseFont)) { "New font cannot be interpolated with existing font. $newFont, ${run.baseFont}" "New font cannot be interpolated with existing font. $newFont," + " ${run.baseFont}" } if (updateBase) { Loading @@ -397,14 +415,15 @@ class TextInterpolator( } if (updateBase) { for (i in line.baseX.indices) { line.baseX[i] = newGlyphs.getGlyphX(i) line.baseY[i] = newGlyphs.getGlyphY(i) for (i in lineRun.baseX.indices) { lineRun.baseX[i] = newGlyphs.getGlyphX(i) lineRun.baseY[i] = newGlyphs.getGlyphY(i) } } else { for (i in line.baseX.indices) { line.targetX[i] = newGlyphs.getGlyphX(i) line.targetY[i] = newGlyphs.getGlyphY(i) for (i in lineRun.baseX.indices) { lineRun.targetX[i] = newGlyphs.getGlyphX(i) lineRun.targetY[i] = newGlyphs.getGlyphY(i) } } } } Loading @@ -412,16 +431,16 @@ class TextInterpolator( // Linear interpolate the paint. private fun lerp( from: ArrayList<Paint>, to: ArrayList<Paint>, from: ArrayList<TextPaint>, to: ArrayList<TextPaint>, progress: Float, out: ArrayList<Paint> out: ArrayList<TextPaint> ) { out.clear() // Currently only font size & colors are interpolated. // TODO(172943390): Add other interpolation or support custom interpolator. for (index in from.indices) { val paint = Paint(from[index]) val paint = TextPaint(from[index]) paint.textSize = MathUtils.lerp(from[index].textSize, to[index].textSize, progress) paint.color = ColorUtils.blendARGB(from[index].color, to[index].color, progress) out.add(paint) Loading @@ -429,18 +448,20 @@ class TextInterpolator( } // Shape the text and stores the result to out argument. private fun shapeText(layout: Layout, paints: ArrayList<Paint>): List<PositionedGlyphs> { val out = mutableListOf<PositionedGlyphs>() private fun shapeText( layout: Layout, paints: ArrayList<TextPaint> ): List<List<PositionedGlyphs>> { val out = mutableListOf<List<PositionedGlyphs>>() for (lineNo in 0 until layout.lineCount) { // Shape all lines. val lineStart = layout.getLineStart(lineNo) val count = layout.getLineEnd(lineNo) - lineStart out.add(TextRunShaper.shapeTextRun( layout.text, // Styles are ignored. lineStart, count, // shape range lineStart, count, // shape context = shape range. 0f, 0f, // the layout offset. Not changed. layout.getParagraphDirection(lineNo) == Layout.DIR_RIGHT_TO_LEFT, paints[lineNo])) // Use given paint instead of layout's for style interpolation. val runs = mutableListOf<PositionedGlyphs>() TextShaper.shapeText(layout.text, lineStart, count, layout.textDirectionHeuristic, paints[lineNo]) { _, _, glyphs, _ -> runs.add(glyphs) } out.add(runs) } return out } Loading packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt +2 −3 Original line number Diff line number Diff line Loading @@ -17,7 +17,6 @@ package com.android.keyguard import android.animation.ValueAnimator import android.graphics.Paint import android.testing.AndroidTestingRunner import android.text.Layout import android.text.StaticLayout Loading Loading @@ -53,7 +52,7 @@ class TextAnimatorTest : SysuiTestCase() { val layout = makeLayout("Hello, World", PAINT[0]) val valueAnimator = mock(ValueAnimator::class.java) val textInterpolator = mock(TextInterpolator::class.java) val paint = arrayListOf(mock(Paint::class.java)) val paint = arrayListOf(mock(TextPaint::class.java)) `when`(textInterpolator.targetPaint).thenReturn(paint) val textAnimator = TextAnimator(layout, {}).apply { Loading Loading @@ -85,7 +84,7 @@ class TextAnimatorTest : SysuiTestCase() { val layout = makeLayout("Hello, World", PAINT[0]) val valueAnimator = mock(ValueAnimator::class.java) val textInterpolator = mock(TextInterpolator::class.java) val paint = arrayListOf(mock(Paint::class.java)) val paint = arrayListOf(mock(TextPaint::class.java)) `when`(textInterpolator.targetPaint).thenReturn(paint) val textAnimator = TextAnimator(layout, {}).apply { Loading packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt +58 −11 Original line number Diff line number Diff line Loading @@ -18,11 +18,12 @@ package com.android.keyguard import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Paint import android.testing.AndroidTestingRunner import android.text.Layout import android.text.StaticLayout import android.text.TextPaint import android.text.TextDirectionHeuristic import android.text.TextDirectionHeuristics import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat Loading @@ -31,6 +32,7 @@ import org.junit.runner.RunWith import kotlin.math.ceil private const val TEXT = "Hello, World." private const val BIDI_TEXT = "abc\u05D0\u05D1\u05D2" private const val BMP_WIDTH = 400 private const val BMP_HEIGHT = 300 Loading @@ -38,11 +40,11 @@ private val PAINT = TextPaint().apply { textSize = 32f } private val START_PAINT = arrayListOf<Paint>(TextPaint(PAINT).apply { private val START_PAINT = arrayListOf(TextPaint(PAINT).apply { fontVariationSettings = "'wght' 400" }) private val END_PAINT = arrayListOf<Paint>(TextPaint(PAINT).apply { private val END_PAINT = arrayListOf(TextPaint(PAINT).apply { fontVariationSettings = "'wght' 700" }) Loading @@ -50,9 +52,14 @@ private val END_PAINT = arrayListOf<Paint>(TextPaint(PAINT).apply { @SmallTest class TextInterpolatorTest : SysuiTestCase() { private fun makeLayout(text: String, paint: TextPaint): Layout { private fun makeLayout( text: String, paint: TextPaint, dir: TextDirectionHeuristic = TextDirectionHeuristics.LTR ): Layout { val width = ceil(Layout.getDesiredWidth(text, 0, text.length, paint)).toInt() return StaticLayout.Builder.obtain(text, 0, text.length, paint, width).build() return StaticLayout.Builder.obtain(text, 0, text.length, paint, width) .setTextDirection(dir).build() } @Test Loading @@ -69,7 +76,7 @@ class TextInterpolatorTest : SysuiTestCase() { // Just after created TextInterpolator, it should have 0 progress. assertThat(interp.progress).isEqualTo(0f) val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(TEXT, START_PAINT[0] as TextPaint).toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(TEXT, START_PAINT[0]).toBitmap(BMP_WIDTH, BMP_HEIGHT) assertThat(expected.sameAs(actual)).isTrue() } Loading @@ -87,7 +94,7 @@ class TextInterpolatorTest : SysuiTestCase() { interp.progress = 1f val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(TEXT, END_PAINT[0] as TextPaint).toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(TEXT, END_PAINT[0]).toBitmap(BMP_WIDTH, BMP_HEIGHT) assertThat(expected.sameAs(actual)).isTrue() } Loading @@ -108,9 +115,9 @@ class TextInterpolatorTest : SysuiTestCase() { // end state. interp.progress = 0.5f val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) assertThat(actual.sameAs(makeLayout(TEXT, START_PAINT[0] as TextPaint) assertThat(actual.sameAs(makeLayout(TEXT, START_PAINT[0]) .toBitmap(BMP_WIDTH, BMP_HEIGHT))).isFalse() assertThat(actual.sameAs(makeLayout(TEXT, END_PAINT[0] as TextPaint) assertThat(actual.sameAs(makeLayout(TEXT, END_PAINT[0]) .toBitmap(BMP_WIDTH, BMP_HEIGHT))).isFalse() } Loading @@ -135,10 +142,50 @@ class TextInterpolatorTest : SysuiTestCase() { assertThat(expected.sameAs(actual)).isTrue() } @Test fun testBidi_LTR() { val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.LTR) val interp = TextInterpolator(layout) TextInterpolator.updatePaint(interp.basePaint, START_PAINT) interp.onBasePaintModified() TextInterpolator.updatePaint(interp.targetPaint, END_PAINT) interp.onTargetPaintModified() // Just after created TextInterpolator, it should have 0 progress. assertThat(interp.progress).isEqualTo(0f) val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(BIDI_TEXT, START_PAINT[0], TextDirectionHeuristics.LTR) .toBitmap(BMP_WIDTH, BMP_HEIGHT) assertThat(expected.sameAs(actual)).isTrue() } @Test fun testBidi_RTL() { val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL) val interp = TextInterpolator(layout) TextInterpolator.updatePaint(interp.basePaint, START_PAINT) interp.onBasePaintModified() TextInterpolator.updatePaint(interp.targetPaint, END_PAINT) interp.onTargetPaintModified() // Just after created TextInterpolator, it should have 0 progress. assertThat(interp.progress).isEqualTo(0f) val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(BIDI_TEXT, START_PAINT[0], TextDirectionHeuristics.RTL) .toBitmap(BMP_WIDTH, BMP_HEIGHT) assertThat(expected.sameAs(actual)).isTrue() } } private fun Layout.toBitmap(width: Int, height: Int) = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8).also { draw(Canvas(it)) }!! Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also { draw(Canvas(it)) }!! private fun TextInterpolator.toBitmap(width: Int, height: Int) = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8).also { draw(Canvas(it)) } No newline at end of file Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also { draw(Canvas(it)) } No newline at end of file Loading
packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt +143 −122 Original line number Diff line number Diff line Loading @@ -19,8 +19,9 @@ import android.graphics.Canvas import android.graphics.Paint import android.graphics.fonts.Font import android.graphics.text.PositionedGlyphs import android.graphics.text.TextRunShaper import android.text.Layout import android.text.TextPaint import android.text.TextShaper import android.util.MathUtils import com.android.internal.graphics.ColorUtils import java.lang.Math.max Loading Loading @@ -57,10 +58,10 @@ class TextInterpolator( */ val targetPaint = createDefaultPaint(layout.paint, lines) private fun createDefaultPaint(paint: Paint, lines: Int): ArrayList<Paint> { val paintList = ArrayList<Paint>() private fun createDefaultPaint(paint: TextPaint, lines: Int): ArrayList<TextPaint> { val paintList = ArrayList<TextPaint>() for (i in 0 until lines) paintList.add(Paint(paint)) paintList.add(TextPaint(paint)) return paintList } Loading @@ -79,9 +80,9 @@ class TextInterpolator( } /** * A class represents text layout of a single line. * A class represents text layout of a single run. */ private class Line( private class Run( val glyphIds: IntArray, val baseX: FloatArray, // same length as glyphIds val baseY: FloatArray, // same length as glyphIds Loading @@ -90,11 +91,18 @@ class TextInterpolator( val fontRuns: List<FontRun> ) /** * A class represents text layout of a single line. */ private class Line( val runs: List<Run> ) private var lines = listOf<Line>() private val fontInterpolator = FontInterpolator() // Recycling object for glyph drawing. Will be extended for the longest font run if needed. private val tmpDrawPaints = ArrayList<Paint>() private val tmpDrawPaints = ArrayList<TextPaint>() private var tmpPositionArray = FloatArray(20) /** Loading Loading @@ -215,23 +223,25 @@ class TextInterpolator( } lines.forEach { line -> for (i in line.baseX.indices) { line.baseX[i] = MathUtils.lerp(line.baseX[i], line.targetX[i], progress) line.baseY[i] = MathUtils.lerp(line.baseY[i], line.targetY[i], progress) line.runs.forEach { run -> 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) } line.fontRuns.forEach { run.fontRuns.forEach { it.baseFont = fontInterpolator.lerp(it.baseFont, it.targetFont, progress) } } } progress = 0f } companion object { fun updatePaint(toUpdate: ArrayList<Paint>, newValues: ArrayList<Paint>) { fun updatePaint(toUpdate: ArrayList<TextPaint>, newValues: ArrayList<TextPaint>) { toUpdate.clear() for (paint in newValues) toUpdate.add(Paint(paint)) toUpdate.add(TextPaint(paint)) } } Loading @@ -243,23 +253,25 @@ class TextInterpolator( fun draw(canvas: Canvas) { lerp(basePaint, targetPaint, progress, tmpDrawPaints) lines.forEachIndexed { lineNo, line -> line.runs.forEach { run -> canvas.save() try { // Move to drawing origin. val origin = layout.getDrawOrigin(lineNo) canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat()) line.fontRuns.forEach { run -> run.fontRuns.forEach { fontRun -> if (lineNo >= tmpDrawPaints.size) drawFontRun(canvas, line, run, tmpDrawPaints[0]) drawFontRun(canvas, run, fontRun, tmpDrawPaints[0]) else drawFontRun(canvas, line, run, tmpDrawPaints[lineNo]) drawFontRun(canvas, run, fontRun, tmpDrawPaints[lineNo]) } } finally { canvas.restore() } } } } // Shape text with current paint parameters. private fun shapeText(layout: Layout) { Loading @@ -271,7 +283,9 @@ class TextInterpolator( } var maxRunLength = 0 lines = baseLayout.zip(targetLayout) { base, target -> lines = baseLayout.zip(targetLayout) { baseLine, targetLine -> val runs = baseLine.zip(targetLine) { base, target -> require(base.glyphCount() == target.glyphCount()) { "Inconsistent glyph count at line ${lines.size}" } Loading Loading @@ -328,7 +342,9 @@ class TextInterpolator( fontRun.add(FontRun(start, glyphCount, baseFont, targetFont)) maxRunLength = max(maxRunLength, glyphCount - start) } Line(glyphIds, baseX, baseY, targetX, targetY, fontRun) Run(glyphIds, baseX, baseY, targetX, targetY, fontRun) } Line(runs) } // Update float array used for drawing. Loading @@ -338,7 +354,7 @@ class TextInterpolator( } // Draws single font run. private fun drawFontRun(c: Canvas, line: Line, run: FontRun, paint: Paint) { private fun drawFontRun(c: Canvas, line: Run, run: FontRun, paint: Paint) { var arrayIndex = 0 for (i in run.start until run.end) { tmpPositionArray[arrayIndex++] = Loading @@ -358,7 +374,7 @@ class TextInterpolator( } private fun updatePositionsAndFonts( layoutResult: List<PositionedGlyphs>, layoutResult: List<List<PositionedGlyphs>>, updateBase: Boolean ) { // Update target positions with newly calculated text layout. Loading @@ -366,15 +382,16 @@ class TextInterpolator( "The new layout result has different line count." } lines.zip(layoutResult) { line, newGlyphs -> require(newGlyphs.glyphCount() == line.glyphIds.size) { lines.zip(layoutResult) { line, runs -> line.runs.zip(runs) { lineRun, newGlyphs -> require(newGlyphs.glyphCount() == lineRun.glyphIds.size) { "The new layout has different glyph count." } line.fontRuns.forEach { run -> lineRun.fontRuns.forEach { run -> val newFont = newGlyphs.getFont(run.start) for (i in run.start until run.end) { require(newGlyphs.getGlyphId(run.start) == line.glyphIds[run.start]) { require(newGlyphs.getGlyphId(run.start) == lineRun.glyphIds[run.start]) { "The new layout has different glyph ID at ${run.start}" } require(newFont === newGlyphs.getFont(i)) { Loading @@ -383,10 +400,11 @@ class TextInterpolator( } } // The passing base font and target font is already interpolatable, so just check // new font can be interpolatable with base font. // The passing base font and target font is already interpolatable, so just // check new font can be interpolatable with base font. require(FontInterpolator.canInterpolate(newFont, run.baseFont)) { "New font cannot be interpolated with existing font. $newFont, ${run.baseFont}" "New font cannot be interpolated with existing font. $newFont," + " ${run.baseFont}" } if (updateBase) { Loading @@ -397,14 +415,15 @@ class TextInterpolator( } if (updateBase) { for (i in line.baseX.indices) { line.baseX[i] = newGlyphs.getGlyphX(i) line.baseY[i] = newGlyphs.getGlyphY(i) for (i in lineRun.baseX.indices) { lineRun.baseX[i] = newGlyphs.getGlyphX(i) lineRun.baseY[i] = newGlyphs.getGlyphY(i) } } else { for (i in line.baseX.indices) { line.targetX[i] = newGlyphs.getGlyphX(i) line.targetY[i] = newGlyphs.getGlyphY(i) for (i in lineRun.baseX.indices) { lineRun.targetX[i] = newGlyphs.getGlyphX(i) lineRun.targetY[i] = newGlyphs.getGlyphY(i) } } } } Loading @@ -412,16 +431,16 @@ class TextInterpolator( // Linear interpolate the paint. private fun lerp( from: ArrayList<Paint>, to: ArrayList<Paint>, from: ArrayList<TextPaint>, to: ArrayList<TextPaint>, progress: Float, out: ArrayList<Paint> out: ArrayList<TextPaint> ) { out.clear() // Currently only font size & colors are interpolated. // TODO(172943390): Add other interpolation or support custom interpolator. for (index in from.indices) { val paint = Paint(from[index]) val paint = TextPaint(from[index]) paint.textSize = MathUtils.lerp(from[index].textSize, to[index].textSize, progress) paint.color = ColorUtils.blendARGB(from[index].color, to[index].color, progress) out.add(paint) Loading @@ -429,18 +448,20 @@ class TextInterpolator( } // Shape the text and stores the result to out argument. private fun shapeText(layout: Layout, paints: ArrayList<Paint>): List<PositionedGlyphs> { val out = mutableListOf<PositionedGlyphs>() private fun shapeText( layout: Layout, paints: ArrayList<TextPaint> ): List<List<PositionedGlyphs>> { val out = mutableListOf<List<PositionedGlyphs>>() for (lineNo in 0 until layout.lineCount) { // Shape all lines. val lineStart = layout.getLineStart(lineNo) val count = layout.getLineEnd(lineNo) - lineStart out.add(TextRunShaper.shapeTextRun( layout.text, // Styles are ignored. lineStart, count, // shape range lineStart, count, // shape context = shape range. 0f, 0f, // the layout offset. Not changed. layout.getParagraphDirection(lineNo) == Layout.DIR_RIGHT_TO_LEFT, paints[lineNo])) // Use given paint instead of layout's for style interpolation. val runs = mutableListOf<PositionedGlyphs>() TextShaper.shapeText(layout.text, lineStart, count, layout.textDirectionHeuristic, paints[lineNo]) { _, _, glyphs, _ -> runs.add(glyphs) } out.add(runs) } return out } Loading
packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt +2 −3 Original line number Diff line number Diff line Loading @@ -17,7 +17,6 @@ package com.android.keyguard import android.animation.ValueAnimator import android.graphics.Paint import android.testing.AndroidTestingRunner import android.text.Layout import android.text.StaticLayout Loading Loading @@ -53,7 +52,7 @@ class TextAnimatorTest : SysuiTestCase() { val layout = makeLayout("Hello, World", PAINT[0]) val valueAnimator = mock(ValueAnimator::class.java) val textInterpolator = mock(TextInterpolator::class.java) val paint = arrayListOf(mock(Paint::class.java)) val paint = arrayListOf(mock(TextPaint::class.java)) `when`(textInterpolator.targetPaint).thenReturn(paint) val textAnimator = TextAnimator(layout, {}).apply { Loading Loading @@ -85,7 +84,7 @@ class TextAnimatorTest : SysuiTestCase() { val layout = makeLayout("Hello, World", PAINT[0]) val valueAnimator = mock(ValueAnimator::class.java) val textInterpolator = mock(TextInterpolator::class.java) val paint = arrayListOf(mock(Paint::class.java)) val paint = arrayListOf(mock(TextPaint::class.java)) `when`(textInterpolator.targetPaint).thenReturn(paint) val textAnimator = TextAnimator(layout, {}).apply { Loading
packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt +58 −11 Original line number Diff line number Diff line Loading @@ -18,11 +18,12 @@ package com.android.keyguard import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Paint import android.testing.AndroidTestingRunner import android.text.Layout import android.text.StaticLayout import android.text.TextPaint import android.text.TextDirectionHeuristic import android.text.TextDirectionHeuristics import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat Loading @@ -31,6 +32,7 @@ import org.junit.runner.RunWith import kotlin.math.ceil private const val TEXT = "Hello, World." private const val BIDI_TEXT = "abc\u05D0\u05D1\u05D2" private const val BMP_WIDTH = 400 private const val BMP_HEIGHT = 300 Loading @@ -38,11 +40,11 @@ private val PAINT = TextPaint().apply { textSize = 32f } private val START_PAINT = arrayListOf<Paint>(TextPaint(PAINT).apply { private val START_PAINT = arrayListOf(TextPaint(PAINT).apply { fontVariationSettings = "'wght' 400" }) private val END_PAINT = arrayListOf<Paint>(TextPaint(PAINT).apply { private val END_PAINT = arrayListOf(TextPaint(PAINT).apply { fontVariationSettings = "'wght' 700" }) Loading @@ -50,9 +52,14 @@ private val END_PAINT = arrayListOf<Paint>(TextPaint(PAINT).apply { @SmallTest class TextInterpolatorTest : SysuiTestCase() { private fun makeLayout(text: String, paint: TextPaint): Layout { private fun makeLayout( text: String, paint: TextPaint, dir: TextDirectionHeuristic = TextDirectionHeuristics.LTR ): Layout { val width = ceil(Layout.getDesiredWidth(text, 0, text.length, paint)).toInt() return StaticLayout.Builder.obtain(text, 0, text.length, paint, width).build() return StaticLayout.Builder.obtain(text, 0, text.length, paint, width) .setTextDirection(dir).build() } @Test Loading @@ -69,7 +76,7 @@ class TextInterpolatorTest : SysuiTestCase() { // Just after created TextInterpolator, it should have 0 progress. assertThat(interp.progress).isEqualTo(0f) val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(TEXT, START_PAINT[0] as TextPaint).toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(TEXT, START_PAINT[0]).toBitmap(BMP_WIDTH, BMP_HEIGHT) assertThat(expected.sameAs(actual)).isTrue() } Loading @@ -87,7 +94,7 @@ class TextInterpolatorTest : SysuiTestCase() { interp.progress = 1f val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(TEXT, END_PAINT[0] as TextPaint).toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(TEXT, END_PAINT[0]).toBitmap(BMP_WIDTH, BMP_HEIGHT) assertThat(expected.sameAs(actual)).isTrue() } Loading @@ -108,9 +115,9 @@ class TextInterpolatorTest : SysuiTestCase() { // end state. interp.progress = 0.5f val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) assertThat(actual.sameAs(makeLayout(TEXT, START_PAINT[0] as TextPaint) assertThat(actual.sameAs(makeLayout(TEXT, START_PAINT[0]) .toBitmap(BMP_WIDTH, BMP_HEIGHT))).isFalse() assertThat(actual.sameAs(makeLayout(TEXT, END_PAINT[0] as TextPaint) assertThat(actual.sameAs(makeLayout(TEXT, END_PAINT[0]) .toBitmap(BMP_WIDTH, BMP_HEIGHT))).isFalse() } Loading @@ -135,10 +142,50 @@ class TextInterpolatorTest : SysuiTestCase() { assertThat(expected.sameAs(actual)).isTrue() } @Test fun testBidi_LTR() { val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.LTR) val interp = TextInterpolator(layout) TextInterpolator.updatePaint(interp.basePaint, START_PAINT) interp.onBasePaintModified() TextInterpolator.updatePaint(interp.targetPaint, END_PAINT) interp.onTargetPaintModified() // Just after created TextInterpolator, it should have 0 progress. assertThat(interp.progress).isEqualTo(0f) val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(BIDI_TEXT, START_PAINT[0], TextDirectionHeuristics.LTR) .toBitmap(BMP_WIDTH, BMP_HEIGHT) assertThat(expected.sameAs(actual)).isTrue() } @Test fun testBidi_RTL() { val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL) val interp = TextInterpolator(layout) TextInterpolator.updatePaint(interp.basePaint, START_PAINT) interp.onBasePaintModified() TextInterpolator.updatePaint(interp.targetPaint, END_PAINT) interp.onTargetPaintModified() // Just after created TextInterpolator, it should have 0 progress. assertThat(interp.progress).isEqualTo(0f) val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(BIDI_TEXT, START_PAINT[0], TextDirectionHeuristics.RTL) .toBitmap(BMP_WIDTH, BMP_HEIGHT) assertThat(expected.sameAs(actual)).isTrue() } } private fun Layout.toBitmap(width: Int, height: Int) = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8).also { draw(Canvas(it)) }!! Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also { draw(Canvas(it)) }!! private fun TextInterpolator.toBitmap(width: Int, height: Int) = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8).also { draw(Canvas(it)) } No newline at end of file Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also { draw(Canvas(it)) } No newline at end of file