Support expressions for scalar values

Change-Id: I87c1ca4dd6d6f2e1e591eedc97ee7cb088039eab
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/436117
Commit-Queue: Avinash Parchuri <aparchur@google.com>
Auto-Submit: Kyle Carlstrom <kylecarlstrom@google.com>
Reviewed-by: Avinash Parchuri <aparchur@google.com>
Reviewed-by: Florin Malita <fmalita@chromium.org>
diff --git a/modules/skottie/BUILD.gn b/modules/skottie/BUILD.gn
index de79d0a..0f67f3f 100644
--- a/modules/skottie/BUILD.gn
+++ b/modules/skottie/BUILD.gn
@@ -55,6 +55,7 @@
         sources = [
           "src/SkottieTest.cpp",
           "tests/AudioLayer.cpp",
+          "tests/Expression.cpp",
           "tests/Image.cpp",
           "tests/Keyframe.cpp",
           "tests/Text.cpp",
diff --git a/modules/skottie/include/Skottie.h b/modules/skottie/include/Skottie.h
index eae0277..cc4c92a 100644
--- a/modules/skottie/include/Skottie.h
+++ b/modules/skottie/include/Skottie.h
@@ -53,6 +53,29 @@
     virtual void log(Level, const char message[], const char* json = nullptr);
 };
 
+// Evaluates AE expressions.
+template <class T>
+class SK_API ExpressionEvaluator : public SkRefCnt {
+public:
+    // Evaluate the expression at the current time.
+    virtual T evaluate(float t) = 0;
+};
+
+/**
+ * Creates ExpressionEvaluators to evaluate AE expressions and return the results.
+ */
+class SK_API ExpressionManager : public SkRefCnt {
+public:
+    virtual sk_sp<ExpressionEvaluator<float>> createNumberExpressionEvaluator(
+        const char expression[]) = 0;
+
+    virtual sk_sp<ExpressionEvaluator<SkString>> createStringExpressionEvaluator(
+        const char expression[]) = 0;
+
+    virtual sk_sp<ExpressionEvaluator<std::vector<float>>> createArrayExpressionEvaluator(
+        const char expression[]) = 0;
+};
+
 /**
  * Interface for receiving AE composition markers at Animation build time.
  */
@@ -127,6 +150,12 @@
         Builder& setPrecompInterceptor(sk_sp<PrecompInterceptor>);
 
         /**
+         * Registers an ExpressionManager to evaluate AE expressions.
+         * If unspecified, expressions in the animation JSON will be ignored.
+         */
+        Builder& setExpressionManager(sk_sp<ExpressionManager>);
+
+        /**
          * Animation factories.
          */
         sk_sp<Animation> make(SkStream*);
@@ -142,6 +171,7 @@
         sk_sp<Logger>             fLogger;
         sk_sp<MarkerObserver  >   fMarkerObserver;
         sk_sp<PrecompInterceptor> fPrecompInterceptor;
+        sk_sp<ExpressionManager>  fExpressionManager;
         Stats                     fStats;
     };
 
diff --git a/modules/skottie/src/Skottie.cpp b/modules/skottie/src/Skottie.cpp
index 68b50e3..f1e62dd 100644
--- a/modules/skottie/src/Skottie.cpp
+++ b/modules/skottie/src/Skottie.cpp
@@ -161,6 +161,7 @@
 AnimationBuilder::AnimationBuilder(sk_sp<ResourceProvider> rp, sk_sp<SkFontMgr> fontmgr,
                                    sk_sp<PropertyObserver> pobserver, sk_sp<Logger> logger,
                                    sk_sp<MarkerObserver> mobserver, sk_sp<PrecompInterceptor> pi,
+                                   sk_sp<ExpressionManager> expressionmgr,
                                    Animation::Builder::Stats* stats,
                                    const SkSize& comp_size, float duration, float framerate,
                                    uint32_t flags)
@@ -170,6 +171,7 @@
     , fLogger(std::move(logger))
     , fMarkerObserver(std::move(mobserver))
     , fPrecompInterceptor(std::move(pi))
+    , fExpressionManager(std::move(expressionmgr))
     , fStats(stats)
     , fCompSize(comp_size)
     , fDuration(duration)
@@ -290,6 +292,10 @@
     return dispatched;
 }
 
+sk_sp<ExpressionManager> AnimationBuilder::expression_manager() const {
+    return fExpressionManager;
+}
+
 void AnimationBuilder::AutoPropertyTracker::updateContext(PropertyObserver* observer,
                                                           const skjson::ObjectValue& obj) {
 
@@ -335,6 +341,11 @@
     return *this;
 }
 
+Animation::Builder& Animation::Builder::setExpressionManager(sk_sp<ExpressionManager> em) {
+    fExpressionManager = std::move(em);
+    return *this;
+}
+
 sk_sp<Animation> Animation::Builder::make(SkStream* stream) {
     if (!stream->hasLength()) {
         // TODO: handle explicit buffering?
@@ -409,6 +420,7 @@
                                        std::move(fLogger),
                                        std::move(fMarkerObserver),
                                        std::move(fPrecompInterceptor),
+                                       std::move(fExpressionManager),
                                        &fStats, size, duration, fps, fFlags);
     auto ainfo = builder.parse(json);
 
diff --git a/modules/skottie/src/SkottiePriv.h b/modules/skottie/src/SkottiePriv.h
index 9b5036a..4e94383 100644
--- a/modules/skottie/src/SkottiePriv.h
+++ b/modules/skottie/src/SkottiePriv.h
@@ -53,6 +53,7 @@
 public:
     AnimationBuilder(sk_sp<ResourceProvider>, sk_sp<SkFontMgr>, sk_sp<PropertyObserver>,
                      sk_sp<Logger>, sk_sp<MarkerObserver>, sk_sp<PrecompInterceptor>,
+                     sk_sp<ExpressionManager>,
                      Animation::Builder::Stats*, const SkSize& comp_size,
                      float duration, float framerate, uint32_t flags);
 
@@ -175,6 +176,8 @@
     bool dispatchTextProperty(const sk_sp<TextAdapter>&) const;
     bool dispatchTransformProperty(const sk_sp<TransformAdapter2D>&) const;
 
+    sk_sp<ExpressionManager> expression_manager() const;
+
 private:
     friend class CompositionBuilder;
     friend class LayerBuilder;
@@ -237,6 +240,7 @@
     sk_sp<Logger>              fLogger;
     sk_sp<MarkerObserver>      fMarkerObserver;
     sk_sp<PrecompInterceptor>  fPrecompInterceptor;
+    sk_sp<ExpressionManager>   fExpressionManager;
     Animation::Builder::Stats* fStats;
     const SkSize               fCompSize;
     const float                fDuration,
diff --git a/modules/skottie/src/animator/Animator.cpp b/modules/skottie/src/animator/Animator.cpp
index dc6ff08..8fdd5da 100644
--- a/modules/skottie/src/animator/Animator.cpp
+++ b/modules/skottie/src/animator/Animator.cpp
@@ -54,13 +54,25 @@
         return false;
     }
 
+    // Handle expressions on the property.
+    if (const skjson::StringValue* expr = (*jprop)["x"]) {
+        if (!abuilder.expression_manager()) {
+            abuilder.log(Logger::Level::kWarning, jprop,
+                         "Expression encountered but ExpressionManager not provided.");
+        } else {
+            sk_sp<Animator> expression_animator = builder.makeFromExpression(
+                                                    *abuilder.expression_manager(),
+                                                    expr->begin());
+            if (expression_animator) {
+                fAnimators.push_back(std::move(expression_animator));
+                return true;
+            }
+        }
+    }
+
     const auto& jpropA = (*jprop)["a"];
     const auto& jpropK = (*jprop)["k"];
 
-    if (!(*jprop)["x"].is<skjson::NullValue>()) {
-        abuilder.log(Logger::Level::kWarning, nullptr, "Unsupported expression.");
-    }
-
     // Older Json versions don't have an "a" animation marker.
     // For those, we attempt to parse both ways.
     if (!ParseDefault<bool>(jpropA, false)) {
diff --git a/modules/skottie/src/animator/KeyframeAnimator.h b/modules/skottie/src/animator/KeyframeAnimator.h
index 6196f7c..1fbac4c 100644
--- a/modules/skottie/src/animator/KeyframeAnimator.h
+++ b/modules/skottie/src/animator/KeyframeAnimator.h
@@ -11,6 +11,7 @@
 #include "include/core/SkCubicMap.h"
 #include "include/core/SkPoint.h"
 #include "include/private/SkNoncopyable.h"
+#include "modules/skottie/include/Skottie.h"
 #include "modules/skottie/src/animator/Animator.h"
 
 #include <vector>
@@ -108,7 +109,10 @@
 public:
     virtual ~AnimatorBuilder();
 
-    virtual sk_sp<KeyframeAnimator> makeFromKeyframes(const AnimationBuilder&, const skjson::ArrayValue&) = 0;
+    virtual sk_sp<KeyframeAnimator> makeFromKeyframes(const AnimationBuilder&,
+                                                      const skjson::ArrayValue&) = 0;
+
+    virtual sk_sp<Animator> makeFromExpression(ExpressionManager&, const char*) = 0;
 
     virtual bool parseValue(const AnimationBuilder&, const skjson::Value&) const = 0;
 
diff --git a/modules/skottie/src/animator/ScalarKeyframeAnimator.cpp b/modules/skottie/src/animator/ScalarKeyframeAnimator.cpp
index 5e4dbe7..f98fbf3 100644
--- a/modules/skottie/src/animator/ScalarKeyframeAnimator.cpp
+++ b/modules/skottie/src/animator/ScalarKeyframeAnimator.cpp
@@ -39,6 +39,28 @@
     using INHERITED = KeyframeAnimator;
 };
 
+    // Scalar specialization: stores scalar values (floats).
+class ScalarExpressionAnimator final : public Animator {
+public:
+    ScalarExpressionAnimator(sk_sp<ExpressionEvaluator<ScalarValue>> expression_evaluator,
+        ScalarValue* target_value)
+        : fExpressionEvaluator(std::move(expression_evaluator))
+        , fTarget(target_value) {}
+
+private:
+
+    StateChanged onSeek(float t) override {
+        auto old_value = *fTarget;
+
+        *fTarget = fExpressionEvaluator->evaluate(t);
+
+        return *fTarget != old_value;
+    }
+
+    sk_sp<ExpressionEvaluator<ScalarValue>> fExpressionEvaluator;
+    ScalarValue* fTarget;
+};
+
 class ScalarAnimatorBuilder final : public AnimatorBuilder {
     public:
         explicit ScalarAnimatorBuilder(ScalarValue* target) : fTarget(target) {}
@@ -54,6 +76,13 @@
                         new ScalarKeyframeAnimator(std::move(fKFs), std::move(fCMs), fTarget));
         }
 
+        sk_sp<Animator> makeFromExpression(ExpressionManager& em, const char* expr) override {
+            sk_sp<ExpressionEvaluator<ScalarValue>> expression_evaluator =
+                em.createNumberExpressionEvaluator(expr);
+            return sk_make_sp<ScalarExpressionAnimator>(expression_evaluator, fTarget);
+        }
+
+
         bool parseValue(const AnimationBuilder&, const skjson::Value& jv) const override {
             return Parse(jv, fTarget);
         }
diff --git a/modules/skottie/src/animator/TextKeyframeAnimator.cpp b/modules/skottie/src/animator/TextKeyframeAnimator.cpp
index 6c031da..47582ae 100644
--- a/modules/skottie/src/animator/TextKeyframeAnimator.cpp
+++ b/modules/skottie/src/animator/TextKeyframeAnimator.cpp
@@ -61,6 +61,10 @@
                                                 fTarget));
     }
 
+    sk_sp<Animator> makeFromExpression(ExpressionManager&, const char*) override {
+        return nullptr;
+    }
+
     bool parseValue(const AnimationBuilder& abuilder, const skjson::Value& jv) const override {
         return Parse(jv, abuilder, fTarget);
     }
diff --git a/modules/skottie/src/animator/Vec2KeyframeAnimator.cpp b/modules/skottie/src/animator/Vec2KeyframeAnimator.cpp
index f328dda..0652ce6 100644
--- a/modules/skottie/src/animator/Vec2KeyframeAnimator.cpp
+++ b/modules/skottie/src/animator/Vec2KeyframeAnimator.cpp
@@ -125,6 +125,10 @@
                                                  fRotTarget));
         }
 
+        sk_sp<Animator> makeFromExpression(ExpressionManager&, const char*) override {
+            return nullptr;
+        }
+
         bool parseValue(const AnimationBuilder&, const skjson::Value& jv) const override {
             return Parse(jv, fVecTarget);
         }
diff --git a/modules/skottie/src/animator/VectorKeyframeAnimator.cpp b/modules/skottie/src/animator/VectorKeyframeAnimator.cpp
index 56f7d79..2fc197a 100644
--- a/modules/skottie/src/animator/VectorKeyframeAnimator.cpp
+++ b/modules/skottie/src/animator/VectorKeyframeAnimator.cpp
@@ -186,6 +186,10 @@
                                            fTarget));
 }
 
+sk_sp<Animator> VectorAnimatorBuilder::makeFromExpression(ExpressionManager&, const char*) {
+    return nullptr;
+}
+
 bool VectorAnimatorBuilder::parseValue(const AnimationBuilder&,
                                                const skjson::Value& jv) const {
     size_t vec_len;
diff --git a/modules/skottie/src/animator/VectorKeyframeAnimator.h b/modules/skottie/src/animator/VectorKeyframeAnimator.h
index d253134..d4ba2d6 100644
--- a/modules/skottie/src/animator/VectorKeyframeAnimator.h
+++ b/modules/skottie/src/animator/VectorKeyframeAnimator.h
@@ -21,7 +21,10 @@
 
     VectorAnimatorBuilder(std::vector<float>*, VectorLenParser, VectorDataParser);
 
-    sk_sp<KeyframeAnimator> makeFromKeyframes(const AnimationBuilder&, const skjson::ArrayValue&) override;
+    sk_sp<KeyframeAnimator> makeFromKeyframes(const AnimationBuilder&,
+                                              const skjson::ArrayValue&) override;
+
+    sk_sp<Animator> makeFromExpression(ExpressionManager&, const char*) override;
 
 private:
     bool parseValue(const AnimationBuilder&, const skjson::Value&) const override;
diff --git a/modules/skottie/tests/Expression.cpp b/modules/skottie/tests/Expression.cpp
new file mode 100644
index 0000000..29d2177
--- /dev/null
+++ b/modules/skottie/tests/Expression.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "modules/skottie/include/Skottie.h"
+#include "modules/skottie/include/SkottieProperty.h"
+#include "tests/Test.h"
+#include <iostream>
+
+using namespace skottie;
+
+namespace {
+
+class FakeScalarExpressionEvaluator : public ExpressionEvaluator<float> {
+public:
+    float evaluate(float t) override {
+        return 7.0f;
+    }
+};
+
+class FakeExpressionManager : public ExpressionManager {
+public:
+    sk_sp<ExpressionEvaluator<float>> createNumberExpressionEvaluator(
+        const char expression[]) override {
+        return sk_make_sp<FakeScalarExpressionEvaluator>();
+    }
+
+    sk_sp<ExpressionEvaluator<SkString>> createStringExpressionEvaluator(
+        const char expression[]) override {
+        return nullptr;
+    }
+
+    sk_sp<ExpressionEvaluator<std::vector<float>>> createArrayExpressionEvaluator(
+        const char expression[]) override {
+        return nullptr;
+    }
+};
+
+class FakePropertyObserver : public PropertyObserver {
+    public:
+        void onOpacityProperty(const char node_name[],
+                                     const LazyHandle<OpacityPropertyHandle>& opacity_handle) override {
+            opacity_handle_.reset(opacity_handle().release());
+        }
+
+    std::unique_ptr<OpacityPropertyHandle> opacity_handle_;
+};
+} // namespace
+
+DEF_TEST(Skottie_ScalarExpression, r) {
+    static constexpr char json[] =
+        R"({
+             "v": "5.2.1",
+             "w": 100,
+             "h": 100,
+             "fr": 10,
+             "ip": 0,
+             "op": 100,
+             "layers": [
+               {
+                 "ip": 0,
+                 "op": 100,
+                 "ty": 1,
+                 "nm": "My Layer",
+                 "sr": 1,
+                 "ks": {
+                   "o": {
+                     "a": 0,
+                     "k": 100,
+                     "ix": 11,
+                     "x": "fake; return value is specified by the FakeScalarExpressionEvaluator."
+                   },
+                    "r": {
+                        "a": 0,
+                        "k": 0,
+                        "ix": 10
+                    },
+                    "p": {
+                        "a": 0,
+                        "k": [
+                            50,
+                            50,
+                            0
+                        ],
+                        "ix": 2,
+                        "l": 2
+                    },
+                    "a": {
+                        "a": 0,
+                        "k": [
+                            50,
+                            50,
+                            0
+                        ],
+                        "ix": 1,
+                        "l": 2
+                    },
+                    "s": {
+                        "a": 0,
+                        "k": [
+                            100,
+                            100,
+                            100
+                        ],
+                        "ix": 6,
+                        "l": 2
+                    }
+                },
+                "ao": 0,
+                "sw": 100,
+                "sh": 100,
+                "sc": "#51251d",
+                "st": 0,
+                "bm": 0
+               }
+             ]
+           })";
+
+    SkMemoryStream stream(json, strlen(json));
+
+    auto em = sk_make_sp<FakeExpressionManager>();
+    auto observer = sk_make_sp<FakePropertyObserver>();
+
+    auto anim = Animation::Builder()
+        .setExpressionManager(em)
+        .setPropertyObserver(observer)
+        .make(&stream);
+
+    REPORTER_ASSERT(r, anim);
+
+    anim->seekFrameTime(0);
+
+    REPORTER_ASSERT(r, SkScalarNearlyEqual(observer->opacity_handle_->get(), 7.0f));
+}
diff --git a/modules/skottie/tests/Keyframe.cpp b/modules/skottie/tests/Keyframe.cpp
index abd98c0..b534fc8 100644
--- a/modules/skottie/tests/Keyframe.cpp
+++ b/modules/skottie/tests/Keyframe.cpp
@@ -23,7 +23,7 @@
 class MockProperty final : public AnimatablePropertyContainer {
 public:
     explicit MockProperty(const char* jprop) {
-        AnimationBuilder abuilder(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+        AnimationBuilder abuilder(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
                                   {100, 100}, 10, 1, 0);
         skjson::DOM json_dom(jprop, strlen(jprop));