[skottie] Plumb external SkFontMgr

Allow embedders to pass a font manager.

In order to avoid excessive factory API clutter, introduce an
Animation::Builder helper to wrap factory options.

Also clean up various bits:

  * hoist scene parsing out of the Animation ctor
  * store the animation duration explicitly (instead of unused fps)
  * plumb const SkFontMgr& internally (instead of a ref)

Change-Id: I3e180dfa85ba18c8462cfeb5a7385bef985ed6c4
Reviewed-on: https://skia-review.googlesource.com/148800
Reviewed-by: Ben Wagner <bungeman@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/modules/skottie/include/Skottie.h b/modules/skottie/include/Skottie.h
index 2375a2e..95d50c0 100644
--- a/modules/skottie/include/Skottie.h
+++ b/modules/skottie/include/Skottie.h
@@ -8,6 +8,7 @@
 #ifndef Skottie_DEFINED
 #define Skottie_DEFINED
 
+#include "SkFontMgr.h"
 #include "SkRefCnt.h"
 #include "SkSize.h"
 #include "SkString.h"
@@ -37,19 +38,56 @@
 
 class SK_API Animation : public SkRefCnt {
 public:
-    struct Stats {
-        float  fTotalLoadTimeMS,
-               fJsonParseTimeMS,
-               fSceneParseTimeMS;
-        size_t fJsonSize,
-               fAnimatorCount;
+
+    class Builder final {
+    public:
+        struct Stats {
+            float  fTotalLoadTimeMS  = 0, // Total animation instantiation time.
+                   fJsonParseTimeMS  = 0, // Time spent building a JSON DOM.
+                   fSceneParseTimeMS = 0; // Time spent constructing the animation scene graph.
+            size_t fJsonSize         = 0, // Input JSON size.
+                   fAnimatorCount    = 0; // Number of dynamically animated properties.
+        };
+
+        /**
+         * Returns various animation build stats.
+         *
+         * @return Stats (see above).
+         */
+        const Stats& getStats() const { return fStats; }
+
+        /**
+         * Specify a loader for external resources (images, etc.).  Ownership stays with the caller
+         * (the ResrouceProvider must be valid for this builder's lifespan).
+         */
+        Builder& setResourceProvider(const ResourceProvider*);
+
+        /**
+         * Specify a font manager for loading animation fonts.
+         */
+        Builder& setFontManager(sk_sp<SkFontMgr>);
+
+        /**
+         * Animation factories.
+         */
+        sk_sp<Animation> make(SkStream*);
+        sk_sp<Animation> make(const char* data, size_t length);
+        sk_sp<Animation> makeFromFile(const char path[]);
+
+    private:
+        const ResourceProvider* fResourceProvider = nullptr;
+        sk_sp<SkFontMgr>        fFontMgr;
+        Stats                   fStats;
     };
 
-    static sk_sp<Animation> Make(const char* data, size_t length,
-                                 const ResourceProvider* = nullptr, Stats* = nullptr);
-    static sk_sp<Animation> Make(SkStream*, const ResourceProvider* = nullptr, Stats* = nullptr);
-    static sk_sp<Animation> MakeFromFile(const char path[], const ResourceProvider* = nullptr,
-                                         Stats* = nullptr);
+    /**
+     * Animation factories.
+     *
+     * Use the Builder helper above for more options/control.
+     */
+    static sk_sp<Animation> Make(const char* data, size_t length);
+    static sk_sp<Animation> Make(SkStream*);
+    static sk_sp<Animation> MakeFromFile(const char path[]);
 
     ~Animation() override;
 
@@ -72,9 +110,7 @@
     /**
      * Returns the animation duration in seconds.
      */
-    SkScalar duration() const {
-        return (fOutPoint - fInPoint) / fFrameRate;
-    }
+    SkScalar duration() const { return fDuration; }
 
     const SkString& version() const { return fVersion;   }
     const SkSize&      size() const { return fSize;      }
@@ -82,16 +118,15 @@
     void setShowInval(bool show);
 
 private:
-    Animation(const ResourceProvider&, SkString ver, const SkSize& size, SkScalar fps,
-              SkScalar in, SkScalar out, const skjson::ObjectValue&, Stats*);
-
-    SkString                     fVersion;
-    SkSize                       fSize;
-    SkScalar                     fFrameRate,
-                                 fInPoint,
-                                 fOutPoint;
+    Animation(std::unique_ptr<sksg::Scene>, SkString ver, const SkSize& size,
+              SkScalar inPoint, SkScalar outPoint, SkScalar duration);
 
     std::unique_ptr<sksg::Scene> fScene;
+    const SkString               fVersion;
+    const SkSize                 fSize;
+    const SkScalar               fInPoint,
+                                 fOutPoint,
+                                 fDuration;
 
     typedef SkRefCnt INHERITED;
 };
diff --git a/modules/skottie/src/Skottie.cpp b/modules/skottie/src/Skottie.cpp
index da1f835..75b0c4e 100644
--- a/modules/skottie/src/Skottie.cpp
+++ b/modules/skottie/src/Skottie.cpp
@@ -130,7 +130,7 @@
 }
 
 AnimationBuilder::AnimationBuilder(const ResourceProvider& rp, sk_sp<SkFontMgr> fontmgr,
-                                  Animation::Stats* stats, float duration, float framerate)
+                                  Animation::Builder::Stats* stats, float duration, float framerate)
     : fResourceProvider(rp)
     , fFontMgr(std::move(fontmgr))
     , fStats(stats)
@@ -163,7 +163,17 @@
 
 } // namespace internal
 
-sk_sp<Animation> Animation::Make(SkStream* stream, const ResourceProvider* provider, Stats* stats) {
+Animation::Builder& Animation::Builder::setResourceProvider(const ResourceProvider* rp) {
+    fResourceProvider = rp;
+    return *this;
+}
+
+Animation::Builder& Animation::Builder::setFontManager(sk_sp<SkFontMgr> fmgr) {
+    fFontMgr = std::move(fmgr);
+    return *this;
+}
+
+sk_sp<Animation> Animation::Builder::make(SkStream* stream) {
     if (!stream->hasLength()) {
         // TODO: handle explicit buffering?
         LOG("!! cannot parse streaming content\n");
@@ -176,17 +186,22 @@
         return nullptr;
     }
 
-    return Make(static_cast<const char*>(data->data()), data->size(), provider, stats);
+    return this->make(static_cast<const char*>(data->data()), data->size());
 }
 
-sk_sp<Animation> Animation::Make(const char* data, size_t data_len,
-                                 const ResourceProvider* provider, Stats* stats) {
-    Stats stats_storage;
-    if (!stats)
-        stats = &stats_storage;
-    memset(stats, 0, sizeof(struct Stats));
+sk_sp<Animation> Animation::Builder::make(const char* data, size_t data_len) {
+    // Sanitize factory args.
+    class NullResourceProvider final : public ResourceProvider {
+        sk_sp<SkData> load(const char[], const char[]) const override { return nullptr; }
+    };
 
-    stats->fJsonSize = data_len;
+    const NullResourceProvider nullProvider;
+    auto resolvedProvider = fResourceProvider ? fResourceProvider : &nullProvider;
+    auto resolvedFontMgr  = fFontMgr ? fFontMgr : SkFontMgr::RefDefault();
+
+    memset(&fStats, 0, sizeof(struct Stats));
+
+    fStats.fJsonSize = data_len;
     const auto t0 = SkTime::GetMSecs();
 
     const skjson::DOM dom(data, data_len);
@@ -198,40 +213,43 @@
     const auto& json = dom.root().as<skjson::ObjectValue>();
 
     const auto t1 = SkTime::GetMSecs();
-    stats->fJsonParseTimeMS = t1 - t0;
+    fStats.fJsonParseTimeMS = t1 - t0;
 
     const auto version  = ParseDefault<SkString>(json["v"], SkString());
     const auto size     = SkSize::Make(ParseDefault<float>(json["w"], 0.0f),
                                        ParseDefault<float>(json["h"], 0.0f));
     const auto fps      = ParseDefault<float>(json["fr"], -1.0f),
                inPoint  = ParseDefault<float>(json["ip"], 0.0f),
-               outPoint = SkTMax(ParseDefault<float>(json["op"], SK_ScalarMax), inPoint);
+               outPoint = SkTMax(ParseDefault<float>(json["op"], SK_ScalarMax), inPoint),
+               duration = (outPoint - inPoint) / fps;
 
     if (size.isEmpty() || version.isEmpty() || fps <= 0 ||
-        !SkScalarIsFinite(inPoint) || !SkScalarIsFinite(outPoint)) {
+        !SkScalarIsFinite(inPoint) || !SkScalarIsFinite(outPoint) || !SkScalarIsFinite(duration)) {
         LOG("!! invalid animation params (version: %s, size: [%f %f], frame rate: %f, "
             "in-point: %f, out-point: %f)\n",
             version.c_str(), size.width(), size.height(), fps, inPoint, outPoint);
         return nullptr;
     }
 
-    class NullResourceProvider final : public ResourceProvider {
-        sk_sp<SkData> load(const char[], const char[]) const override { return nullptr; }
-    };
+    SkASSERT(resolvedProvider);
+    SkASSERT(resolvedFontMgr);
+    internal::AnimationBuilder builder(*resolvedProvider, std::move(resolvedFontMgr), &fStats,
+                                       duration, fps);
+    auto scene = builder.parse(json);
 
-    NullResourceProvider null_provider;
-    const auto anim = sk_sp<Animation>(new Animation(provider ? *provider : null_provider,
-                                                     std::move(version), size, fps,
-                                                     inPoint, outPoint, json, stats));
     const auto t2 = SkTime::GetMSecs();
-    stats->fSceneParseTimeMS = t2 - t1;
-    stats->fTotalLoadTimeMS  = t2 - t0;
+    fStats.fSceneParseTimeMS = t2 - t1;
+    fStats.fTotalLoadTimeMS  = t2 - t0;
 
-    return anim;
+    if (!scene) {
+        LOG("!! could not parse animation.\n");
+    }
+
+    return sk_sp<Animation>(
+        new Animation(std::move(scene), std::move(version), size, inPoint, outPoint, duration));
 }
 
-sk_sp<Animation> Animation::MakeFromFile(const char path[], const ResourceProvider* res,
-                                         Stats* stats) {
+sk_sp<Animation> Animation::Builder::makeFromFile(const char path[]) {
     class DirectoryResourceProvider final : public ResourceProvider {
     public:
         explicit DirectoryResourceProvider(SkString dir) : fDir(std::move(dir)) {}
@@ -246,33 +264,33 @@
         const SkString fDir;
     };
 
-    const auto data =  SkData::MakeFromFileName(path);
+    const auto data = SkData::MakeFromFileName(path);
     if (!data)
         return nullptr;
 
-    std::unique_ptr<ResourceProvider> defaultProvider;
-    if (!res) {
-        defaultProvider = skstd::make_unique<DirectoryResourceProvider>(SkOSPath::Dirname(path));
+    const auto* origProvider = fResourceProvider;
+    std::unique_ptr<ResourceProvider> localProvider;
+    if (!fResourceProvider) {
+        localProvider = skstd::make_unique<DirectoryResourceProvider>(SkOSPath::Dirname(path));
+        fResourceProvider = localProvider.get();
     }
 
-    return Make(static_cast<const char*>(data->data()), data->size(),
-                res ? res : defaultProvider.get(), stats);
+    auto animation = this->make(static_cast<const char*>(data->data()), data->size());
+
+    // Don't leak localProvider references.
+    fResourceProvider = origProvider;
+
+    return animation;
 }
 
-Animation::Animation(const ResourceProvider& resources,
-                     SkString version, const SkSize& size,
-                     SkScalar fps, SkScalar in, SkScalar out,
-                     const skjson::ObjectValue& json, Stats* stats)
-    : fVersion(std::move(version))
+Animation::Animation(std::unique_ptr<sksg::Scene> scene, SkString version, const SkSize& size,
+                     SkScalar inPoint, SkScalar outPoint, SkScalar duration)
+    : fScene(std::move(scene))
+    , fVersion(std::move(version))
     , fSize(size)
-    , fFrameRate(fps)
-    , fInPoint(in)
-    , fOutPoint(out) {
-
-    internal::AnimationBuilder builder(resources, SkFontMgr::RefDefault(), stats,
-                                       this->duration(), fps);
-
-    fScene = builder.parse(json);
+    , fInPoint(inPoint)
+    , fOutPoint(outPoint)
+    , fDuration(duration) {
 
     // In case the client calls render before the first tick.
     this->seek(0);
@@ -306,4 +324,16 @@
     fScene->animate(fInPoint + SkTPin(t, 0.0f, 1.0f) * (fOutPoint - fInPoint));
 }
 
+sk_sp<Animation> Animation::Make(const char* data, size_t length) {
+    return Builder().make(data, length);
+}
+
+sk_sp<Animation> Animation::Make(SkStream* stream) {
+    return Builder().make(stream);
+}
+
+sk_sp<Animation> Animation::MakeFromFile(const char path[]) {
+    return Builder().makeFromFile(path);
+}
+
 } // namespace skottie
diff --git a/modules/skottie/src/SkottieLayer.cpp b/modules/skottie/src/SkottieLayer.cpp
index 457e535..55e5e33 100644
--- a/modules/skottie/src/SkottieLayer.cpp
+++ b/modules/skottie/src/SkottieLayer.cpp
@@ -8,6 +8,7 @@
 #include "SkottiePriv.h"
 
 #include "SkData.h"
+#include "SkFontMgr.h"
 #include "SkImage.h"
 #include "SkJSON.h"
 #include "SkMakeUnique.h"
@@ -211,14 +212,15 @@
         return nullptr;
     }
 
-    auto animation = Animation::Make(static_cast<const char*>(data->data()), data->size(),
-                                     &fResourceProvider);
+    auto animation = Animation::Builder()
+            .setResourceProvider(&fResourceProvider)
+            .setFontManager(fFontMgr)
+            .make(static_cast<const char*>(data->data()), data->size());
     if (!animation) {
         LOG("!! Could not parse nested animation: %s\n", name);
         return nullptr;
     }
 
-
     ascope->push_back(
         skstd::make_unique<SkottieAnimatorAdapter>(animation, animation->duration() / fDuration));
 
diff --git a/modules/skottie/src/SkottiePriv.h b/modules/skottie/src/SkottiePriv.h
index aa83183..7300c9d 100644
--- a/modules/skottie/src/SkottiePriv.h
+++ b/modules/skottie/src/SkottiePriv.h
@@ -44,7 +44,7 @@
 
 class AnimationBuilder final : public SkNoncopyable {
 public:
-    AnimationBuilder(const ResourceProvider&, sk_sp<SkFontMgr>, Animation::Stats*,
+    AnimationBuilder(const ResourceProvider&, sk_sp<SkFontMgr>, Animation::Builder::Stats*,
                     float duration, float framerate);
 
     std::unique_ptr<sksg::Scene> parse(const skjson::ObjectValue&);
@@ -75,11 +75,11 @@
     sk_sp<sksg::RenderNode> attachSolidLayer  (const skjson::ObjectValue&, AnimatorScope*);
     sk_sp<sksg::RenderNode> attachTextLayer   (const skjson::ObjectValue&, AnimatorScope*);
 
-    const ResourceProvider& fResourceProvider;
-    const sk_sp<SkFontMgr>  fFontMgr;
-    Animation::Stats*       fStats;
-    const float             fDuration,
-                            fFrameRate;
+    const ResourceProvider&    fResourceProvider;
+    sk_sp<SkFontMgr>           fFontMgr;
+    Animation::Builder::Stats* fStats;
+    const float                fDuration,
+                               fFrameRate;
 
     struct AssetInfo {
         const skjson::ObjectValue* fAsset;
diff --git a/platform_tools/android/apps/skottie/src/main/cpp/native-lib.cpp b/platform_tools/android/apps/skottie/src/main/cpp/native-lib.cpp
index 3cbedbc..d4b4a74 100644
--- a/platform_tools/android/apps/skottie/src/main/cpp/native-lib.cpp
+++ b/platform_tools/android/apps/skottie/src/main/cpp/native-lib.cpp
@@ -123,8 +123,7 @@
     skottieAnimation->mRunner = skottieRunner;
     skottieAnimation->mStream = std::move(stream);
 
-    skottieAnimation->mAnimation = skottie::Animation::Make(skottieAnimation->mStream.get(),
-                                                            nullptr, nullptr);
+    skottieAnimation->mAnimation = skottie::Animation::Make(skottieAnimation->mStream.get());
     skottieAnimation->mTimeBase  = 0.0f; // force a time reset
     skottieAnimation->mDuration = 1000 * skottieAnimation->mAnimation->duration();
 
diff --git a/tools/viewer/SkottieSlide.cpp b/tools/viewer/SkottieSlide.cpp
index aa08533..51b0311 100644
--- a/tools/viewer/SkottieSlide.cpp
+++ b/tools/viewer/SkottieSlide.cpp
@@ -15,7 +15,7 @@
 
 #include <cmath>
 
-static void draw_stats_box(SkCanvas* canvas, const skottie::Animation::Stats& stats) {
+static void draw_stats_box(SkCanvas* canvas, const skottie::Animation::Builder::Stats& stats) {
     static constexpr SkRect kR = { 10, 10, 280, 120 };
     static constexpr SkScalar kTextSize = 20;
 
@@ -59,9 +59,11 @@
 }
 
 void SkottieSlide::load(SkScalar w, SkScalar h) {
-    fAnimation = skottie::Animation::MakeFromFile(fPath.c_str(), nullptr, &fAnimationStats);
-    fWinSize   = SkSize::Make(w, h);
-    fTimeBase  = 0; // force a time reset
+    skottie::Animation::Builder builder;
+    fAnimation      = builder.makeFromFile(fPath.c_str());
+    fAnimationStats = builder.getStats();
+    fWinSize        = SkSize::Make(w, h);
+    fTimeBase       = 0; // force a time reset
 
     if (fAnimation) {
         fAnimation->setShowInval(fShowAnimationInval);
diff --git a/tools/viewer/SkottieSlide.h b/tools/viewer/SkottieSlide.h
index 9ed4d78..9bf6bf5 100644
--- a/tools/viewer/SkottieSlide.h
+++ b/tools/viewer/SkottieSlide.h
@@ -32,13 +32,13 @@
     bool onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState, uint32_t modifiers) override;
 
 private:
-    SkString                  fPath;
-    sk_sp<skottie::Animation> fAnimation;
-    skottie::Animation::Stats fAnimationStats;
-    SkSize                    fWinSize = SkSize::MakeEmpty();
-    SkMSec                    fTimeBase  = 0;
-    bool                      fShowAnimationInval = false,
-                              fShowAnimationStats = false;
+    SkString                           fPath;
+    sk_sp<skottie::Animation>          fAnimation;
+    skottie::Animation::Builder::Stats fAnimationStats;
+    SkSize                             fWinSize = SkSize::MakeEmpty();
+    SkMSec                             fTimeBase  = 0;
+    bool                               fShowAnimationInval = false,
+                                       fShowAnimationStats = false;
 
     typedef Slide INHERITED;
 };