Do not use Paint.setFontVariationSettings as basis for variant Typefaces

Post cache eviction, it was possible to generate the wrong typeface
for an animation if the typeface was edited but not the font variation
settings as Paint.setFontVariationSettings would not. If the typeface
must be changed, reinitialization of the cache is also required.

Bug: 279877970
Test: Manually checked variant typeface generation
Change-Id: I5f76c0aa3d8aca7c1096c34c09b5861b980fe0ff
This commit is contained in:
Hawkwood Glazier
2023-04-29 16:39:26 +00:00
parent 8c01119c26
commit d702ba7fee
4 changed files with 37 additions and 24 deletions

View File

@@ -23,8 +23,8 @@ import android.animation.ValueAnimator
import android.graphics.Canvas
import android.graphics.Typeface
import android.graphics.fonts.Font
import android.graphics.fonts.FontVariationAxis
import android.text.Layout
import android.text.TextPaint
import android.util.LruCache
private const val DEFAULT_ANIMATION_DURATION: Long = 300
@@ -33,18 +33,39 @@ private const val TYPEFACE_CACHE_MAX_ENTRIES = 5
typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
interface TypefaceVariantCache {
fun getTypefaceForVariant(fvar: String, targetPaint: TextPaint): Typeface?
fun getTypefaceForVariant(fvar: String?): Typeface?
companion object {
fun createVariantTypeface(baseTypeface: Typeface, fVar: String?): Typeface {
if (fVar.isNullOrEmpty()) {
return baseTypeface
}
val axes = FontVariationAxis.fromFontVariationSettings(fVar).toMutableList()
axes.removeIf { !baseTypeface.isSupportedAxes(it.getOpenTypeTagValue()) }
if (axes.isEmpty()) {
return baseTypeface
}
return Typeface.createFromTypefaceWithVariation(baseTypeface, axes)
}
}
}
class TypefaceVariantCacheImpl() : TypefaceVariantCache {
class TypefaceVariantCacheImpl(
var baseTypeface: Typeface,
) : TypefaceVariantCache {
private val cache = LruCache<String, Typeface>(TYPEFACE_CACHE_MAX_ENTRIES)
override fun getTypefaceForVariant(fvar: String, targetPaint: TextPaint): Typeface? {
override fun getTypefaceForVariant(fvar: String?): Typeface? {
if (fvar == null) {
return baseTypeface
}
cache.get(fvar)?.let {
return it
}
targetPaint.fontVariationSettings = fvar
return targetPaint.typeface?.also { cache.put(fvar, it) }
return TypefaceVariantCache
.createVariantTypeface(baseTypeface, fvar)
.also { cache.put(fvar, it) }
}
}
@@ -78,7 +99,7 @@ class TextAnimator(
layout: Layout,
private val invalidateCallback: () -> Unit,
) {
var typefaceCache: TypefaceVariantCache = TypefaceVariantCacheImpl()
var typefaceCache: TypefaceVariantCache = TypefaceVariantCacheImpl(layout.paint.typeface)
get() = field
set(value) {
field = value
@@ -244,8 +265,7 @@ class TextAnimator(
}
if (!fvar.isNullOrBlank()) {
textInterpolator.targetPaint.typeface =
typefaceCache.getTypefaceForVariant(fvar, textInterpolator.targetPaint)
textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
}
if (color != null) {
@@ -339,4 +359,3 @@ class TextAnimator(
)
}
}

View File

@@ -38,6 +38,8 @@ class TextInterpolator(
* Once you modified the style parameters, you have to call reshapeText to recalculate base text
* layout.
*
* Do not bypass the cache and update the typeface or font variation directly.
*
* @return a paint object
*/
val basePaint = TextPaint(layout.paint)
@@ -48,6 +50,8 @@ class TextInterpolator(
* Once you modified the style parameters, you have to call reshapeText to recalculate target
* text layout.
*
* Do not bypass the cache and update the typeface or font variation directly.
*
* @return a paint object
*/
val targetPaint = TextPaint(layout.paint)
@@ -217,14 +221,8 @@ class TextInterpolator(
run.fontRuns.forEach { fontRun ->
fontRun.baseFont =
fontInterpolator.lerp(fontRun.baseFont, fontRun.targetFont, progress)
val fvar = run {
val tmpFontVariationsArray = mutableListOf<FontVariationAxis>()
fontRun.baseFont.axes.forEach {
tmpFontVariationsArray.add(FontVariationAxis(it.tag, it.styleValue))
}
FontVariationAxis.toFontVariationSettings(tmpFontVariationsArray)
}
basePaint.typeface = typefaceCache.getTypefaceForVariant(fvar, basePaint)
val fvar = FontVariationAxis.toFontVariationSettings(fontRun.baseFont.axes)
basePaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
}
}
}

View File

@@ -39,10 +39,6 @@ import org.mockito.Mockito.verify
import kotlin.math.ceil
private val PAINT = TextPaint().apply {
textSize = 32f
}
@RunWith(AndroidTestingRunner::class)
@SmallTest
class TextAnimatorTest : SysuiTestCase() {

View File

@@ -49,7 +49,7 @@ private val VF_FONT = Font.Builder(File("/system/fonts/Roboto-Regular.ttf")).bui
private fun Font.toTypeface() =
Typeface.CustomFallbackBuilder(FontFamily.Builder(this).build()).build()
private val PAINT = TextPaint().apply {
internal val PAINT = TextPaint().apply {
typeface = Font.Builder(VF_FONT).setFontVariationSettings("'wght' 400").build().toTypeface()
textSize = 32f
}
@@ -79,7 +79,7 @@ class TextInterpolatorTest : SysuiTestCase() {
@Before
fun setup() {
typefaceCache = TypefaceVariantCacheImpl()
typefaceCache = TypefaceVariantCacheImpl(PAINT.typeface)
}
@Test