diff options
| author | 2025-02-13 17:21:17 +0000 | |
|---|---|---|
| committer | 2025-02-14 16:59:24 +0000 | |
| commit | a1e84d0888dd976082e46129832ebdb2a234e83a (patch) | |
| tree | f1378bea688ce45ae4c8836272e32bc54958e8b8 | |
| parent | c26379f212e327cbf9ee93b3f72fb08be07a1b82 (diff) | |
Limit interpolated axis values to set of valid values
Rouding the axis values to the nearest valid step value should keep the
var cache from missing repeatedly due to functionally identical axis
values. Formerly this was not a problem because the progress float was
rounded to the nearest animation frame. However that is no longer
occuring as it caused issues w/ non-linear interpolation.
Since different axes have different min and max values, we manually
adjust the step size to roughly target the same number of valid values
within that range. This gives us a good target, but also allows us to
make specific adjustments to keep hit-rate high depending on which
axes are more important visually.
Bug: 394840414
Test: Ran animation on device
Flag: NONE TextAnimator Memory Fix
Change-Id: I1800b71601ef10e1c2b9fd083134f312c8dd6711
9 files changed, 223 insertions, 117 deletions
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt index f8bcb81dc073..bc75b1dad40c 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt @@ -22,19 +22,8 @@ import android.util.Log import android.util.LruCache import android.util.MathUtils import androidx.annotation.VisibleForTesting -import java.lang.Float.max -import java.lang.Float.min import kotlin.math.roundToInt -private const val TAG_WGHT = "wght" -private const val TAG_ITAL = "ital" - -private const val FONT_WEIGHT_DEFAULT_VALUE = 400f -private const val FONT_ITALIC_MAX = 1f -private const val FONT_ITALIC_MIN = 0f -private const val FONT_ITALIC_ANIMATION_STEP = 0.1f -private const val FONT_ITALIC_DEFAULT_VALUE = 0f - /** Caches for font interpolation */ interface FontCache { val animationFrameCount: Int @@ -91,11 +80,8 @@ class FontCacheImpl(override val animationFrameCount: Int = DEFAULT_FONT_CACHE_M class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) { /** Linear interpolate the font variation settings. */ fun lerp(start: Font, end: Font, progress: Float, linearProgress: Float): Font { - if (progress == 0f) { - return start - } else if (progress == 1f) { - return end - } + if (progress <= 0f) return start + if (progress >= 1f) return end val startAxes = start.axes ?: EMPTY_AXES val endAxes = end.axes ?: EMPTY_AXES @@ -110,7 +96,7 @@ class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) { InterpKey(start, end, (linearProgress * fontCache.animationFrameCount).roundToInt()) fontCache.get(iKey)?.let { if (DEBUG) { - Log.d(LOG_TAG, "[$progress] Interp. cache hit for $iKey") + Log.d(LOG_TAG, "[$progress, $linearProgress] Interp. cache hit for $iKey") } return it } @@ -121,37 +107,16 @@ class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) { // and also pre-fill the missing axes value with default value from 'fvar' table. val newAxes = lerp(startAxes, endAxes) { tag, startValue, endValue -> - when (tag) { - TAG_WGHT -> - MathUtils.lerp( - startValue ?: FONT_WEIGHT_DEFAULT_VALUE, - endValue ?: FONT_WEIGHT_DEFAULT_VALUE, - progress, - ) - TAG_ITAL -> - adjustItalic( - MathUtils.lerp( - startValue ?: FONT_ITALIC_DEFAULT_VALUE, - endValue ?: FONT_ITALIC_DEFAULT_VALUE, - progress, - ) - ) - else -> { - require(startValue != null && endValue != null) { - "Unable to interpolate due to unknown default axes value : $tag" - } - MathUtils.lerp(startValue, endValue, progress) - } - } + MathUtils.lerp(startValue, endValue, progress) } // Check if we already make font for this axes. This is typically happens if the animation - // happens backward. + // happens backward and is being linearly interpolated. val vKey = VarFontKey(start, newAxes) fontCache.get(vKey)?.let { fontCache.put(iKey, it) if (DEBUG) { - Log.d(LOG_TAG, "[$progress] Axis cache hit for $vKey") + Log.d(LOG_TAG, "[$progress, $linearProgress] Axis cache hit for $vKey") } return it } @@ -164,14 +129,14 @@ class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) { fontCache.put(vKey, newFont) // Cache misses are likely to create memory leaks, so this is logged at error level. - Log.e(LOG_TAG, "[$progress] Cache MISS for $iKey / $vKey") + Log.e(LOG_TAG, "[$progress, $linearProgress] Cache MISS for $iKey / $vKey") return newFont } private fun lerp( start: Array<FontVariationAxis>, end: Array<FontVariationAxis>, - filter: (tag: String, left: Float?, right: Float?) -> Float, + filter: (tag: String, left: Float, right: Float) -> Float, ): List<FontVariationAxis> { // Safe to modify result of Font#getAxes since it returns cloned object. start.sortBy { axis -> axis.tag } @@ -191,39 +156,37 @@ class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) { else -> tagA.compareTo(tagB) } - val axis = + val tag = + when { + comp == 0 -> tagA!! + comp < 0 -> tagA!! + else -> tagB!! + } + + val axisDefinition = GSFAxes.getAxis(tag) + require(comp == 0 || axisDefinition != null) { + "Unable to interpolate due to unknown default axes value: $tag" + } + + val axisValue = when { - comp == 0 -> { - val v = filter(tagA!!, start[i++].styleValue, end[j++].styleValue) - FontVariationAxis(tagA, v) - } - comp < 0 -> { - val v = filter(tagA!!, start[i++].styleValue, null) - FontVariationAxis(tagA, v) - } - else -> { // comp > 0 - val v = filter(tagB!!, null, end[j++].styleValue) - FontVariationAxis(tagB, v) - } + comp == 0 -> filter(tag, start[i++].styleValue, end[j++].styleValue) + comp < 0 -> filter(tag, start[i++].styleValue, axisDefinition!!.defaultValue) + else -> filter(tag, axisDefinition!!.defaultValue, end[j++].styleValue) } - result.add(axis) + // Round axis value to valid intermediate steps. This improves the cache hit rate. + val step = axisDefinition?.animationStep ?: DEFAULT_ANIMATION_STEP + result.add(FontVariationAxis(tag, (axisValue / step).roundToInt() * step)) } return result } - // For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps - // Cache hit ratio in the Skia glyph cache. - private fun adjustItalic(value: Float) = - coerceInWithStep(value, FONT_ITALIC_MIN, FONT_ITALIC_MAX, FONT_ITALIC_ANIMATION_STEP) - - private fun coerceInWithStep(v: Float, min: Float, max: Float, step: Float) = - (v.coerceIn(min, max) / step).toInt() * step - companion object { private const val LOG_TAG = "FontInterpolator" private val DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG) private val EMPTY_AXES = arrayOf<FontVariationAxis>() + private const val DEFAULT_ANIMATION_STEP = 1f // Returns true if given two font instance can be interpolated. fun canInterpolate(start: Font, end: Font) = diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt index 9545bda80b2d..9a746870c6ff 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt @@ -1,12 +1,20 @@ -package com.android.systemui.animation +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -object GSFAxes { - const val WEIGHT = "wght" - const val WIDTH = "wdth" - const val SLANT = "slnt" - const val ROUND = "ROND" - const val OPTICAL_SIZE = "opsz" -} +package com.android.systemui.animation class FontVariationUtils { private var mWeight = -1 @@ -46,20 +54,20 @@ class FontVariationUtils { } var resultString = "" if (mWeight >= 0) { - resultString += "'${GSFAxes.WEIGHT}' $mWeight" + resultString += "'${GSFAxes.WEIGHT.tag}' $mWeight" } if (mWidth >= 0) { resultString += - (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.WIDTH}' $mWidth" + (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.WIDTH.tag}' $mWidth" } if (mOpticalSize >= 0) { resultString += (if (resultString.isBlank()) "" else ", ") + - "'${GSFAxes.OPTICAL_SIZE}' $mOpticalSize" + "'${GSFAxes.OPTICAL_SIZE.tag}' $mOpticalSize" } if (mRoundness >= 0) { resultString += - (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.ROUND}' $mRoundness" + (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.ROUND.tag}' $mRoundness" } return if (isUpdated) resultString else "" } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt new file mode 100644 index 000000000000..f4e03613169a --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.animation + +data class AxisDefinition( + val tag: String, + val minValue: Float, + val defaultValue: Float, + val maxValue: Float, + val animationStep: Float, +) + +object GSFAxes { + val WEIGHT = + AxisDefinition( + tag = "wght", + minValue = 1f, + defaultValue = 400f, + maxValue = 1000f, + animationStep = 10f, + ) + + val WIDTH = + AxisDefinition( + tag = "wdth", + minValue = 25f, + defaultValue = 100f, + maxValue = 151f, + animationStep = 1f, + ) + + val SLANT = + AxisDefinition( + tag = "slnt", + minValue = 0f, + defaultValue = 0f, + maxValue = -10f, + animationStep = 0.1f, + ) + + val ROUND = + AxisDefinition( + tag = "ROND", + minValue = 0f, + defaultValue = 0f, + maxValue = 100f, + animationStep = 1f, + ) + + val GRADE = + AxisDefinition( + tag = "GRAD", + minValue = 0f, + defaultValue = 0f, + maxValue = 100f, + animationStep = 1f, + ) + + val OPTICAL_SIZE = + AxisDefinition( + tag = "opsz", + minValue = 6f, + defaultValue = 18f, + maxValue = 144f, + animationStep = 1f, + ) + + // Not a GSF Axis, but present for FontInterpolator compatibility + val ITALIC = + AxisDefinition( + tag = "ITAL", + minValue = 0f, + defaultValue = 0f, + maxValue = 1f, + animationStep = 0.1f, + ) + + private val AXIS_MAP = + listOf(WEIGHT, WIDTH, SLANT, ROUND, GRADE, OPTICAL_SIZE, ITALIC) + .map { def -> def.tag.toLowerCase() to def } + .toMap() + + fun getAxis(axis: String): AxisDefinition? = AXIS_MAP[axis.toLowerCase()] +} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt index 004d1aa1fe93..ac1c5a8dfaf3 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt @@ -130,39 +130,25 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController private val FONT_AXES = listOf( - ClockFontAxis( - key = GSFAxes.WEIGHT, + GSFAxes.WEIGHT.toClockAxis( type = AxisType.Float, - minValue = 25f, currentValue = 400f, - maxValue = 1000f, name = "Weight", description = "Glyph Weight", ), - ClockFontAxis( - key = GSFAxes.WIDTH, + GSFAxes.WIDTH.toClockAxis( type = AxisType.Float, - minValue = 25f, currentValue = 85f, - maxValue = 151f, name = "Width", description = "Glyph Width", ), - ClockFontAxis( - key = GSFAxes.ROUND, + GSFAxes.ROUND.toClockAxis( type = AxisType.Boolean, - minValue = 0f, - currentValue = 0f, - maxValue = 100f, name = "Round", description = "Glyph Roundness", ), - ClockFontAxis( - key = GSFAxes.SLANT, + GSFAxes.SLANT.toClockAxis( type = AxisType.Boolean, - minValue = 0f, - currentValue = 0f, - maxValue = -10f, name = "Slant", description = "Glyph Slant", ), @@ -170,10 +156,10 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController private val LEGACY_FLEX_SETTINGS = listOf( - ClockFontAxisSetting(GSFAxes.WEIGHT, 600f), - ClockFontAxisSetting(GSFAxes.WIDTH, 100f), - ClockFontAxisSetting(GSFAxes.ROUND, 100f), - ClockFontAxisSetting(GSFAxes.SLANT, 0f), + GSFAxes.WEIGHT.toClockAxisSetting(600f), + GSFAxes.WIDTH.toClockAxisSetting(100f), + GSFAxes.ROUND.toClockAxisSetting(100f), + GSFAxes.SLANT.toClockAxisSetting(0f), ) } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt index b2dbd6552955..b4c2f5de290f 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt @@ -132,7 +132,7 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock: if (!isLargeClock) { axes = axes.map { axis -> - if (axis.key == GSFAxes.WIDTH && axis.value > SMALL_CLOCK_MAX_WDTH) { + if (axis.key == GSFAxes.WIDTH.tag && axis.value > SMALL_CLOCK_MAX_WDTH) { axis.copy(value = SMALL_CLOCK_MAX_WDTH) } else { axis diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FontUtils.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FontUtils.kt new file mode 100644 index 000000000000..212b1e29d1b8 --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FontUtils.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shared.clocks + +import com.android.systemui.animation.AxisDefinition +import com.android.systemui.plugins.clocks.AxisType +import com.android.systemui.plugins.clocks.ClockFontAxis +import com.android.systemui.plugins.clocks.ClockFontAxisSetting + +fun AxisDefinition.toClockAxis( + type: AxisType, + currentValue: Float? = null, + name: String, + description: String, +): ClockFontAxis { + return ClockFontAxis( + key = this.tag, + type = type, + maxValue = this.maxValue, + minValue = this.minValue, + currentValue = currentValue ?: this.defaultValue, + name = name, + description = description, + ) +} + +fun AxisDefinition.toClockAxisSetting(value: Float? = null): ClockFontAxisSetting { + return ClockFontAxisSetting(this.tag, value ?: this.defaultValue) +} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt index 8317aa39ef2b..5b0db225f9cd 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt @@ -49,6 +49,7 @@ import com.android.systemui.shared.clocks.DigitTranslateAnimator import com.android.systemui.shared.clocks.DimensionParser import com.android.systemui.shared.clocks.FLEX_CLOCK_ID import com.android.systemui.shared.clocks.FontTextStyle +import com.android.systemui.shared.clocks.toClockAxisSetting import java.lang.Thread import kotlin.math.max import kotlin.math.min @@ -594,25 +595,25 @@ open class SimpleDigitalClockTextView( val FIDGET_INTERPOLATOR = PathInterpolator(0.26873f, 0f, 0.45042f, 1f) val FIDGET_DISTS = mapOf( - GSFAxes.WEIGHT to Pair(200f, 500f), - GSFAxes.WIDTH to Pair(30f, 75f), - GSFAxes.ROUND to Pair(0f, 50f), - GSFAxes.SLANT to Pair(0f, -5f), + GSFAxes.WEIGHT.tag to Pair(200f, 500f), + GSFAxes.WIDTH.tag to Pair(30f, 75f), + GSFAxes.ROUND.tag to Pair(0f, 50f), + GSFAxes.SLANT.tag to Pair(0f, -5f), ) val AOD_COLOR = Color.WHITE - val LS_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 400f) - val AOD_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 200f) - val WIDTH_AXIS = ClockFontAxisSetting(GSFAxes.WIDTH, 85f) - val ROUND_AXIS = ClockFontAxisSetting(GSFAxes.ROUND, 0f) - val SLANT_AXIS = ClockFontAxisSetting(GSFAxes.SLANT, 0f) + val LS_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(400f) + val AOD_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(200f) + val WIDTH_AXIS = GSFAxes.WIDTH.toClockAxisSetting(85f) + val ROUND_AXIS = GSFAxes.ROUND.toClockAxisSetting(0f) + val SLANT_AXIS = GSFAxes.SLANT.toClockAxisSetting(0f) // Axes for Legacy version of the Flex Clock - val FLEX_LS_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 600f) - val FLEX_AOD_LARGE_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 74f) - val FLEX_AOD_SMALL_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 133f) - val FLEX_LS_WIDTH_AXIS = ClockFontAxisSetting(GSFAxes.WIDTH, 100f) - val FLEX_AOD_WIDTH_AXIS = ClockFontAxisSetting(GSFAxes.WIDTH, 43f) - val FLEX_ROUND_AXIS = ClockFontAxisSetting(GSFAxes.ROUND, 100f) + val FLEX_LS_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(600f) + val FLEX_AOD_LARGE_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(74f) + val FLEX_AOD_SMALL_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(133f) + val FLEX_LS_WIDTH_AXIS = GSFAxes.WIDTH.toClockAxisSetting(100f) + val FLEX_AOD_WIDTH_AXIS = GSFAxes.WIDTH.toClockAxisSetting(43f) + val FLEX_ROUND_AXIS = GSFAxes.ROUND.toClockAxisSetting(100f) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt index f44769d522eb..8d3640d8d809 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt @@ -21,7 +21,7 @@ class FontVariationUtilsTest : SysuiTestCase() { roundness = 100, ) Assert.assertEquals( - "'${GSFAxes.WEIGHT}' 100, '${GSFAxes.WIDTH}' 100, '${GSFAxes.ROUND}' 100", + "'${GSFAxes.WEIGHT.tag}' 100, '${GSFAxes.WIDTH.tag}' 100, '${GSFAxes.ROUND.tag}' 100", initFvar, ) val updatedFvar = @@ -32,7 +32,8 @@ class FontVariationUtilsTest : SysuiTestCase() { roundness = 100, ) Assert.assertEquals( - "'${GSFAxes.WEIGHT}' 200, '${GSFAxes.WIDTH}' 100, '${GSFAxes.OPTICAL_SIZE}' 0, '${GSFAxes.ROUND}' 100", + "'${GSFAxes.WEIGHT.tag}' 200, '${GSFAxes.WIDTH.tag}' 100," + + " '${GSFAxes.OPTICAL_SIZE.tag}' 0, '${GSFAxes.ROUND.tag}' 100", updatedFvar, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt index 2e634390679a..c2a495d13c02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt @@ -64,6 +64,12 @@ class FontInterpolatorTest : SysuiTestCase() { "'wght' 500, 'ital' 0.5, 'GRAD' 450", interp.lerp(startFont, endFont, 0.5f, 0.5f), ) + + // Ensure axes rounded correctly to nearest step + assertSameAxes( + "'wght' 490, 'ital' 0.5, 'GRAD' 446", + interp.lerp(startFont, endFont, 0.492f, 0.492f), + ) } @Test |