Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bf703ba6 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere Committed by Android (Google) Code Review
Browse files

Merge "Format the Animation library using ktfmt"

parents e8602a37 d72c1329
Loading
Loading
Loading
Loading
+56 −50
Original line number Diff line number Diff line
@@ -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_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 {

    /**
@@ -61,8 +59,11 @@ class FontInterpolator {
        var index: Int,
        val sortedAxes: MutableList<FontVariationAxis>
    ) {
        constructor(font: Font, axes: List<FontVariationAxis>):
                this(font.sourceIdentifier,
        constructor(
            font: Font,
            axes: List<FontVariationAxis>
        ) : this(
            font.sourceIdentifier,
            font.ttcIndex,
            axes.toMutableList().apply { sortBy { it.tag } }
        )
@@ -86,9 +87,7 @@ class FontInterpolator {
    private val tmpInterpKey = InterpKey(null, null, 0f)
    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 {
        if (progress == 0f) {
            return start
@@ -115,19 +114,26 @@ class FontInterpolator {
        // 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
        // and also pre-fill the missing axes value with default value from 'fvar' table.
        val newAxes = lerp(startAxes, endAxes) { tag, startValue, endValue ->
        val newAxes =
            lerp(startAxes, endAxes) { tag, startValue, endValue ->
                when (tag) {
                    // TODO: Good to parse 'fvar' table for retrieving default value.
                TAG_WGHT -> adjustWeight(
                    TAG_WGHT ->
                        adjustWeight(
                            MathUtils.lerp(
                                startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
                                endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
                                progress))
                TAG_ITAL -> adjustItalic(
                                progress
                            )
                        )
                    TAG_ITAL ->
                        adjustItalic(
                            MathUtils.lerp(
                                startValue ?: 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"
@@ -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.
        // Font.Builder#build won't throw IOException since creating fonts from existing fonts will
        // not do any IO work.
        val newFont = Font.Builder(start)
                .setFontVariationSettings(newAxes.toTypedArray())
                .build()
        val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build()
        interpCache[InterpKey(start, end, progress)] = newFont
        verFontCache[VarFontKey(start, newAxes)] = newFont
        return newFont
@@ -173,13 +177,15 @@ class FontInterpolator {
            val tagA = if (i < start.size) start[i].tag else null
            val tagB = if (j < end.size) end[j].tag else null

            val comp = when {
            val comp =
                when {
                    tagA == null -> 1
                    tagB == null -> -1
                    else -> tagA.compareTo(tagB)
                }

            val axis = when {
            val axis =
                when {
                    comp == 0 -> {
                        val v = filter(tagA!!, start[i++].styleValue, end[j++].styleValue)
                        FontVariationAxis(tagA, v)
+72 −88
Original line number Diff line number Diff line
@@ -36,8 +36,7 @@ typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
 * 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:
 *
 * <pre>
 * <code>
 * ```
 *     class SimpleTextAnimation : View {
 *         @JvmOverloads constructor(...)
 *
@@ -53,78 +52,59 @@ typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
 *             animator.setTextStyle(-1 /* unchanged weight */, sizePx, animate)
 *         }
 *     }
 * </code>
 * </pre>
 * ```
 */
class TextAnimator(
    layout: Layout,
    private val invalidateCallback: () -> Unit
) {
class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
    // Following two members are for mutable for testing purposes.
    public var textInterpolator: TextInterpolator = TextInterpolator(layout)
    public var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply {
    public var animator: ValueAnimator =
        ValueAnimator.ofFloat(1f).apply {
            duration = DEFAULT_ANIMATION_DURATION
            addUpdateListener {
                textInterpolator.progress = it.animatedValue as Float
                invalidateCallback()
            }
        addListener(object : AnimatorListenerAdapter() {
            addListener(
                object : AnimatorListenerAdapter() {
                    override fun onAnimationEnd(animation: Animator?) {
                        textInterpolator.rebase()
                    }
                    override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
        })
                }
            )
        }

    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

        /**
         * 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

        /**
         * Mutable text size of the glyph in pixels.
         */
        /** Mutable text size of the glyph in pixels. */
        var textSize: Float = 0f

        /**
         * Mutable color of the glyph.
         */
        /** Mutable color of the glyph. */
        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
            protected set

        /**
         * Immutable run length of the font run.
         */
        /** Immutable run length of the font run. */
        abstract var runLength: Int
            protected set

        /**
         * Immutable glyph index of the font run.
         */
        /** Immutable glyph index of the font run. */
        abstract var glyphIndex: Int
            protected set

        /**
         * Immutable font instance for this font run.
         */
        /** Immutable font instance for this font run. */
        abstract var font: Font
            protected set

        /**
         * Immutable glyph ID for this glyph.
         */
        /** Immutable glyph ID for this glyph. */
        abstract var glyphId: Int
            protected set
    }
@@ -142,30 +122,30 @@ class TextAnimator(
    /**
     * 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.
     * 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
     * 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.
@@ -188,22 +168,23 @@ class TextAnimator(
     */
    var glyphFilter: GlyphCallback?
        get() = textInterpolator.glyphFilter
        set(value) { textInterpolator.glyphFilter = value }
        set(value) {
            textInterpolator.glyphFilter = value
        }

    fun draw(c: Canvas) = textInterpolator.draw(c)

    /**
     * Set text style with animation.
     *
     * By passing -1 to weight, the view preserve the current weight.
     * By passing -1 to textSize, the view preserve the current text size.
     * Bu passing -1 to duration, the default text animation, 1000ms, is used.
     * By passing false to animate, the text will be updated without animation.
     * By passing -1 to weight, the view preserve the current weight. By passing -1 to textSize, the
     * view preserve the current text size. Bu passing -1 to duration, the default text animation,
     * 1000ms, is used. By passing false to animate, the text will be updated without animation.
     *
     * @param weight an optional text weight.
     * @param textSize an optional font size.
     * @param colors an optional colors array that must be the same size as numLines passed to
     *  the TextInterpolator
     * @param colors an optional colors array that must be the same size as numLines passed to the
     * TextInterpolator
     * @param animate an optional boolean indicating true for showing style transition as animation,
     * false for immediate style transition. True by default.
     * @param duration an optional animation duration in milliseconds. This is ignored if animate is
@@ -232,7 +213,8 @@ class TextAnimator(
        if (weight >= 0) {
            // Paint#setFontVariationSettings creates Typeface instance from scratch. To reduce the
            // memory impact, cache the typeface result.
            textInterpolator.targetPaint.typeface = typefaceCache.getOrElse(weight) {
            textInterpolator.targetPaint.typeface =
                typefaceCache.getOrElse(weight) {
                    textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
                    textInterpolator.targetPaint.typeface
                }
@@ -244,14 +226,16 @@ class TextAnimator(

        if (animate) {
            animator.startDelay = delay
            animator.duration = if (duration == -1L) {
            animator.duration =
                if (duration == -1L) {
                    DEFAULT_ANIMATION_DURATION
                } else {
                    duration
                }
            interpolator?.let { animator.interpolator = it }
            if (onAnimationEnd != null) {
                val listener = object : AnimatorListenerAdapter() {
                val listener =
                    object : AnimatorListenerAdapter() {
                        override fun onAnimationEnd(animation: Animator?) {
                            onAnimationEnd.run()
                            animator.removeListener(this)
+114 −122
Original line number Diff line number Diff line
@@ -26,12 +26,8 @@ import android.util.MathUtils
import com.android.internal.graphics.ColorUtils
import java.lang.Math.max

/**
 * Provide text style linear interpolation for plain text.
 */
class TextInterpolator(
    layout: Layout
) {
/** Provide text style linear interpolation for plain text. */
class TextInterpolator(layout: Layout) {

    /**
     * Returns base paint used for interpolation.
@@ -64,12 +60,11 @@ class TextInterpolator(
        var baseFont: 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(
        val glyphIds: IntArray,
        val baseX: FloatArray, // same length as glyphIds
@@ -79,12 +74,8 @@ class TextInterpolator(
        val fontRuns: List<FontRun>
    )

    /**
     * A class represents text layout of a single line.
     */
    private class Line(
        val runs: List<Run>
    )
    /** A class represents text layout of a single line. */
    private class Line(val runs: List<Run>)

    private var lines = listOf<Line>()
    private val fontInterpolator = FontInterpolator()
@@ -106,8 +97,8 @@ class TextInterpolator(
    /**
     * The layout used for drawing text.
     *
     * Only non-styled text is supported. Even if the given layout is created from Spanned, the
     * span information is not used.
     * Only non-styled text is supported. Even if the given layout is created from Spanned, the span
     * information is not used.
     *
     * The paint objects used for interpolation are not changed by this method call.
     *
@@ -130,8 +121,8 @@ class TextInterpolator(
    /**
     * Recalculate internal text layout for interpolation.
     *
     * Whenever the target paint is modified, call this method to recalculate internal
     * text layout used for interpolation.
     * Whenever the target paint is modified, call this method to recalculate internal text layout
     * used for interpolation.
     */
    fun onTargetPaintModified() {
        updatePositionsAndFonts(shapeText(layout, targetPaint), updateBase = false)
@@ -140,8 +131,8 @@ class TextInterpolator(
    /**
     * Recalculate internal text layout for interpolation.
     *
     * Whenever the base paint is modified, call this method to recalculate internal
     * text layout used for interpolation.
     * Whenever the base paint is modified, call this method to recalculate internal text layout
     * used for interpolation.
     */
    fun onBasePaintModified() {
        updatePositionsAndFonts(shapeText(layout, basePaint), updateBase = true)
@@ -152,11 +143,11 @@ class TextInterpolator(
     *
     * 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
     * calculate text position of the middle state by linear interpolation. Due to this trick,
     * the text positions of the middle state is likely different from the text shaper result.
     * So, if you want to start animation from the middle state, you will see the glyph jumps due to
     * this trick, i.e. the progress 0.5 of interpolation between weight 400 and 700 is different
     * from text shape result of weight 550.
     * calculate text position of the middle state by linear interpolation. Due to this trick, the
     * text positions of the middle state is likely different from the text shaper result. So, if
     * you want to start animation from the middle state, you will see the glyph jumps due to this
     * trick, i.e. the progress 0.5 of interpolation between weight 400 and 700 is different from
     * text shape result of weight 550.
     *
     * 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
@@ -168,8 +159,7 @@ class TextInterpolator(
     * 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
     *
     * <pre>
     * <code>
     * ```
     *     val interp = TextInterpolator(layout)
     *
     *     // Interpolate between weight 200 to 400.
@@ -199,9 +189,7 @@ class TextInterpolator(
     *         // progress is 0.5
     *         animator.start()
     *     }
     * </code>
     * </pre>
     *
     * ```
     */
    fun rebase() {
        if (progress == 0f) {
@@ -243,9 +231,7 @@ class TextInterpolator(
                    val origin = layout.getDrawOrigin(lineNo)
                    canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat())

                    run.fontRuns.forEach { fontRun ->
                        drawFontRun(canvas, run, fontRun, tmpPaint)
                    }
                    run.fontRuns.forEach { fontRun -> drawFontRun(canvas, run, fontRun, tmpPaint) }
                } finally {
                    canvas.restore()
                }
@@ -263,17 +249,20 @@ class TextInterpolator(
        }

        var maxRunLength = 0
        lines = baseLayout.zip(targetLayout) { baseLine, targetLine ->
            val runs = baseLine.zip(targetLine) { base, target ->

        lines =
            baseLayout.zip(targetLayout) { baseLine, targetLine ->
                val runs =
                    baseLine.zip(targetLine) { base, target ->
                        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) {
                        // 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}"
@@ -302,7 +291,8 @@ class TextInterpolator(

                                if (baseFont !== nextBaseFont) {
                                    require(targetFont !== nextTargetFont) {
                                "Base font has changed at $i but target font has not changed."
                                        "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))
@@ -311,11 +301,13 @@ class TextInterpolator(
                                    targetFont = nextTargetFont
                                    start = i
                                    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) {
                                "Base font has not changed at $i but target font has changed."
                                        "Base font has not changed at $i but target font has " +
                                            "changed."
                                    }
                                }
                            }
@@ -392,7 +384,8 @@ class TextInterpolator(
                    0,
                    i - prevStart,
                    font,
                        tmpPaintForGlyph)
                    tmpPaintForGlyph
                )
                prevStart = i
                arrayIndex = 0
            }
@@ -408,7 +401,8 @@ class TextInterpolator(
            0,
            run.end - prevStart,
            font,
                tmpPaintForGlyph)
            tmpPaintForGlyph
        )
    }

    private fun updatePositionsAndFonts(
@@ -416,9 +410,7 @@ class TextInterpolator(
        updateBase: Boolean
    ) {
        // Update target positions with newly calculated text layout.
        check(layoutResult.size == lines.size) {
            "The new layout result has different line count."
        }
        check(layoutResult.size == lines.size) { "The new layout result has different line count." }

        lines.zip(layoutResult) { line, runs ->
            line.runs.zip(runs) { lineRun, newGlyphs ->
@@ -478,19 +470,19 @@ class TextInterpolator(
    }

    // Shape the text and stores the result to out argument.
    private fun shapeText(
        layout: Layout,
        paint: TextPaint
    ): List<List<PositionedGlyphs>> {
    private fun shapeText(layout: Layout, paint: TextPaint): List<List<PositionedGlyphs>> {
        val out = mutableListOf<List<PositionedGlyphs>>()
        for (lineNo in 0 until layout.lineCount) { // Shape all lines.
            val lineStart = layout.getLineStart(lineNo)
            val count = layout.getLineEnd(lineNo) - lineStart
            val runs = mutableListOf<PositionedGlyphs>()
            TextShaper.shapeText(layout.text, lineStart, count, layout.textDirectionHeuristic,
                    paint) { _, _, glyphs, _ ->
                runs.add(glyphs)
            }
            TextShaper.shapeText(
                layout.text,
                lineStart,
                count,
                layout.textDirectionHeuristic,
                paint
            ) { _, _, glyphs, _ -> runs.add(glyphs) }
            out.add(runs)
        }
        return out