Merge "Format Font/Text Animation files" into tm-qpr-dev am: f3a6b54477

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20948431

Change-Id: Ic3c748a737931b851792e392a0cd06d89c7fe28a
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
TreeHugger Robot
2023-01-20 20:58:04 +00:00
committed by Automerger Merge Worker
4 changed files with 233 additions and 252 deletions

View File

@@ -33,9 +33,7 @@ private const val FONT_ITALIC_MIN = 0f
private const val FONT_ITALIC_ANIMATION_STEP = 0.1f private const val FONT_ITALIC_ANIMATION_STEP = 0.1f
private const val FONT_ITALIC_DEFAULT_VALUE = 0f private const val FONT_ITALIC_DEFAULT_VALUE = 0f
/** /** Provide interpolation of two fonts by adjusting font variation settings. */
* Provide interpolation of two fonts by adjusting font variation settings.
*/
class FontInterpolator { class FontInterpolator {
/** /**
@@ -61,11 +59,14 @@ class FontInterpolator {
var index: Int, var index: Int,
val sortedAxes: MutableList<FontVariationAxis> val sortedAxes: MutableList<FontVariationAxis>
) { ) {
constructor(font: Font, axes: List<FontVariationAxis>) : constructor(
this(font.sourceIdentifier, font: Font,
font.ttcIndex, axes: List<FontVariationAxis>
axes.toMutableList().apply { sortBy { it.tag } } ) : this(
) font.sourceIdentifier,
font.ttcIndex,
axes.toMutableList().apply { sortBy { it.tag } }
)
fun set(font: Font, axes: List<FontVariationAxis>) { fun set(font: Font, axes: List<FontVariationAxis>) {
sourceId = font.sourceIdentifier sourceId = font.sourceIdentifier
@@ -86,9 +87,7 @@ class FontInterpolator {
private val tmpInterpKey = InterpKey(null, null, 0f) private val tmpInterpKey = InterpKey(null, null, 0f)
private val tmpVarFontKey = VarFontKey(0, 0, mutableListOf()) private val tmpVarFontKey = VarFontKey(0, 0, mutableListOf())
/** /** Linear interpolate the font variation settings. */
* Linear interpolate the font variation settings.
*/
fun lerp(start: Font, end: Font, progress: Float): Font { fun lerp(start: Font, end: Font, progress: Float): Font {
if (progress == 0f) { if (progress == 0f) {
return start return start
@@ -115,27 +114,34 @@ class FontInterpolator {
// this doesn't take much time since the variation axes is usually up to 5. If we need to // this doesn't take much time since the variation axes is usually up to 5. If we need to
// support more number of axes, we may want to preprocess the font and store the sorted axes // support more number of axes, we may want to preprocess the font and store the sorted axes
// and also pre-fill the missing axes value with default value from 'fvar' table. // and also pre-fill the missing axes value with default value from 'fvar' table.
val newAxes = lerp(startAxes, endAxes) { tag, startValue, endValue -> val newAxes =
when (tag) { lerp(startAxes, endAxes) { tag, startValue, endValue ->
// TODO: Good to parse 'fvar' table for retrieving default value. when (tag) {
TAG_WGHT -> adjustWeight( // TODO: Good to parse 'fvar' table for retrieving default value.
MathUtils.lerp( TAG_WGHT ->
adjustWeight(
MathUtils.lerp(
startValue ?: FONT_WEIGHT_DEFAULT_VALUE, startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
endValue ?: FONT_WEIGHT_DEFAULT_VALUE, endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
progress)) progress
TAG_ITAL -> adjustItalic( )
MathUtils.lerp( )
TAG_ITAL ->
adjustItalic(
MathUtils.lerp(
startValue ?: FONT_ITALIC_DEFAULT_VALUE, startValue ?: FONT_ITALIC_DEFAULT_VALUE,
endValue ?: FONT_ITALIC_DEFAULT_VALUE, endValue ?: FONT_ITALIC_DEFAULT_VALUE,
progress)) progress
else -> { )
require(startValue != null && endValue != null) { )
"Unable to interpolate due to unknown default axes value : $tag" 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 // Check if we already make font for this axes. This is typically happens if the animation
// happens backward. // happens backward.
@@ -149,9 +155,7 @@ class FontInterpolator {
// This is the first time to make the font for the axes. Build and store it to the cache. // This is the first time to make the font for the axes. Build and store it to the cache.
// Font.Builder#build won't throw IOException since creating fonts from existing fonts will // Font.Builder#build won't throw IOException since creating fonts from existing fonts will
// not do any IO work. // not do any IO work.
val newFont = Font.Builder(start) val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build()
.setFontVariationSettings(newAxes.toTypedArray())
.build()
interpCache[InterpKey(start, end, progress)] = newFont interpCache[InterpKey(start, end, progress)] = newFont
verFontCache[VarFontKey(start, newAxes)] = newFont verFontCache[VarFontKey(start, newAxes)] = newFont
return newFont return newFont
@@ -173,26 +177,28 @@ class FontInterpolator {
val tagA = if (i < start.size) start[i].tag else null val tagA = if (i < start.size) start[i].tag else null
val tagB = if (j < end.size) end[j].tag else null val tagB = if (j < end.size) end[j].tag else null
val comp = when { val comp =
tagA == null -> 1 when {
tagB == null -> -1 tagA == null -> 1
else -> tagA.compareTo(tagB) tagB == null -> -1
} else -> tagA.compareTo(tagB)
}
val axis = when { val axis =
comp == 0 -> { when {
val v = filter(tagA!!, start[i++].styleValue, end[j++].styleValue) comp == 0 -> {
FontVariationAxis(tagA, v) 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 -> {
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)
}
}
result.add(axis) result.add(axis)
} }
@@ -202,21 +208,21 @@ class FontInterpolator {
// For the performance reasons, we animate weight with FONT_WEIGHT_ANIMATION_STEP. This helps // For the performance reasons, we animate weight with FONT_WEIGHT_ANIMATION_STEP. This helps
// Cache hit ratio in the Skia glyph cache. // Cache hit ratio in the Skia glyph cache.
private fun adjustWeight(value: Float) = private fun adjustWeight(value: Float) =
coerceInWithStep(value, FONT_WEIGHT_MIN, FONT_WEIGHT_MAX, FONT_WEIGHT_ANIMATION_STEP) coerceInWithStep(value, FONT_WEIGHT_MIN, FONT_WEIGHT_MAX, FONT_WEIGHT_ANIMATION_STEP)
// For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps // For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps
// Cache hit ratio in the Skia glyph cache. // Cache hit ratio in the Skia glyph cache.
private fun adjustItalic(value: Float) = private fun adjustItalic(value: Float) =
coerceInWithStep(value, FONT_ITALIC_MIN, FONT_ITALIC_MAX, FONT_ITALIC_ANIMATION_STEP) coerceInWithStep(value, FONT_ITALIC_MIN, FONT_ITALIC_MAX, FONT_ITALIC_ANIMATION_STEP)
private fun coerceInWithStep(v: Float, min: Float, max: Float, step: Float) = private fun coerceInWithStep(v: Float, min: Float, max: Float, step: Float) =
(v.coerceIn(min, max) / step).toInt() * step (v.coerceIn(min, max) / step).toInt() * step
companion object { companion object {
private val EMPTY_AXES = arrayOf<FontVariationAxis>() private val EMPTY_AXES = arrayOf<FontVariationAxis>()
// Returns true if given two font instance can be interpolated. // Returns true if given two font instance can be interpolated.
fun canInterpolate(start: Font, end: Font) = fun canInterpolate(start: Font, end: Font) =
start.ttcIndex == end.ttcIndex && start.sourceIdentifier == end.sourceIdentifier start.ttcIndex == end.ttcIndex && start.sourceIdentifier == end.sourceIdentifier
} }
} }

View File

@@ -36,8 +36,8 @@ typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
* Currently this class can provide text style animation for text weight and text size. For example * Currently this class can provide text style animation for text weight and text size. For example
* the simple view that draws text with animating text size is like as follows: * the simple view that draws text with animating text size is like as follows:
* *
* <pre> * <pre> <code>
* <code> * ```
* class SimpleTextAnimation : View { * class SimpleTextAnimation : View {
* @JvmOverloads constructor(...) * @JvmOverloads constructor(...)
* *
@@ -53,83 +53,63 @@ typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
* animator.setTextStyle(-1 /* unchanged weight */, sizePx, animate) * animator.setTextStyle(-1 /* unchanged weight */, sizePx, animate)
* } * }
* } * }
* </code> * ```
* </pre> * </code> </pre>
*/ */
class TextAnimator( class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
layout: Layout,
private val invalidateCallback: () -> Unit
) {
// Following two members are for mutable for testing purposes. // Following two members are for mutable for testing purposes.
public var textInterpolator: TextInterpolator = TextInterpolator(layout) public var textInterpolator: TextInterpolator = TextInterpolator(layout)
public var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply { public var animator: ValueAnimator =
duration = DEFAULT_ANIMATION_DURATION ValueAnimator.ofFloat(1f).apply {
addUpdateListener { duration = DEFAULT_ANIMATION_DURATION
textInterpolator.progress = it.animatedValue as Float addUpdateListener {
invalidateCallback() textInterpolator.progress = it.animatedValue as Float
} invalidateCallback()
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
textInterpolator.rebase()
} }
override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase() addListener(
}) object : AnimatorListenerAdapter() {
} override fun onAnimationEnd(animation: Animator?) {
textInterpolator.rebase()
}
override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
}
)
}
sealed class PositionedGlyph { sealed class PositionedGlyph {
/** /** Mutable X coordinate of the glyph position relative from drawing offset. */
* Mutable X coordinate of the glyph position relative from drawing offset.
*/
var x: Float = 0f var x: Float = 0f
/** /** Mutable Y coordinate of the glyph position relative from the baseline. */
* Mutable Y coordinate of the glyph position relative from the baseline.
*/
var y: Float = 0f var y: Float = 0f
/** /** The current line of text being drawn, in a multi-line TextView. */
* The current line of text being drawn, in a multi-line TextView.
*/
var lineNo: Int = 0 var lineNo: Int = 0
/** /** Mutable text size of the glyph in pixels. */
* Mutable text size of the glyph in pixels.
*/
var textSize: Float = 0f var textSize: Float = 0f
/** /** Mutable color of the glyph. */
* Mutable color of the glyph.
*/
var color: Int = 0 var color: Int = 0
/** /** Immutable character offset in the text that the current font run start. */
* Immutable character offset in the text that the current font run start.
*/
abstract var runStart: Int abstract var runStart: Int
protected set protected set
/** /** Immutable run length of the font run. */
* Immutable run length of the font run.
*/
abstract var runLength: Int abstract var runLength: Int
protected set protected set
/** /** Immutable glyph index of the font run. */
* Immutable glyph index of the font run.
*/
abstract var glyphIndex: Int abstract var glyphIndex: Int
protected set protected set
/** /** Immutable font instance for this font run. */
* Immutable font instance for this font run.
*/
abstract var font: Font abstract var font: Font
protected set protected set
/** /** Immutable glyph ID for this glyph. */
* Immutable glyph ID for this glyph.
*/
abstract var glyphId: Int abstract var glyphId: Int
protected set protected set
} }
@@ -147,20 +127,18 @@ class TextAnimator(
/** /**
* GlyphFilter applied just before drawing to canvas for tweaking positions and text size. * 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 * This callback is called for each glyphs just before drawing the glyphs. This function will be
* be called with the intrinsic position, size, color, glyph ID and font instance. You can * called with the intrinsic position, size, color, glyph ID and font instance. You can mutate
* mutate the position, size and color for tweaking animations. * the position, size and color for tweaking animations. Do not keep the reference of passed
* Do not keep the reference of passed glyph object. The interpolator reuses that object for * glyph object. The interpolator reuses that object for avoiding object allocations.
* avoiding object allocations.
* *
* Details: * Details: The text is drawn with font run units. The font run is a text segment that draws
* The text is drawn with font run units. The font run is a text segment that draws with the * with the same font. The {@code runStart} and {@code runLimit} is a range of the font run in
* same font. The {@code runStart} and {@code runLimit} is a range of the font run in the text * the text that current glyph is in. Once the font run is determined, the system will convert
* that current glyph is in. Once the font run is determined, the system will convert characters * characters into glyph IDs. The {@code glyphId} is the glyph identifier in the font and {@code
* into glyph IDs. The {@code glyphId} is the glyph identifier in the font and * glyphIndex} is the offset of the converted glyph array. Please note that the {@code
* {@code glyphIndex} is the offset of the converted glyph array. Please note that the * glyphIndex} is not a character index, because the character will not be converted to glyph
* {@code glyphIndex} is not a character index, because the character will not be converted to * one-by-one. If there are ligatures including emoji sequence, etc, the glyph ID may be
* glyph one-by-one. If there are ligatures including emoji sequence, etc, the glyph ID may be
* composed from multiple characters. * composed from multiple characters.
* *
* Here is an example of font runs: "fin. 終わり" * Here is an example of font runs: "fin. 終わり"
@@ -193,7 +171,9 @@ class TextAnimator(
*/ */
var glyphFilter: GlyphCallback? var glyphFilter: GlyphCallback?
get() = textInterpolator.glyphFilter get() = textInterpolator.glyphFilter
set(value) { textInterpolator.glyphFilter = value } set(value) {
textInterpolator.glyphFilter = value
}
fun draw(c: Canvas) = textInterpolator.draw(c) fun draw(c: Canvas) = textInterpolator.draw(c)
@@ -208,7 +188,7 @@ class TextAnimator(
* @param weight an optional text weight. * @param weight an optional text weight.
* @param textSize an optional font size. * @param textSize an optional font size.
* @param colors an optional colors array that must be the same size as numLines passed to * @param colors an optional colors array that must be the same size as numLines passed to
* the TextInterpolator * the TextInterpolator
* @param animate an optional boolean indicating true for showing style transition as animation, * @param animate an optional boolean indicating true for showing style transition as animation,
* false for immediate style transition. True by default. * false for immediate style transition. True by default.
* @param duration an optional animation duration in milliseconds. This is ignored if animate is * @param duration an optional animation duration in milliseconds. This is ignored if animate is
@@ -237,10 +217,11 @@ class TextAnimator(
if (weight >= 0) { if (weight >= 0) {
// Paint#setFontVariationSettings creates Typeface instance from scratch. To reduce the // Paint#setFontVariationSettings creates Typeface instance from scratch. To reduce the
// memory impact, cache the typeface result. // memory impact, cache the typeface result.
textInterpolator.targetPaint.typeface = typefaceCache.getOrElse(weight) { textInterpolator.targetPaint.typeface =
textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight" typefaceCache.getOrElse(weight) {
textInterpolator.targetPaint.typeface textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
} textInterpolator.targetPaint.typeface
}
} }
if (color != null) { if (color != null) {
textInterpolator.targetPaint.color = color textInterpolator.targetPaint.color = color
@@ -249,22 +230,24 @@ class TextAnimator(
if (animate) { if (animate) {
animator.startDelay = delay animator.startDelay = delay
animator.duration = if (duration == -1L) { animator.duration =
DEFAULT_ANIMATION_DURATION if (duration == -1L) {
} else { DEFAULT_ANIMATION_DURATION
duration } else {
} duration
}
interpolator?.let { animator.interpolator = it } interpolator?.let { animator.interpolator = it }
if (onAnimationEnd != null) { if (onAnimationEnd != null) {
val listener = object : AnimatorListenerAdapter() { val listener =
override fun onAnimationEnd(animation: Animator?) { object : AnimatorListenerAdapter() {
onAnimationEnd.run() override fun onAnimationEnd(animation: Animator?) {
animator.removeListener(this) onAnimationEnd.run()
animator.removeListener(this)
}
override fun onAnimationCancel(animation: Animator?) {
animator.removeListener(this)
}
} }
override fun onAnimationCancel(animation: Animator?) {
animator.removeListener(this)
}
}
animator.addListener(listener) animator.addListener(listener)
} }
animator.start() animator.start()

View File

@@ -26,12 +26,8 @@ import android.util.MathUtils
import com.android.internal.graphics.ColorUtils import com.android.internal.graphics.ColorUtils
import java.lang.Math.max import java.lang.Math.max
/** /** Provide text style linear interpolation for plain text. */
* Provide text style linear interpolation for plain text. class TextInterpolator(layout: Layout) {
*/
class TextInterpolator(
layout: Layout
) {
/** /**
* Returns base paint used for interpolation. * Returns base paint used for interpolation.
@@ -64,12 +60,11 @@ class TextInterpolator(
var baseFont: Font, var baseFont: Font,
var targetFont: Font var targetFont: Font
) { ) {
val length: Int get() = end - start val length: Int
get() = end - start
} }
/** /** A class represents text layout of a single run. */
* A class represents text layout of a single run.
*/
private class Run( private class Run(
val glyphIds: IntArray, val glyphIds: IntArray,
val baseX: FloatArray, // same length as glyphIds val baseX: FloatArray, // same length as glyphIds
@@ -79,12 +74,8 @@ class TextInterpolator(
val fontRuns: List<FontRun> val fontRuns: List<FontRun>
) )
/** /** A class represents text layout of a single line. */
* A class represents text layout of a single line. private class Line(val runs: List<Run>)
*/
private class Line(
val runs: List<Run>
)
private var lines = listOf<Line>() private var lines = listOf<Line>()
private val fontInterpolator = FontInterpolator() private val fontInterpolator = FontInterpolator()
@@ -106,8 +97,8 @@ class TextInterpolator(
/** /**
* The layout used for drawing text. * The layout used for drawing text.
* *
* Only non-styled text is supported. Even if the given layout is created from Spanned, the * Only non-styled text is supported. Even if the given layout is created from Spanned, the span
* span information is not used. * information is not used.
* *
* The paint objects used for interpolation are not changed by this method call. * The paint objects used for interpolation are not changed by this method call.
* *
@@ -133,8 +124,8 @@ class TextInterpolator(
/** /**
* Recalculate internal text layout for interpolation. * Recalculate internal text layout for interpolation.
* *
* Whenever the target paint is modified, call this method to recalculate internal * Whenever the target paint is modified, call this method to recalculate internal text layout
* text layout used for interpolation. * used for interpolation.
*/ */
fun onTargetPaintModified() { fun onTargetPaintModified() {
updatePositionsAndFonts(shapeText(layout, targetPaint), updateBase = false) updatePositionsAndFonts(shapeText(layout, targetPaint), updateBase = false)
@@ -143,8 +134,8 @@ class TextInterpolator(
/** /**
* Recalculate internal text layout for interpolation. * Recalculate internal text layout for interpolation.
* *
* Whenever the base paint is modified, call this method to recalculate internal * Whenever the base paint is modified, call this method to recalculate internal text layout
* text layout used for interpolation. * used for interpolation.
*/ */
fun onBasePaintModified() { fun onBasePaintModified() {
updatePositionsAndFonts(shapeText(layout, basePaint), updateBase = true) updatePositionsAndFonts(shapeText(layout, basePaint), updateBase = true)
@@ -155,11 +146,11 @@ class TextInterpolator(
* *
* The text interpolator does not calculate all the text position by text shaper due to * The text interpolator does not calculate all the text position by text shaper due to
* performance reasons. Instead, the text interpolator shape the start and end state and * performance reasons. Instead, the text interpolator shape the start and end state and
* calculate text position of the middle state by linear interpolation. Due to this trick, * calculate text position of the middle state by linear interpolation. Due to this trick, the
* the text positions of the middle state is likely different from the text shaper result. * text positions of the middle state is likely different from the text shaper result. So, if
* So, if you want to start animation from the middle state, you will see the glyph jumps due to * you want to start animation from the middle state, you will see the glyph jumps due to this
* this trick, i.e. the progress 0.5 of interpolation between weight 400 and 700 is different * trick, i.e. the progress 0.5 of interpolation between weight 400 and 700 is different from
* from text shape result of weight 550. * text shape result of weight 550.
* *
* After calling this method, do not call onBasePaintModified() since it reshape the text and * After calling this method, do not call onBasePaintModified() since it reshape the text and
* update the base state. As in above notice, the text shaping result at current progress is * update the base state. As in above notice, the text shaping result at current progress is
@@ -171,8 +162,8 @@ class TextInterpolator(
* animate weight from 200 to 400, then if you want to move back to 200 at the half of the * animate weight from 200 to 400, then if you want to move back to 200 at the half of the
* animation, it will look like * animation, it will look like
* *
* <pre> * <pre> <code>
* <code> * ```
* val interp = TextInterpolator(layout) * val interp = TextInterpolator(layout)
* *
* // Interpolate between weight 200 to 400. * // Interpolate between weight 200 to 400.
@@ -202,9 +193,8 @@ class TextInterpolator(
* // progress is 0.5 * // progress is 0.5
* animator.start() * animator.start()
* } * }
* </code> * ```
* </pre> * </code> </pre>
*
*/ */
fun rebase() { fun rebase() {
if (progress == 0f) { if (progress == 0f) {
@@ -266,69 +256,73 @@ class TextInterpolator(
} }
var maxRunLength = 0 var maxRunLength = 0
lines = baseLayout.zip(targetLayout) { baseLine, targetLine -> lines =
val runs = baseLine.zip(targetLine) { base, target -> baseLayout.zip(targetLayout) { baseLine, targetLine ->
val runs =
require(base.glyphCount() == target.glyphCount()) { baseLine.zip(targetLine) { base, target ->
"Inconsistent glyph count at line ${lines.size}" require(base.glyphCount() == target.glyphCount()) {
} "Inconsistent glyph count at line ${lines.size}"
val glyphCount = base.glyphCount()
// Good to recycle the array if the existing array can hold the new layout result.
val glyphIds = IntArray(glyphCount) {
base.getGlyphId(it).also { baseGlyphId ->
require(baseGlyphId == target.getGlyphId(it)) {
"Inconsistent glyph ID at $it in line ${lines.size}"
} }
}
}
val baseX = FloatArray(glyphCount) { base.getGlyphX(it) } val glyphCount = base.glyphCount()
val baseY = FloatArray(glyphCount) { base.getGlyphY(it) }
val targetX = FloatArray(glyphCount) { target.getGlyphX(it) }
val targetY = FloatArray(glyphCount) { target.getGlyphY(it) }
// Calculate font runs // Good to recycle the array if the existing array can hold the new layout
val fontRun = mutableListOf<FontRun>() // result.
if (glyphCount != 0) { val glyphIds =
var start = 0 IntArray(glyphCount) {
var baseFont = base.getFont(start) base.getGlyphId(it).also { baseGlyphId ->
var targetFont = target.getFont(start) require(baseGlyphId == target.getGlyphId(it)) {
require(FontInterpolator.canInterpolate(baseFont, targetFont)) { "Inconsistent glyph ID at $it in line ${lines.size}"
"Cannot interpolate font at $start ($baseFont vs $targetFont)" }
} }
for (i in 1 until glyphCount) {
val nextBaseFont = base.getFont(i)
val nextTargetFont = target.getFont(i)
if (baseFont !== nextBaseFont) {
require(targetFont !== nextTargetFont) {
"Base font has changed at $i but target font has not changed."
} }
// Font transition point. push run and reset context.
fontRun.add(FontRun(start, i, baseFont, targetFont)) val baseX = FloatArray(glyphCount) { base.getGlyphX(it) }
maxRunLength = max(maxRunLength, i - start) val baseY = FloatArray(glyphCount) { base.getGlyphY(it) }
baseFont = nextBaseFont val targetX = FloatArray(glyphCount) { target.getGlyphX(it) }
targetFont = nextTargetFont val targetY = FloatArray(glyphCount) { target.getGlyphY(it) }
start = i
// Calculate font runs
val fontRun = mutableListOf<FontRun>()
if (glyphCount != 0) {
var start = 0
var baseFont = base.getFont(start)
var targetFont = target.getFont(start)
require(FontInterpolator.canInterpolate(baseFont, targetFont)) { require(FontInterpolator.canInterpolate(baseFont, targetFont)) {
"Cannot interpolate font at $start ($baseFont vs $targetFont)" "Cannot interpolate font at $start ($baseFont vs $targetFont)"
} }
} else { // baseFont === nextBaseFont
require(targetFont === nextTargetFont) { for (i in 1 until glyphCount) {
"Base font has not changed at $i but target font has changed." val nextBaseFont = base.getFont(i)
val nextTargetFont = target.getFont(i)
if (baseFont !== nextBaseFont) {
require(targetFont !== nextTargetFont) {
"Base font has changed at $i but target font is unchanged."
}
// Font transition point. push run and reset context.
fontRun.add(FontRun(start, i, baseFont, targetFont))
maxRunLength = max(maxRunLength, i - start)
baseFont = nextBaseFont
targetFont = nextTargetFont
start = i
require(FontInterpolator.canInterpolate(baseFont, targetFont)) {
"Cannot interpolate font at $start" +
" ($baseFont vs $targetFont)"
}
} else { // baseFont === nextBaseFont
require(targetFont === nextTargetFont) {
"Base font is unchanged at $i but target font has changed."
}
}
} }
fontRun.add(FontRun(start, glyphCount, baseFont, targetFont))
maxRunLength = max(maxRunLength, glyphCount - start)
} }
Run(glyphIds, baseX, baseY, targetX, targetY, fontRun)
} }
fontRun.add(FontRun(start, glyphCount, baseFont, targetFont)) Line(runs)
maxRunLength = max(maxRunLength, glyphCount - start)
}
Run(glyphIds, baseX, baseY, targetX, targetY, fontRun)
} }
Line(runs)
}
// Update float array used for drawing. // Update float array used for drawing.
if (tmpPositionArray.size < maxRunLength * 2) { if (tmpPositionArray.size < maxRunLength * 2) {
@@ -360,9 +354,9 @@ class TextInterpolator(
if (glyphFilter == null) { if (glyphFilter == null) {
for (i in run.start until run.end) { for (i in run.start until run.end) {
tmpPositionArray[arrayIndex++] = tmpPositionArray[arrayIndex++] =
MathUtils.lerp(line.baseX[i], line.targetX[i], progress) MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
tmpPositionArray[arrayIndex++] = tmpPositionArray[arrayIndex++] =
MathUtils.lerp(line.baseY[i], line.targetY[i], progress) MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
} }
c.drawGlyphs(line.glyphIds, run.start, tmpPositionArray, 0, run.length, font, paint) c.drawGlyphs(line.glyphIds, run.start, tmpPositionArray, 0, run.length, font, paint)
return return
@@ -391,13 +385,14 @@ class TextInterpolator(
tmpPaintForGlyph.color = tmpGlyph.color tmpPaintForGlyph.color = tmpGlyph.color
c.drawGlyphs( c.drawGlyphs(
line.glyphIds, line.glyphIds,
prevStart, prevStart,
tmpPositionArray, tmpPositionArray,
0, 0,
i - prevStart, i - prevStart,
font, font,
tmpPaintForGlyph) tmpPaintForGlyph
)
prevStart = i prevStart = i
arrayIndex = 0 arrayIndex = 0
} }
@@ -407,13 +402,14 @@ class TextInterpolator(
} }
c.drawGlyphs( c.drawGlyphs(
line.glyphIds, line.glyphIds,
prevStart, prevStart,
tmpPositionArray, tmpPositionArray,
0, 0,
run.end - prevStart, run.end - prevStart,
font, font,
tmpPaintForGlyph) tmpPaintForGlyph
)
} }
private fun updatePositionsAndFonts( private fun updatePositionsAndFonts(
@@ -421,9 +417,7 @@ class TextInterpolator(
updateBase: Boolean updateBase: Boolean
) { ) {
// Update target positions with newly calculated text layout. // Update target positions with newly calculated text layout.
check(layoutResult.size == lines.size) { check(layoutResult.size == lines.size) { "The new layout result has different line count." }
"The new layout result has different line count."
}
lines.zip(layoutResult) { line, runs -> lines.zip(layoutResult) { line, runs ->
line.runs.zip(runs) { lineRun, newGlyphs -> line.runs.zip(runs) { lineRun, newGlyphs ->
@@ -439,7 +433,7 @@ class TextInterpolator(
} }
require(newFont === newGlyphs.getFont(i)) { require(newFont === newGlyphs.getFont(i)) {
"The new layout has different font run." + "The new layout has different font run." +
" $newFont vs ${newGlyphs.getFont(i)} at $i" " $newFont vs ${newGlyphs.getFont(i)} at $i"
} }
} }
@@ -447,7 +441,7 @@ class TextInterpolator(
// check new font can be interpolatable with base font. // check new font can be interpolatable with base font.
require(FontInterpolator.canInterpolate(newFont, run.baseFont)) { require(FontInterpolator.canInterpolate(newFont, run.baseFont)) {
"New font cannot be interpolated with existing font. $newFont," + "New font cannot be interpolated with existing font. $newFont," +
" ${run.baseFont}" " ${run.baseFont}"
} }
if (updateBase) { if (updateBase) {
@@ -483,10 +477,7 @@ class TextInterpolator(
} }
// Shape the text and stores the result to out argument. // Shape the text and stores the result to out argument.
private fun shapeText( private fun shapeText(layout: Layout, paint: TextPaint): List<List<PositionedGlyphs>> {
layout: Layout,
paint: TextPaint
): List<List<PositionedGlyphs>> {
var text = StringBuilder() var text = StringBuilder()
val out = mutableListOf<List<PositionedGlyphs>>() val out = mutableListOf<List<PositionedGlyphs>>()
for (lineNo in 0 until layout.lineCount) { // Shape all lines. for (lineNo in 0 until layout.lineCount) { // Shape all lines.
@@ -500,10 +491,13 @@ class TextInterpolator(
} }
val runs = mutableListOf<PositionedGlyphs>() val runs = mutableListOf<PositionedGlyphs>()
TextShaper.shapeText(layout.text, lineStart, count, layout.textDirectionHeuristic, TextShaper.shapeText(
paint) { _, _, glyphs, _ -> layout.text,
runs.add(glyphs) lineStart,
} count,
layout.textDirectionHeuristic,
paint
) { _, _, glyphs, _ -> runs.add(glyphs) }
out.add(runs) out.add(runs)
if (lineNo > 0) { if (lineNo > 0) {
@@ -517,8 +511,8 @@ class TextInterpolator(
} }
private fun Layout.getDrawOrigin(lineNo: Int) = private fun Layout.getDrawOrigin(lineNo: Int) =
if (getParagraphDirection(lineNo) == Layout.DIR_LEFT_TO_RIGHT) { if (getParagraphDirection(lineNo) == Layout.DIR_LEFT_TO_RIGHT) {
getLineLeft(lineNo) getLineLeft(lineNo)
} else { } else {
getLineRight(lineNo) getLineRight(lineNo)
} }

View File

@@ -1,7 +1,5 @@
+packages/SystemUI +packages/SystemUI
-packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
-packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt -packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
-packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
-packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt -packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
-packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt -packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt
-packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt -packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt