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:
@@ -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(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user