Skip to content

Commit

Permalink
js expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
Zhirkevich Alexander Y authored and Zhirkevich Alexander Y committed Jul 9, 2024
1 parent e67a454 commit 9dc58e7
Show file tree
Hide file tree
Showing 38 changed files with 805 additions and 343 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import androidx.compose.ui.graphics.Color
import io.github.alexzhirkevich.compottie.dynamic.DynamicTextLayerProvider
import io.github.alexzhirkevich.compottie.dynamic.derive
import io.github.alexzhirkevich.compottie.internal.AnimationState
import io.github.alexzhirkevich.compottie.internal.animation.expressions.ExpressionEvaluator
import io.github.alexzhirkevich.compottie.internal.animation.expressions.RawExpressionEvaluator.evaluate
import io.github.alexzhirkevich.compottie.internal.helpers.text.TextDocument
import io.github.alexzhirkevich.compottie.internal.utils.toOffset
import io.github.alexzhirkevich.compottie.internal.utils.toSize
Expand All @@ -32,8 +34,10 @@ internal class AnimatedTextDocument(

private val document = TextDocument()

private val evaluator = expression?.let(::ExpressionEvaluator)

@Transient
var dynamic :DynamicTextLayerProvider? = null
var dynamic : DynamicTextLayerProvider? = null

private val fillColorList by lazy {
ArrayList<Float>(4)
Expand Down Expand Up @@ -70,6 +74,8 @@ internal class AnimatedTextDocument(
override fun interpolated(state: AnimationState): TextDocument {
val raw = delegate.raw(state)

val evaluatedText = evaluator?.run { evaluate(state) } as? String ?: raw.text

return document.apply {
fontFamily = raw.fontFamily
fillColor = dynamic?.fillColor?.let {
Expand Down Expand Up @@ -100,7 +106,7 @@ internal class AnimatedTextDocument(
positionList
}
} ?: raw.wrapPosition
text = dynamic?.text.derive(raw.text.orEmpty(), state)
text = dynamic?.text.derive(evaluatedText.orEmpty(), state)
textJustify = dynamic?.textJustify.derive(raw.textJustify, state)
textTracking = dynamic?.tracking.derive(raw.textTracking ?: 0f, state)
baselineShift =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,11 @@ internal fun interface Expression {
state: AnimationState
): Any

object UndefinedExpression: Expression {
override fun invoke(
property: RawProperty<Any>,
context: EvaluationContext,
state: AnimationState,
) = Undefined
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import io.github.alexzhirkevich.compottie.internal.animation.expressions.operati

internal interface ExpressionContext<T> : Expression {

fun interpret(op: String, args: List<Expression>): Expression?
fun interpret(op: String?, args: List<Expression>): Expression?

fun withContext(
block: T.(
Expand Down Expand Up @@ -39,16 +39,38 @@ internal interface ExpressionContext<T> : Expression {
}
}

internal fun List<Expression>.getForNameOrIndex(
internal fun List<Expression>.argForNameOrIndex(
index : Int,
vararg name : String,
) : Expression? {

forEach { op ->
if (op is OpAssign && name.any { op.variableName == it } ) {
if (op is OpAssign && name.any { op.variableName == it }) {
return op.assignableValue
}
}

return getOrNull(index)
return argAtOrNull(index)
}

internal fun List<Expression>.argAt(
index : Int,
) : Expression {

return get(index).let {
if (it is OpAssign)
it.assignableValue
else it
}
}

internal fun List<Expression>.argAtOrNull(
index : Int,
) : Expression? {

return getOrNull(index).let {
if (it is OpAssign)
it.assignableValue
else it
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,19 @@ private class ExpressionEvaluatorImpl(expr : String) : ExpressionEvaluator {

private val expression: Expression = MainExpressionInterpreter(expr).interpret()

private var warned : Boolean = false

override fun RawProperty<*>.evaluate(state: AnimationState): Any {
return try {
if (state.enableExpressions) {
return if (state.enableExpressions) {
try {
context.reset()
expression.invoke(this, context, state)
context.result.toListOrThis()
} else {
raw(state)
}
} catch (t: Throwable) {
if (!warned){
warned = true
} catch (t: Throwable) {
Compottie.logger?.warn(
"Error occurred in a Lottie expression. Try disable expressions for Painter using enableExpressions=false: ${t.message}"
"Error occurred in a Lottie expression. Try to disable expressions for Painter using enableExpressions=false: ${t.message}"
)
raw(state)
}
} else {
raw(state)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import io.github.alexzhirkevich.compottie.internal.animation.expressions.operati
import io.github.alexzhirkevich.compottie.internal.animation.expressions.operations.condition.OpEquals
import io.github.alexzhirkevich.compottie.internal.animation.expressions.operations.value.OpGetVariable
import io.github.alexzhirkevich.compottie.internal.animation.expressions.operations.OpGlobalContext
import io.github.alexzhirkevich.compottie.internal.animation.expressions.operations.js.JsContext
import io.github.alexzhirkevich.compottie.internal.animation.expressions.operations.condition.OpIfCondition
import io.github.alexzhirkevich.compottie.internal.animation.expressions.operations.value.OpIndex
import io.github.alexzhirkevich.compottie.internal.animation.expressions.operations.value.OpMakeArray
Expand Down Expand Up @@ -303,50 +304,63 @@ internal class SingleExpressionInterpreter(
} while (ch.isFun())

val func = expr.substring(startPos, pos)
val args = buildList {
when {
eat('(') -> {
if (eat(')')){
return@buildList //empty args
}
do {
add(parseAssignment(OpGlobalContext))
} while (eat(','))

require(eat(')')) {
"Bad expression:Missing ')' after argument to $func"
}
}

parseFunction(context, func)
}

else -> error("Unsupported Lottie expression: $expr")
}
}

private fun parseFunction(context: Expression, func : String?) : Expression {
val args = buildList {
when {
eat('(') -> {
if (eat(')')){
return@buildList //empty args
}
}
if (EXPR_DEBUG_PRINT_ENABLED) {
println("making fun $func")
}
do {
add(parseAssignment(OpGlobalContext))
} while (eat(','))

val parsedOp = when (context) {
is ExpressionContext<*> -> context.interpret(func, args)
?: unresolvedReference(
ref = func,
obj = context::class.simpleName
?.substringAfter("Op")
?.substringBefore("Context")
)
else -> error("Unsupported Lottie expression function: $func")
require(eat(')')) {
"Bad expression:Missing ')' after argument to $func"
}
}
}
}
if (EXPR_DEBUG_PRINT_ENABLED) {
println("making fun $func")
}

when {
// begin condition || property || index
parsedOp is OpVar ||
parsedOp is OpIfCondition
|| eat('.')
|| nextCharIs('['::equals) ->
parseFactorOp(parsedOp) // continue with receiver
val parsedOp = when (context) {
is ExpressionContext<*> -> context.interpret(func, args)
?: unresolvedReference(
ref = func ?: "null",
obj = context::class.simpleName
?.substringAfter("Op")
?.substringBefore("Context")
)

else -> {
JsContext.interpret(context, func, args)
?: error("Unsupported Lottie expression function: $func")
}
}

else -> parsedOp
}
return when {
// inplace function invocation
parsedOp is ExpressionContext<*> && nextCharIs { it == '(' } -> {
parseFunction(parsedOp, null)
}
// begin condition || property || index
parsedOp is OpVar ||
parsedOp is OpIfCondition
|| eat('.')
|| nextCharIs('['::equals) ->
parseFactorOp(parsedOp) // continue with receiver

else -> error("Unsupported Lottie expression: $expr")
else -> parsedOp
}
}

Expand Down
Loading

0 comments on commit 9dc58e7

Please sign in to comment.