Loading packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt +4 −0 Original line number Diff line number Diff line Loading @@ -257,6 +257,8 @@ class AnimatableClockView @JvmOverloads constructor( ) } private val glyphFilter: GlyphCallback? = null // Add text animation tweak here. /** * Set text style with an optional animation. * Loading Loading @@ -288,6 +290,7 @@ class AnimatableClockView @JvmOverloads constructor( delay = delay, onAnimationEnd = onAnimationEnd ) textAnimator?.glyphFilter = glyphFilter } else { // when the text animator is set, update its start values onTextAnimatorInitialized = Runnable { Loading @@ -301,6 +304,7 @@ class AnimatableClockView @JvmOverloads constructor( delay = delay, onAnimationEnd = onAnimationEnd ) textAnimator?.glyphFilter = glyphFilter } } } Loading packages/SystemUI/src/com/android/keyguard/TextAnimator.kt +106 −0 Original line number Diff line number Diff line Loading @@ -22,12 +22,14 @@ import android.animation.TimeInterpolator import android.animation.ValueAnimator import android.graphics.Canvas import android.graphics.Typeface import android.graphics.fonts.Font import android.text.Layout import android.util.SparseArray private const val TAG_WGHT = "wght" private const val DEFAULT_ANIMATION_DURATION: Long = 300 typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit /** * This class provides text animation between two styles. * Loading Loading @@ -74,6 +76,59 @@ class TextAnimator( }) } sealed class PositionedGlyph { /** * Mutable X coordinate of the glyph position relative from drawing offset. */ var x: Float = 0f /** * Mutable Y coordinate of the glyph position relative from the baseline. */ var y: Float = 0f /** * Mutable text size of the glyph in pixels. */ var textSize: Float = 0f /** * Mutable color of the glyph. */ var color: Int = 0 /** * Immutable character offset in the text that the current font run start. */ abstract var runStart: Int protected set /** * Immutable run length of the font run. */ abstract var runLength: Int protected set /** * Immutable glyph index of the font run. */ abstract var glyphIndex: Int protected set /** * Immutable font instance for this font run. */ abstract var font: Font protected set /** * Immutable glyph ID for this glyph. */ abstract var glyphId: Int protected set } private val typefaceCache = SparseArray<Typeface?>() fun updateLayout(layout: Layout) { Loading @@ -84,6 +139,57 @@ class TextAnimator( return animator.isRunning } /** * GlyphFilter applied just before drawing to canvas for tweaking positions and text size. * * This callback is called for each glyphs just before drawing the glyphs. This function will * be called with the intrinsic position, size, color, glyph ID and font instance. You can * mutate the position, size and color for tweaking animations. * Do not keep the reference of passed glyph object. The interpolator reuses that object for * avoiding object allocations. * * Details: * The text is drawn with font run units. The font run is a text segment that draws with the * same font. The {@code runStart} and {@code runLimit} is a range of the font run in the text * that current glyph is in. Once the font run is determined, the system will convert characters * into glyph IDs. The {@code glyphId} is the glyph identifier in the font and * {@code glyphIndex} is the offset of the converted glyph array. Please note that the * {@code glyphIndex} is not a character index, because the character will not be converted to * glyph one-by-one. If there are ligatures including emoji sequence, etc, the glyph ID may be * composed from multiple characters. * * Here is an example of font runs: "fin. 終わり" * * Characters : f i n . _ 終 わ り * Code Points: \u0066 \u0069 \u006E \u002E \u0020 \u7D42 \u308F \u308A * Font Runs : <-- Roboto-Regular.ttf --><-- NotoSans-CJK.otf --> * runStart = 0, runLength = 5 runStart = 5, runLength = 3 * Glyph IDs : 194 48 7 8 4367 1039 1002 * Glyph Index: 0 1 2 3 0 1 2 * * In this example, the "fi" is converted into ligature form, thus the single glyph ID is * assigned for two characters, f and i. * * Example: * ``` * private val glyphFilter: GlyphCallback = { glyph, progress -> * val index = glyph.runStart * val i = glyph.glyphIndex * val moveAmount = 1.3f * val sign = (-1 + 2 * ((i + index) % 2)) * val turnProgress = if (progress < .5f) progress / 0.5f else (1.0f - progress) / 0.5f * * // You can modify (x, y) coordinates, textSize and color during animation. * glyph.textSize += glyph.textSize * sign * moveAmount * turnProgress * glyph.y += glyph.y * sign * moveAmount * turnProgress * glyph.x += glyph.x * sign * moveAmount * turnProgress * } * ``` */ var glyphFilter: GlyphCallback? get() = textInterpolator.glyphFilter set(value) { textInterpolator.glyphFilter = value } fun draw(c: Canvas) = textInterpolator.draw(c) /** Loading packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt +75 −14 Original line number Diff line number Diff line Loading @@ -89,8 +89,11 @@ class TextInterpolator( 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 tmpDrawPaint = TextPaint() // Recycling object for glyph drawing and tweaking. private val tmpPaint = TextPaint() private val tmpPaintForGlyph by lazy { TextPaint() } private val tmpGlyph by lazy { MutablePositionedGlyph() } // Will be extended for the longest font run if needed. private var tmpPositionArray = FloatArray(20) /** Loading Loading @@ -206,8 +209,8 @@ class TextInterpolator( } else if (progress == 1f) { basePaint.set(targetPaint) } else { lerp(basePaint, targetPaint, progress, tmpDrawPaint) basePaint.set(tmpDrawPaint) lerp(basePaint, targetPaint, progress, tmpPaint) basePaint.set(tmpPaint) } lines.forEach { line -> Loading @@ -231,7 +234,7 @@ class TextInterpolator( * @param canvas a canvas. */ fun draw(canvas: Canvas) { lerp(basePaint, targetPaint, progress, tmpDrawPaint) lerp(basePaint, targetPaint, progress, tmpPaint) lines.forEachIndexed { lineNo, line -> line.runs.forEach { run -> canvas.save() Loading @@ -241,7 +244,7 @@ class TextInterpolator( canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat()) run.fontRuns.forEach { fontRun -> drawFontRun(canvas, run, fontRun, tmpDrawPaint) drawFontRun(canvas, run, fontRun, tmpPaint) } } finally { canvas.restore() Loading Loading @@ -330,24 +333,82 @@ class TextInterpolator( } } private class MutablePositionedGlyph : TextAnimator.PositionedGlyph() { override var runStart: Int = 0 public set override var runLength: Int = 0 public set override var glyphIndex: Int = 0 public set override lateinit var font: Font public set override var glyphId: Int = 0 public set } var glyphFilter: GlyphCallback? = null // Draws single font run. private fun drawFontRun(c: Canvas, line: Run, run: FontRun, paint: Paint) { var arrayIndex = 0 val font = fontInterpolator.lerp(run.baseFont, run.targetFont, progress) val glyphFilter = glyphFilter if (glyphFilter == null) { for (i in run.start until run.end) { tmpPositionArray[arrayIndex++] = MathUtils.lerp(line.baseX[i], line.targetX[i], progress) tmpPositionArray[arrayIndex++] = MathUtils.lerp(line.baseY[i], line.targetY[i], progress) } c.drawGlyphs(line.glyphIds, run.start, tmpPositionArray, 0, run.length, font, paint) return } tmpGlyph.font = font tmpGlyph.runStart = run.start tmpGlyph.runLength = run.end - run.start tmpPaintForGlyph.set(paint) var prevStart = run.start for (i in run.start until run.end) { tmpGlyph.glyphId = line.glyphIds[i] tmpGlyph.x = MathUtils.lerp(line.baseX[i], line.targetX[i], progress) tmpGlyph.y = MathUtils.lerp(line.baseY[i], line.targetY[i], progress) tmpGlyph.textSize = paint.textSize tmpGlyph.color = paint.color glyphFilter(tmpGlyph, progress) if (tmpGlyph.textSize != paint.textSize || tmpGlyph.color != paint.color) { tmpPaintForGlyph.textSize = tmpGlyph.textSize tmpPaintForGlyph.color = tmpGlyph.color c.drawGlyphs( line.glyphIds, prevStart, tmpPositionArray, 0, i - prevStart, font, tmpPaintForGlyph) prevStart = i arrayIndex = 0 } tmpPositionArray[arrayIndex++] = tmpGlyph.x tmpPositionArray[arrayIndex++] = tmpGlyph.y } c.drawGlyphs( line.glyphIds, run.start, prevStart, tmpPositionArray, 0, run.length, fontInterpolator.lerp(run.baseFont, run.targetFont, progress), paint) run.end - prevStart, font, tmpPaintForGlyph) } private fun updatePositionsAndFonts( Loading packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt +123 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.keyguard import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color import android.graphics.Typeface import android.graphics.fonts.Font import android.graphics.fonts.FontFamily Loading Loading @@ -194,6 +195,128 @@ class TextInterpolatorTest : SysuiTestCase() { assertThat(expected.sameAs(actual)).isTrue() } @Test fun testGlyphCallback_Empty() { val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL) val interp = TextInterpolator(layout).apply { glyphFilter = { glyph, progress -> } } interp.basePaint.set(START_PAINT) interp.onBasePaintModified() interp.targetPaint.set(END_PAINT) interp.onTargetPaintModified() // Just after created TextInterpolator, it should have 0 progress. val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL) .toBitmap(BMP_WIDTH, BMP_HEIGHT) assertThat(expected.sameAs(actual)).isTrue() } @Test fun testGlyphCallback_Xcoordinate() { val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL) val interp = TextInterpolator(layout).apply { glyphFilter = { glyph, progress -> glyph.x += 30f } } interp.basePaint.set(START_PAINT) interp.onBasePaintModified() interp.targetPaint.set(END_PAINT) interp.onTargetPaintModified() // Just after created TextInterpolator, it should have 0 progress. val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL) .toBitmap(BMP_WIDTH, BMP_HEIGHT) // The glyph position was modified by callback, so the bitmap should not be the same. // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps. assertThat(expected.sameAs(actual)).isFalse() } @Test fun testGlyphCallback_Ycoordinate() { val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL) val interp = TextInterpolator(layout).apply { glyphFilter = { glyph, progress -> glyph.y += 30f } } interp.basePaint.set(START_PAINT) interp.onBasePaintModified() interp.targetPaint.set(END_PAINT) interp.onTargetPaintModified() // Just after created TextInterpolator, it should have 0 progress. val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL) .toBitmap(BMP_WIDTH, BMP_HEIGHT) // The glyph position was modified by callback, so the bitmap should not be the same. // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps. assertThat(expected.sameAs(actual)).isFalse() } @Test fun testGlyphCallback_TextSize() { val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL) val interp = TextInterpolator(layout).apply { glyphFilter = { glyph, progress -> glyph.textSize += 10f } } interp.basePaint.set(START_PAINT) interp.onBasePaintModified() interp.targetPaint.set(END_PAINT) interp.onTargetPaintModified() // Just after created TextInterpolator, it should have 0 progress. val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL) .toBitmap(BMP_WIDTH, BMP_HEIGHT) // The glyph position was modified by callback, so the bitmap should not be the same. // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps. assertThat(expected.sameAs(actual)).isFalse() } @Test fun testGlyphCallback_Color() { val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL) val interp = TextInterpolator(layout).apply { glyphFilter = { glyph, progress -> glyph.color = Color.RED } } interp.basePaint.set(START_PAINT) interp.onBasePaintModified() interp.targetPaint.set(END_PAINT) interp.onTargetPaintModified() // Just after created TextInterpolator, it should have 0 progress. val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL) .toBitmap(BMP_WIDTH, BMP_HEIGHT) // The glyph position was modified by callback, so the bitmap should not be the same. // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps. assertThat(expected.sameAs(actual)).isFalse() } } private fun Layout.toBitmap(width: Int, height: Int) = Loading Loading
packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt +4 −0 Original line number Diff line number Diff line Loading @@ -257,6 +257,8 @@ class AnimatableClockView @JvmOverloads constructor( ) } private val glyphFilter: GlyphCallback? = null // Add text animation tweak here. /** * Set text style with an optional animation. * Loading Loading @@ -288,6 +290,7 @@ class AnimatableClockView @JvmOverloads constructor( delay = delay, onAnimationEnd = onAnimationEnd ) textAnimator?.glyphFilter = glyphFilter } else { // when the text animator is set, update its start values onTextAnimatorInitialized = Runnable { Loading @@ -301,6 +304,7 @@ class AnimatableClockView @JvmOverloads constructor( delay = delay, onAnimationEnd = onAnimationEnd ) textAnimator?.glyphFilter = glyphFilter } } } Loading
packages/SystemUI/src/com/android/keyguard/TextAnimator.kt +106 −0 Original line number Diff line number Diff line Loading @@ -22,12 +22,14 @@ import android.animation.TimeInterpolator import android.animation.ValueAnimator import android.graphics.Canvas import android.graphics.Typeface import android.graphics.fonts.Font import android.text.Layout import android.util.SparseArray private const val TAG_WGHT = "wght" private const val DEFAULT_ANIMATION_DURATION: Long = 300 typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit /** * This class provides text animation between two styles. * Loading Loading @@ -74,6 +76,59 @@ class TextAnimator( }) } sealed class PositionedGlyph { /** * Mutable X coordinate of the glyph position relative from drawing offset. */ var x: Float = 0f /** * Mutable Y coordinate of the glyph position relative from the baseline. */ var y: Float = 0f /** * Mutable text size of the glyph in pixels. */ var textSize: Float = 0f /** * Mutable color of the glyph. */ var color: Int = 0 /** * Immutable character offset in the text that the current font run start. */ abstract var runStart: Int protected set /** * Immutable run length of the font run. */ abstract var runLength: Int protected set /** * Immutable glyph index of the font run. */ abstract var glyphIndex: Int protected set /** * Immutable font instance for this font run. */ abstract var font: Font protected set /** * Immutable glyph ID for this glyph. */ abstract var glyphId: Int protected set } private val typefaceCache = SparseArray<Typeface?>() fun updateLayout(layout: Layout) { Loading @@ -84,6 +139,57 @@ class TextAnimator( return animator.isRunning } /** * GlyphFilter applied just before drawing to canvas for tweaking positions and text size. * * This callback is called for each glyphs just before drawing the glyphs. This function will * be called with the intrinsic position, size, color, glyph ID and font instance. You can * mutate the position, size and color for tweaking animations. * Do not keep the reference of passed glyph object. The interpolator reuses that object for * avoiding object allocations. * * Details: * The text is drawn with font run units. The font run is a text segment that draws with the * same font. The {@code runStart} and {@code runLimit} is a range of the font run in the text * that current glyph is in. Once the font run is determined, the system will convert characters * into glyph IDs. The {@code glyphId} is the glyph identifier in the font and * {@code glyphIndex} is the offset of the converted glyph array. Please note that the * {@code glyphIndex} is not a character index, because the character will not be converted to * glyph one-by-one. If there are ligatures including emoji sequence, etc, the glyph ID may be * composed from multiple characters. * * Here is an example of font runs: "fin. 終わり" * * Characters : f i n . _ 終 わ り * Code Points: \u0066 \u0069 \u006E \u002E \u0020 \u7D42 \u308F \u308A * Font Runs : <-- Roboto-Regular.ttf --><-- NotoSans-CJK.otf --> * runStart = 0, runLength = 5 runStart = 5, runLength = 3 * Glyph IDs : 194 48 7 8 4367 1039 1002 * Glyph Index: 0 1 2 3 0 1 2 * * In this example, the "fi" is converted into ligature form, thus the single glyph ID is * assigned for two characters, f and i. * * Example: * ``` * private val glyphFilter: GlyphCallback = { glyph, progress -> * val index = glyph.runStart * val i = glyph.glyphIndex * val moveAmount = 1.3f * val sign = (-1 + 2 * ((i + index) % 2)) * val turnProgress = if (progress < .5f) progress / 0.5f else (1.0f - progress) / 0.5f * * // You can modify (x, y) coordinates, textSize and color during animation. * glyph.textSize += glyph.textSize * sign * moveAmount * turnProgress * glyph.y += glyph.y * sign * moveAmount * turnProgress * glyph.x += glyph.x * sign * moveAmount * turnProgress * } * ``` */ var glyphFilter: GlyphCallback? get() = textInterpolator.glyphFilter set(value) { textInterpolator.glyphFilter = value } fun draw(c: Canvas) = textInterpolator.draw(c) /** Loading
packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt +75 −14 Original line number Diff line number Diff line Loading @@ -89,8 +89,11 @@ class TextInterpolator( 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 tmpDrawPaint = TextPaint() // Recycling object for glyph drawing and tweaking. private val tmpPaint = TextPaint() private val tmpPaintForGlyph by lazy { TextPaint() } private val tmpGlyph by lazy { MutablePositionedGlyph() } // Will be extended for the longest font run if needed. private var tmpPositionArray = FloatArray(20) /** Loading Loading @@ -206,8 +209,8 @@ class TextInterpolator( } else if (progress == 1f) { basePaint.set(targetPaint) } else { lerp(basePaint, targetPaint, progress, tmpDrawPaint) basePaint.set(tmpDrawPaint) lerp(basePaint, targetPaint, progress, tmpPaint) basePaint.set(tmpPaint) } lines.forEach { line -> Loading @@ -231,7 +234,7 @@ class TextInterpolator( * @param canvas a canvas. */ fun draw(canvas: Canvas) { lerp(basePaint, targetPaint, progress, tmpDrawPaint) lerp(basePaint, targetPaint, progress, tmpPaint) lines.forEachIndexed { lineNo, line -> line.runs.forEach { run -> canvas.save() Loading @@ -241,7 +244,7 @@ class TextInterpolator( canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat()) run.fontRuns.forEach { fontRun -> drawFontRun(canvas, run, fontRun, tmpDrawPaint) drawFontRun(canvas, run, fontRun, tmpPaint) } } finally { canvas.restore() Loading Loading @@ -330,24 +333,82 @@ class TextInterpolator( } } private class MutablePositionedGlyph : TextAnimator.PositionedGlyph() { override var runStart: Int = 0 public set override var runLength: Int = 0 public set override var glyphIndex: Int = 0 public set override lateinit var font: Font public set override var glyphId: Int = 0 public set } var glyphFilter: GlyphCallback? = null // Draws single font run. private fun drawFontRun(c: Canvas, line: Run, run: FontRun, paint: Paint) { var arrayIndex = 0 val font = fontInterpolator.lerp(run.baseFont, run.targetFont, progress) val glyphFilter = glyphFilter if (glyphFilter == null) { for (i in run.start until run.end) { tmpPositionArray[arrayIndex++] = MathUtils.lerp(line.baseX[i], line.targetX[i], progress) tmpPositionArray[arrayIndex++] = MathUtils.lerp(line.baseY[i], line.targetY[i], progress) } c.drawGlyphs(line.glyphIds, run.start, tmpPositionArray, 0, run.length, font, paint) return } tmpGlyph.font = font tmpGlyph.runStart = run.start tmpGlyph.runLength = run.end - run.start tmpPaintForGlyph.set(paint) var prevStart = run.start for (i in run.start until run.end) { tmpGlyph.glyphId = line.glyphIds[i] tmpGlyph.x = MathUtils.lerp(line.baseX[i], line.targetX[i], progress) tmpGlyph.y = MathUtils.lerp(line.baseY[i], line.targetY[i], progress) tmpGlyph.textSize = paint.textSize tmpGlyph.color = paint.color glyphFilter(tmpGlyph, progress) if (tmpGlyph.textSize != paint.textSize || tmpGlyph.color != paint.color) { tmpPaintForGlyph.textSize = tmpGlyph.textSize tmpPaintForGlyph.color = tmpGlyph.color c.drawGlyphs( line.glyphIds, prevStart, tmpPositionArray, 0, i - prevStart, font, tmpPaintForGlyph) prevStart = i arrayIndex = 0 } tmpPositionArray[arrayIndex++] = tmpGlyph.x tmpPositionArray[arrayIndex++] = tmpGlyph.y } c.drawGlyphs( line.glyphIds, run.start, prevStart, tmpPositionArray, 0, run.length, fontInterpolator.lerp(run.baseFont, run.targetFont, progress), paint) run.end - prevStart, font, tmpPaintForGlyph) } private fun updatePositionsAndFonts( Loading
packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt +123 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.keyguard import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color import android.graphics.Typeface import android.graphics.fonts.Font import android.graphics.fonts.FontFamily Loading Loading @@ -194,6 +195,128 @@ class TextInterpolatorTest : SysuiTestCase() { assertThat(expected.sameAs(actual)).isTrue() } @Test fun testGlyphCallback_Empty() { val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL) val interp = TextInterpolator(layout).apply { glyphFilter = { glyph, progress -> } } interp.basePaint.set(START_PAINT) interp.onBasePaintModified() interp.targetPaint.set(END_PAINT) interp.onTargetPaintModified() // Just after created TextInterpolator, it should have 0 progress. val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL) .toBitmap(BMP_WIDTH, BMP_HEIGHT) assertThat(expected.sameAs(actual)).isTrue() } @Test fun testGlyphCallback_Xcoordinate() { val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL) val interp = TextInterpolator(layout).apply { glyphFilter = { glyph, progress -> glyph.x += 30f } } interp.basePaint.set(START_PAINT) interp.onBasePaintModified() interp.targetPaint.set(END_PAINT) interp.onTargetPaintModified() // Just after created TextInterpolator, it should have 0 progress. val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL) .toBitmap(BMP_WIDTH, BMP_HEIGHT) // The glyph position was modified by callback, so the bitmap should not be the same. // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps. assertThat(expected.sameAs(actual)).isFalse() } @Test fun testGlyphCallback_Ycoordinate() { val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL) val interp = TextInterpolator(layout).apply { glyphFilter = { glyph, progress -> glyph.y += 30f } } interp.basePaint.set(START_PAINT) interp.onBasePaintModified() interp.targetPaint.set(END_PAINT) interp.onTargetPaintModified() // Just after created TextInterpolator, it should have 0 progress. val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL) .toBitmap(BMP_WIDTH, BMP_HEIGHT) // The glyph position was modified by callback, so the bitmap should not be the same. // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps. assertThat(expected.sameAs(actual)).isFalse() } @Test fun testGlyphCallback_TextSize() { val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL) val interp = TextInterpolator(layout).apply { glyphFilter = { glyph, progress -> glyph.textSize += 10f } } interp.basePaint.set(START_PAINT) interp.onBasePaintModified() interp.targetPaint.set(END_PAINT) interp.onTargetPaintModified() // Just after created TextInterpolator, it should have 0 progress. val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL) .toBitmap(BMP_WIDTH, BMP_HEIGHT) // The glyph position was modified by callback, so the bitmap should not be the same. // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps. assertThat(expected.sameAs(actual)).isFalse() } @Test fun testGlyphCallback_Color() { val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL) val interp = TextInterpolator(layout).apply { glyphFilter = { glyph, progress -> glyph.color = Color.RED } } interp.basePaint.set(START_PAINT) interp.onBasePaintModified() interp.targetPaint.set(END_PAINT) interp.onTargetPaintModified() // Just after created TextInterpolator, it should have 0 progress. val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL) .toBitmap(BMP_WIDTH, BMP_HEIGHT) // The glyph position was modified by callback, so the bitmap should not be the same. // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps. assertThat(expected.sameAs(actual)).isFalse() } } private fun Layout.toBitmap(width: Int, height: Int) = Loading