xref: /aosp_15_r20/external/skia/modules/canvaskit/skottie_bindings.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2019 Google LLC
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "include/codec/SkCodec.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkFontMgr.h"
11 #include "include/core/SkImage.h"
12 #include "include/core/SkStream.h"
13 #include "include/core/SkString.h"
14 #include "include/core/SkTypes.h"
15 #include "include/private/base/SkOnce.h"
16 #include "modules/canvaskit/WasmCommon.h"
17 #include "modules/skottie/include/Skottie.h"
18 #include "modules/skottie/include/SkottieProperty.h"
19 #include "modules/skottie/include/SlotManager.h"
20 #include "modules/skottie/utils/SkottieUtils.h"
21 #include "modules/skottie/utils/TextEditor.h"
22 #include "modules/skparagraph/include/Paragraph.h"
23 #include "modules/skresources/include/SkResources.h"
24 #include "modules/sksg/include/SkSGInvalidationController.h"
25 #include "modules/skshaper/utils/FactoryHelpers.h"
26 #include "modules/skunicode/include/SkUnicode.h"
27 #include "src/base/SkUTF.h"
28 #include "src/ports/SkTypeface_FreeType.h"
29 #include "tools/skui/InputState.h"
30 #include "tools/skui/ModifierKey.h"
31 
32 #include <string>
33 #include <vector>
34 #include <emscripten.h>
35 #include <emscripten/bind.h>
36 
37 #if defined(SK_CODEC_DECODES_GIF)
38 #include "include/codec/SkGifDecoder.h"
39 #endif
40 #if defined(SK_CODEC_DECODES_JPEG)
41 #include "include/codec/SkJpegDecoder.h"
42 #endif
43 #if defined(SK_CODEC_DECODES_PNG)
44 #include "include/codec/SkPngDecoder.h"
45 #endif
46 #if defined(SK_CODEC_DECODES_WEBP)
47 #include "include/codec/SkWebpDecoder.h"
48 #endif
49 
50 #if !defined(CK_NO_FONTS)
51 #include "include/ports/SkFontMgr_empty.h"
52 #endif
53 
54 using namespace emscripten;
55 namespace para = skia::textlayout;
56 namespace {
57 
58 struct SimpleSlottableTextProperty {
59     sk_sp<SkTypeface> typeface;
60     std::string text;
61 
62     float textSize;
63     float minTextSize;
64     float maxTextSize;
65     float strokeWidth;
66     float lineHeight;
67     float lineShift;
68     float ascent;
69     float maxLines;
70 
71     para::TextAlign horizAlign;
72     skottie::Shaper::VAlign vertAlign;
73     skottie::Shaper::ResizePolicy resize;
74     SkUnicode::LineBreakType lineBreak;
75     para::TextDirection direction;
76     SkPaint::Join strokeJoin;
77 
78     WASMPointerF32 boundingBoxPtr;
79     WASMPointerF32 fillColorPtr;
80     WASMPointerF32 strokeColorPtr;
81 
operator skottie::TextPropertyValue__anonaf201dba0111::SimpleSlottableTextProperty82     operator skottie::TextPropertyValue() const {
83         skottie::TextPropertyValue textProperty;
84 
85         textProperty.fTypeface = this->typeface;
86         textProperty.fText = SkString(this->text);
87         textProperty.fTextSize = this->textSize;
88         textProperty.fMinTextSize = this->minTextSize;
89         textProperty.fMaxTextSize = this->maxTextSize;
90         textProperty.fStrokeWidth = this->strokeWidth;
91         textProperty.fLineHeight = this->lineHeight;
92         textProperty.fLineShift = this->lineShift;
93         textProperty.fAscent = this->ascent;
94         textProperty.fMaxLines = this->maxLines;
95 
96         switch (this->horizAlign) {
97         case para::TextAlign::kLeft:
98             textProperty.fHAlign = SkTextUtils::Align::kLeft_Align;
99             break;
100         case para::TextAlign::kCenter:
101             textProperty.fHAlign = SkTextUtils::Align::kCenter_Align;
102             break;
103         case para::TextAlign::kRight:
104             textProperty.fHAlign = SkTextUtils::Align::kRight_Align;
105             break;
106         default:
107             textProperty.fHAlign = SkTextUtils::Align::kLeft_Align;
108             break;
109         }
110 
111         textProperty.fVAlign = this->vertAlign;
112         textProperty.fResize = this->resize;
113 
114         if (this->lineBreak == SkUnicode::LineBreakType::kSoftLineBreak) {
115             textProperty.fLineBreak = skottie::Shaper::LinebreakPolicy::kParagraph;
116         } else {
117             textProperty.fLineBreak = skottie::Shaper::LinebreakPolicy::kExplicit;
118         }
119 
120         if (this->direction == para::TextDirection::kRtl) {
121             textProperty.fDirection = skottie::Shaper::Direction::kRTL;
122         } else {
123             textProperty.fDirection = skottie::Shaper::Direction::kLTR;
124         }
125 
126         textProperty.fStrokeJoin = this->strokeJoin;
127 
128         textProperty.fBox = reinterpret_cast<SkRect*>(this->boundingBoxPtr)[0];
129         textProperty.fFillColor = ptrToSkColor4f(this->fillColorPtr).toSkColor();
130         textProperty.fStrokeColor = ptrToSkColor4f(this->strokeColorPtr).toSkColor();
131 
132         return textProperty;
133     }
134 };
135 
136 // WebTrack wraps a JS object that has a 'seek' method.
137 // Playback logic is kept there.
138 class WebTrack final : public skresources::ExternalTrackAsset {
139 public:
WebTrack(emscripten::val player)140     explicit WebTrack(emscripten::val player) : fPlayer(std::move(player)) {}
141 
142 private:
seek(float t)143     void seek(float t) override {
144         fPlayer.call<void>("seek", val(t));
145     }
146 
147     const emscripten::val fPlayer;
148 };
149 
150 class SkottieAssetProvider : public skottie::ResourceProvider {
151 public:
152     ~SkottieAssetProvider() override = default;
153 
154     // Tried using a map, but that gave strange errors like
155     // https://emscripten.org/docs/porting/guidelines/function_pointer_issues.html
156     // Not entirely sure why, but perhaps the iterator in the map was
157     // confusing enscripten.
158     using AssetVec = std::vector<std::pair<SkString, sk_sp<SkData>>>;
159 
Make(AssetVec assets,emscripten::val soundMap)160     static sk_sp<SkottieAssetProvider> Make(AssetVec assets, emscripten::val soundMap) {
161         return sk_sp<SkottieAssetProvider>(new SkottieAssetProvider(std::move(assets),
162                                                                     std::move(soundMap)));
163     }
164 
loadImageAsset(const char[],const char name[],const char[]) const165     sk_sp<skottie::ImageAsset> loadImageAsset(const char[] /* path */,
166                                               const char name[],
167                                               const char[] /* id */) const override {
168         // For CK/Skottie we ignore paths & IDs, and identify images based solely on name.
169         if (auto data = this->findAsset(name)) {
170             auto codec = DecodeImageData(data);
171             if (!codec) {
172                 return nullptr;
173             }
174             return skresources::MultiFrameImageAsset::Make(std::move(codec));
175         }
176 
177         return nullptr;
178     }
179 
loadAudioAsset(const char[],const char[],const char id[])180     sk_sp<skresources::ExternalTrackAsset> loadAudioAsset(const char[] /* path */,
181                                                           const char[] /* name */,
182                                                           const char id[]) override {
183         emscripten::val player = this->findSoundAsset(id);
184         if (player.as<bool>()) {
185             return sk_make_sp<WebTrack>(std::move(player));
186         }
187 
188         return nullptr;
189     }
190 
loadTypeface(const char name[],const char[]) const191     sk_sp<SkTypeface> loadTypeface(const char name[], const char[] /* url */) const override {
192         sk_sp<SkData> faceData = this->findAsset(name);
193         if (!faceData) {
194             return nullptr;
195         }
196         auto stream = std::make_unique<SkMemoryStream>(faceData);
197         return SkTypeface_FreeType::MakeFromStream(std::move(stream), SkFontArguments());
198     }
199 
load(const char[],const char name[]) const200     sk_sp<SkData> load(const char[]/*path*/, const char name[]) const override {
201         // Ignore paths.
202         return this->findAsset(name);
203     }
204 
205 private:
SkottieAssetProvider(AssetVec assets,emscripten::val soundMap)206     explicit SkottieAssetProvider(AssetVec assets, emscripten::val soundMap)
207     : fAssets(std::move(assets))
208     , fSoundMap(std::move(soundMap)) {}
209 
findAsset(const char name[]) const210     sk_sp<SkData> findAsset(const char name[]) const {
211         for (const auto& asset : fAssets) {
212             if (asset.first.equals(name)) {
213                 return asset.second;
214             }
215         }
216 
217         SkDebugf("Could not find %s\n", name);
218         return nullptr;
219     }
220 
findSoundAsset(const char name[]) const221     emscripten::val findSoundAsset(const char name[]) const {
222         if (fSoundMap.as<bool>() && fSoundMap.hasOwnProperty("getPlayer")) {
223             emscripten::val player = fSoundMap.call<emscripten::val>("getPlayer", val(name));
224             if (player.as<bool>() && player.hasOwnProperty("seek")) {
225                 return player;
226             }
227         }
228         return emscripten::val::null();
229     }
230 
231     const AssetVec fAssets;
232     const emscripten::val fSoundMap;
233 };
234 
235 // Wraps a JS object with 'onError' and 'onWarning' methods.
236 class JSLogger final : public skottie::Logger {
237 public:
Make(emscripten::val logger)238     static sk_sp<JSLogger> Make(emscripten::val logger) {
239         return logger.as<bool>()
240             && logger.hasOwnProperty(kWrnFunc)
241             && logger.hasOwnProperty(kErrFunc)
242                 ? sk_sp<JSLogger>(new JSLogger(std::move(logger)))
243                 : nullptr;
244     }
245 
246 private:
JSLogger(emscripten::val logger)247     explicit JSLogger(emscripten::val logger) : fLogger(std::move(logger)) {}
248 
log(Level lvl,const char msg[],const char * json)249     void log(Level lvl, const char msg[], const char* json) override {
250         const auto* func = lvl == Level::kError ? kErrFunc : kWrnFunc;
251         fLogger.call<void>(func, std::string(msg), std::string(json));
252     }
253 
254     inline static constexpr char kWrnFunc[] = "onWarning",
255                                  kErrFunc[] = "onError";
256 
257     const emscripten::val fLogger;
258 };
259 
260 class ManagedAnimation final : public SkRefCnt {
261 public:
Make(const std::string & json,sk_sp<skottie::ResourceProvider> rp,std::string prop_prefix,emscripten::val logger)262     static sk_sp<ManagedAnimation> Make(const std::string& json,
263                                         sk_sp<skottie::ResourceProvider> rp,
264                                         std::string prop_prefix,
265                                         emscripten::val logger) {
266         auto mgr = std::make_unique<skottie_utils::CustomPropertyManager>(
267                         skottie_utils::CustomPropertyManager::Mode::kCollapseProperties,
268                         prop_prefix.c_str());
269         static constexpr char kInterceptPrefix[] = "__";
270         auto pinterceptor =
271             sk_make_sp<skottie_utils::ExternalAnimationPrecompInterceptor>(rp, kInterceptPrefix);
272         skottie::Animation::Builder builder;
273         builder.setMarkerObserver(mgr->getMarkerObserver())
274                .setPropertyObserver(mgr->getPropertyObserver())
275                .setResourceProvider(rp)
276                .setPrecompInterceptor(std::move(pinterceptor))
277                .setTextShapingFactory(SkShapers::BestAvailable())
278                .setLogger(JSLogger::Make(std::move(logger)));
279         auto animation = builder.make(json.c_str(), json.size());
280         auto slotManager = builder.getSlotManager();
281 
282         return animation
283             ? sk_sp<ManagedAnimation>(new ManagedAnimation(std::move(animation), std::move(mgr),
284                                                            std::move(slotManager), std::move(rp)))
285             : nullptr;
286     }
287 
288     ~ManagedAnimation() override = default;
289 
290     // skottie::Animation API
render(SkCanvas * canvas,const SkRect * dst) const291     void render(SkCanvas* canvas, const SkRect* dst) const { fAnimation->render(canvas, dst); }
292     // Returns a damage rect.
seek(SkScalar t)293     SkRect seek(SkScalar t) {
294         sksg::InvalidationController ic;
295         fAnimation->seek(t, &ic);
296         return ic.bounds();
297     }
298     // Returns a damage rect.
seekFrame(double t)299     SkRect seekFrame(double t) {
300         sksg::InvalidationController ic;
301         fAnimation->seekFrame(t, &ic);
302         return ic.bounds();
303     }
duration() const304     double duration() const { return fAnimation->duration(); }
fps() const305     double fps() const { return fAnimation->fps(); }
size() const306     const SkSize& size() const { return fAnimation->size(); }
version() const307     std::string version() const { return std::string(fAnimation->version().c_str()); }
308 
309     // CustomPropertyManager API
getColorProps() const310     JSArray getColorProps() const {
311         JSArray props = emscripten::val::array();
312 
313         for (const auto& cp : fPropMgr->getColorProps()) {
314             JSObject prop = emscripten::val::object();
315             prop.set("key", cp);
316             prop.set("value", fPropMgr->getColor(cp));
317             props.call<void>("push", prop);
318         }
319 
320         return props;
321     }
322 
getOpacityProps() const323     JSArray getOpacityProps() const {
324         JSArray props = emscripten::val::array();
325 
326         for (const auto& op : fPropMgr->getOpacityProps()) {
327             JSObject prop = emscripten::val::object();
328             prop.set("key", op);
329             prop.set("value", fPropMgr->getOpacity(op));
330             props.call<void>("push", prop);
331         }
332 
333         return props;
334     }
335 
getTextProps() const336     JSArray getTextProps() const {
337         JSArray props = emscripten::val::array();
338 
339         for (const auto& key : fPropMgr->getTextProps()) {
340             const auto txt = fPropMgr->getText(key);
341             JSObject txt_val = emscripten::val::object();
342             txt_val.set("text", txt.fText.c_str());
343             txt_val.set("size", txt.fTextSize);
344 
345             JSObject prop = emscripten::val::object();
346             prop.set("key", key);
347             prop.set("value", std::move(txt_val));
348 
349             props.call<void>("push", prop);
350         }
351 
352         return props;
353     }
354 
getTransformProps() const355     JSArray getTransformProps() const {
356         JSArray props = emscripten::val::array();
357 
358         for (const auto& key : fPropMgr->getTransformProps()) {
359             const auto transform = fPropMgr->getTransform(key);
360             JSObject trans_val = emscripten::val::object();
361             const float anchor[] = {transform.fAnchorPoint.fX, transform.fAnchorPoint.fY};
362             const float position[] = {transform.fPosition.fX, transform.fPosition.fY};
363             const float scale[] = {transform.fScale.fX, transform.fScale.fY};
364             trans_val.set("anchor", MakeTypedArray(2, anchor));
365             trans_val.set("position", MakeTypedArray(2, position));
366             trans_val.set("scale", MakeTypedArray(2, scale));
367             trans_val.set("rotation", transform.fRotation);
368             trans_val.set("skew", transform.fSkew);
369             trans_val.set("skew_axis", transform.fSkewAxis);
370 
371             JSObject prop = emscripten::val::object();
372             prop.set("key", key);
373             prop.set("value", trans_val);
374             props.call<void>("push", prop);
375         }
376 
377         return props;
378     }
379 
setColor(const std::string & key,SkColor c)380     bool setColor(const std::string& key, SkColor c) {
381         return fPropMgr->setColor(key, c);
382     }
383 
setOpacity(const std::string & key,float o)384     bool setOpacity(const std::string& key, float o) {
385         return fPropMgr->setOpacity(key, o);
386     }
387 
setText(const std::string & key,std::string text,float size)388     bool setText(const std::string& key, std::string text, float size) {
389         // preserve all other text fields
390         auto t = fPropMgr->getText(key);
391 
392         t.fText     = SkString(text);
393         t.fTextSize = size;
394 
395         return fPropMgr->setText(key, t);
396     }
397 
setTransform(const std::string & key,SkScalar anchorX,SkScalar anchorY,SkScalar posX,SkScalar posY,SkScalar scaleX,SkScalar scaleY,SkScalar rotation,SkScalar skew,SkScalar skewAxis)398     bool setTransform(const std::string& key, SkScalar anchorX, SkScalar anchorY,
399                                               SkScalar posX, SkScalar posY,
400                                               SkScalar scaleX, SkScalar scaleY,
401                                               SkScalar rotation, SkScalar skew, SkScalar skewAxis) {
402         skottie::TransformPropertyValue transform;
403         transform.fAnchorPoint = {anchorX, anchorY};
404         transform.fPosition = {posX, posY};
405         transform.fScale = {scaleX, scaleY};
406         transform.fRotation = rotation;
407         transform.fSkew = skew;
408         transform.fSkewAxis = skewAxis;
409         return fPropMgr->setTransform(key, transform);
410     }
411 
getMarkers() const412     JSArray getMarkers() const {
413         JSArray markers = emscripten::val::array();
414         for (const auto& m : fPropMgr->markers()) {
415             JSObject marker = emscripten::val::object();
416             marker.set("name", m.name);
417             marker.set("t0"  , m.t0);
418             marker.set("t1"  , m.t1);
419             markers.call<void>("push", marker);
420         }
421         return markers;
422     }
423 
copyStringArrayToJSArray(skia_private::TArray<SkString> slotIDs) const424     JSArray copyStringArrayToJSArray(skia_private::TArray<SkString> slotIDs) const {
425         JSArray retVal = emscripten::val::array();
426         for (auto slotID : slotIDs) {
427             retVal.call<void>("push", emscripten::val(slotID.c_str()));
428         }
429         return retVal;
430     }
431 
432     // Slot Manager API
getSlotInfo() const433     JSObject getSlotInfo() const {
434         JSObject slotInfoJS = emscripten::val::object();
435         auto slotInfo = fSlotMgr->getSlotInfo();
436 
437         slotInfoJS.set("colorSlotIDs", copyStringArrayToJSArray(slotInfo.fColorSlotIDs));
438         slotInfoJS.set("scalarSlotIDs", copyStringArrayToJSArray(slotInfo.fScalarSlotIDs));
439         slotInfoJS.set("vec2SlotIDs", copyStringArrayToJSArray(slotInfo.fVec2SlotIDs));
440         slotInfoJS.set("imageSlotIDs", copyStringArrayToJSArray(slotInfo.fImageSlotIDs));
441         slotInfoJS.set("textSlotIDs", copyStringArrayToJSArray(slotInfo.fTextSlotIDs));
442 
443         return slotInfoJS;
444     }
445 
getColorSlot(const std::string & slotID,WASMPointerF32 outPtr)446     void getColorSlot(const std::string& slotID, WASMPointerF32 outPtr) {
447         SkColor4f c4f;
448         if (auto c = fSlotMgr->getColorSlot(SkString(slotID))) {
449             c4f = SkColor4f::FromColor(*c);
450         } else {
451             c4f = {-1, -1, -1, -1};
452         }
453         memcpy(reinterpret_cast<float*>(outPtr), &c4f, 4 * sizeof(float));
454     }
455 
getScalarSlot(const std::string & slotID)456     emscripten::val getScalarSlot(const std::string& slotID) {
457         if (auto s = fSlotMgr->getScalarSlot(SkString(slotID))) {
458            return emscripten::val(*s);
459         }
460         return emscripten::val::null();
461     }
462 
getVec2Slot(const std::string & slotID,WASMPointerF32 outPtr)463     void getVec2Slot(const std::string& slotID, WASMPointerF32 outPtr) {
464         // [x, y, sentinel]
465         SkV3 vec3;
466         if (auto v = fSlotMgr->getVec2Slot(SkString(slotID))) {
467             vec3 = {v->x, v->y, 1};
468         } else {
469             vec3 = {0, 0, -1};
470         }
471         memcpy(reinterpret_cast<float*>(outPtr), vec3.ptr(), 3 * sizeof(float));
472     }
473 
getTextSlot(const std::string & slotID) const474     JSObject getTextSlot(const std::string& slotID) const {
475         if (auto textProp = fSlotMgr->getTextSlot(SkString(slotID))){
476             JSObject text_val = emscripten::val::object();
477 
478             text_val.set("typeface", textProp->fTypeface);
479             text_val.set("text", emscripten::val(textProp->fText.c_str()));
480             text_val.set("textSize", textProp->fTextSize);
481             text_val.set("minTextSize", textProp->fMinTextSize);
482             text_val.set("maxTextSize", textProp->fMaxTextSize);
483             text_val.set("strokeWidth", textProp->fStrokeWidth);
484             text_val.set("lineHeight", textProp->fLineHeight);
485             text_val.set("lineShift", textProp->fLineShift);
486             text_val.set("ascent", textProp->fAscent);
487             text_val.set("maxLines", textProp->fMaxLines);
488 
489             switch (textProp->fHAlign) {
490             case SkTextUtils::Align::kLeft_Align:
491                 text_val.set("horizAlign", para::TextAlign::kLeft);
492                 break;
493             case SkTextUtils::Align::kRight_Align:
494                 text_val.set("horizAlign", para::TextAlign::kRight);
495                 break;
496             case SkTextUtils::Align::kCenter_Align:
497                 text_val.set("horizAlign", para::TextAlign::kCenter);
498                 break;
499             default:
500                 text_val.set("horizAlign", para::TextAlign::kLeft);
501                 break;
502             }
503 
504             text_val.set("vertAlign", textProp->fVAlign);
505             text_val.set("resize", textProp->fResize);
506 
507             if (textProp->fLineBreak == skottie::Shaper::LinebreakPolicy::kParagraph) {
508                 text_val.set("linebreak", SkUnicode::LineBreakType::kSoftLineBreak);
509             } else {
510                 text_val.set("linebreak", SkUnicode::LineBreakType::kHardLineBreak);
511             }
512 
513             if (textProp->fDirection == skottie::Shaper::Direction::kLTR) {
514                 text_val.set("direction", para::TextDirection::kLtr);
515             } else {
516                 text_val.set("direction", para::TextDirection::kRtl);
517             }
518             text_val.set("strokeJoin", textProp->fStrokeJoin);
519 
520             text_val.set("fillColor", MakeTypedArray(4, SkColor4f::FromColor(textProp->fFillColor)
521                                                             .vec()));
522 
523             text_val.set("strokeColor", MakeTypedArray(4, SkColor4f::FromColor(textProp->fStrokeColor)
524                                                             .vec()));
525 
526             const float box[] = {textProp->fBox.fLeft, textProp->fBox.fTop,
527                                  textProp->fBox.fRight, textProp->fBox.fBottom};
528             text_val.set("boundingBox", MakeTypedArray(4, box));
529             return text_val;
530         }
531         return emscripten::val::null();
532     }
533 
setImageSlot(const std::string & slotID,const std::string & assetName)534     bool setImageSlot(const std::string& slotID, const std::string& assetName) {
535         // look for resource in preloaded SkottieAssetProvider
536         return fSlotMgr->setImageSlot(SkString(slotID), fResourceProvider->loadImageAsset(nullptr,
537                                                                             assetName.data(),
538                                                                             nullptr));
539     }
540 
setColorSlot(const std::string & slotID,SkColor c)541     bool setColorSlot(const std::string& slotID, SkColor c) {
542         return fSlotMgr->setColorSlot(SkString(slotID), c);
543     }
544 
setScalarSlot(const std::string & slotID,float s)545     bool setScalarSlot(const std::string& slotID, float s) {
546         return fSlotMgr->setScalarSlot(SkString(slotID), s);
547     }
548 
setVec2Slot(const std::string & slotID,SkV2 v)549     bool setVec2Slot(const std::string& slotID, SkV2 v) {
550         return fSlotMgr->setVec2Slot(SkString(slotID), v);
551     }
552 
attachEditor(const std::string & layerID,size_t layerIndex)553     bool attachEditor(const std::string& layerID, size_t layerIndex) {
554         if (fTextEditor) {
555             fTextEditor->setEnabled(false);
556             fTextEditor = nullptr;
557         }
558 
559         if (layerID.empty()) {
560             return true;
561         }
562 
563         auto txt_handle = fPropMgr->getTextHandle(layerID, layerIndex);
564         if (!txt_handle) {
565             return false;
566         }
567 
568         std::vector<std::unique_ptr<skottie::TextPropertyHandle>> deps;
569         for (size_t i = 0; ; ++i) {
570             if (i == layerIndex) {
571                 continue;
572             }
573 
574             auto dep_handle = fPropMgr->getTextHandle(layerID, i);
575             if (!dep_handle) {
576                 break;
577             }
578             deps.push_back(std::move(dep_handle));
579         }
580 
581         fTextEditor = sk_make_sp<skottie_utils::TextEditor>(std::move(txt_handle),
582                                                             std::move(deps));
583         return true;
584     }
585 
enableEditor(bool enable)586     void enableEditor(bool enable) {
587         if (fTextEditor) {
588             fTextEditor->setEnabled(enable);
589         }
590     }
591 
dispatchEditorKey(const std::string & key)592     bool dispatchEditorKey(const std::string& key) {
593         // Map some useful keys to the current (odd) text editor bindings.
594         // TODO: Add support for custom bindings in the editor.
595         auto key2char = [](const std::string& key) -> SkUnichar {
596             // Special keys.
597             if (key == "ArrowLeft")  return '[';
598             if (key == "ArrowRight") return ']';
599             if (key == "Backspace")  return '\\';
600 
601             const char* str = key.c_str();
602             const char* end = str + key.size();
603             const SkUnichar uch = SkUTF::NextUTF8(&str, end);
604 
605             // Pass through single code points, ignore everything else.
606             return str == end ? uch : -1;
607         };
608 
609         if (fTextEditor) {
610             const auto uch = key2char(key);
611             if (uch != -1) {
612                 return fTextEditor->onCharInput(uch);
613             }
614         }
615 
616         return false;
617     }
618 
dispatchEditorPointer(float x,float y,skui::InputState state,skui::ModifierKey mod)619     bool dispatchEditorPointer(float x, float y, skui::InputState state, skui::ModifierKey mod) {
620         return fTextEditor
621                 ? fTextEditor->onMouseInput(x, y, state, mod)
622                 : false;
623     }
624 
setEditorCursorWeight(float w)625     void setEditorCursorWeight(float w) {
626         if (fTextEditor) {
627             fTextEditor->setCursorWeight(w);
628         }
629     }
630 
setTextSlot(const std::string & slotID,SimpleSlottableTextProperty t)631     bool setTextSlot(const std::string& slotID, SimpleSlottableTextProperty t) {
632         return fSlotMgr->setTextSlot(SkString(slotID), t);
633     }
634 
635 private:
ManagedAnimation(sk_sp<skottie::Animation> animation,std::unique_ptr<skottie_utils::CustomPropertyManager> propMgr,sk_sp<skottie::SlotManager> slotMgr,sk_sp<skresources::ResourceProvider> rp)636     ManagedAnimation(sk_sp<skottie::Animation> animation,
637                      std::unique_ptr<skottie_utils::CustomPropertyManager> propMgr,
638                      sk_sp<skottie::SlotManager> slotMgr,
639                      sk_sp<skresources::ResourceProvider> rp)
640         : fAnimation(std::move(animation))
641         , fPropMgr(std::move(propMgr))
642         , fSlotMgr(std::move(slotMgr))
643         , fResourceProvider(std::move(rp))
644     {}
645 
646     const sk_sp<skottie::Animation>                             fAnimation;
647     const std::unique_ptr<skottie_utils::CustomPropertyManager> fPropMgr;
648     const sk_sp<skottie::SlotManager>                           fSlotMgr;
649     const sk_sp<skresources::ResourceProvider>                  fResourceProvider;
650 
651     sk_sp<skottie_utils::TextEditor>                            fTextEditor;
652 };
653 
654 } // anonymous ns
655 
EMSCRIPTEN_BINDINGS(Skottie)656 EMSCRIPTEN_BINDINGS(Skottie) {
657     // Animation things (may eventually go in own library)
658     class_<skottie::Animation>("Animation")
659         .smart_ptr<sk_sp<skottie::Animation>>("sk_sp<Animation>")
660         .function("version", optional_override([](skottie::Animation& self)->std::string {
661             return std::string(self.version().c_str());
662         }))
663         .function("_size", optional_override([](skottie::Animation& self,
664                                                 WASMPointerF32 oPtr)->void {
665             SkSize* output = reinterpret_cast<SkSize*>(oPtr);
666             *output = self.size();
667         }))
668         .function("duration", &skottie::Animation::duration)
669         .function("fps"     , &skottie::Animation::fps)
670         .function("seek", optional_override([](skottie::Animation& self, SkScalar t)->void {
671             self.seek(t);
672         }))
673         .function("seekFrame", optional_override([](skottie::Animation& self, double t)->void {
674             self.seekFrame(t);
675         }))
676         .function("_render", optional_override([](skottie::Animation& self, SkCanvas* canvas,
677                                                   WASMPointerF32 fPtr)->void {
678             const SkRect* dst = reinterpret_cast<const SkRect*>(fPtr);
679             self.render(canvas, dst);
680         }), allow_raw_pointers());
681 
682     function("MakeAnimation", optional_override([](std::string json)->sk_sp<skottie::Animation> {
683         return skottie::Animation::Make(json.c_str(), json.length());
684     }));
685     constant("skottie", true);
686 
687     class_<ManagedAnimation>("ManagedAnimation")
688         .smart_ptr<sk_sp<ManagedAnimation>>("sk_sp<ManagedAnimation>")
689         .function("version"   , &ManagedAnimation::version)
690         .function("_size", optional_override([](ManagedAnimation& self,
691                                                 WASMPointerF32 oPtr)->void {
692             SkSize* output = reinterpret_cast<SkSize*>(oPtr);
693             *output = self.size();
694         }))
695         .function("duration"  , &ManagedAnimation::duration)
696         .function("fps"       , &ManagedAnimation::fps)
697         .function("_render", optional_override([](ManagedAnimation& self, SkCanvas* canvas,
698                                                   WASMPointerF32 fPtr)->void {
699             const SkRect* dst = reinterpret_cast<const SkRect*>(fPtr);
700             self.render(canvas, dst);
701         }), allow_raw_pointers())
702         .function("_seek", optional_override([](ManagedAnimation& self, SkScalar t,
703                                                 WASMPointerF32 fPtr) {
704             SkRect* damageRect = reinterpret_cast<SkRect*>(fPtr);
705             damageRect[0] = self.seek(t);
706         }))
707         .function("_seekFrame", optional_override([](ManagedAnimation& self, double frame,
708                                                      WASMPointerF32 fPtr) {
709             SkRect* damageRect = reinterpret_cast<SkRect*>(fPtr);
710             damageRect[0] = self.seekFrame(frame);
711         }))
712         .function("seekFrame" , &ManagedAnimation::seekFrame)
713         .function("_setColor"  , optional_override([](ManagedAnimation& self, const std::string& key, WASMPointerF32 cPtr) {
714             float* fourFloats = reinterpret_cast<float*>(cPtr);
715             SkColor4f color = { fourFloats[0], fourFloats[1], fourFloats[2], fourFloats[3] };
716             return self.setColor(key, color.toSkColor());
717         }))
718         .function("_setTransform"  , optional_override([](ManagedAnimation& self,
719                                                           const std::string& key,
720                                                           WASMPointerF32 transformData) {
721             // transform value info is passed in as an array of 9 scalars in the following order:
722             // anchor xy, position xy, scalexy, rotation, skew, skew axis
723             auto transform = reinterpret_cast<SkScalar*>(transformData);
724             return self.setTransform(key, transform[0], transform[1], transform[2], transform[3],
725                                      transform[4], transform[5], transform[6], transform[7], transform[8]);
726                                                           }))
727         .function("getMarkers"       , &ManagedAnimation::getMarkers)
728         .function("getColorProps"    , &ManagedAnimation::getColorProps)
729         .function("getOpacityProps"  , &ManagedAnimation::getOpacityProps)
730         .function("setOpacity"       , &ManagedAnimation::setOpacity)
731         .function("getTextProps"     , &ManagedAnimation::getTextProps)
732         .function("setText"          , &ManagedAnimation::setText)
733         .function("getTransformProps", &ManagedAnimation::getTransformProps)
734         .function("getSlotInfo"      , &ManagedAnimation::getSlotInfo)
735         .function("_getColorSlot"    , &ManagedAnimation::getColorSlot)
736         .function("_setColorSlot"    , optional_override([](ManagedAnimation& self, const std::string& key, WASMPointerF32 cPtr) {
737             SkColor4f color = ptrToSkColor4f(cPtr);
738             return self.setColorSlot(key, color.toSkColor());
739         }))
740         .function("_getVec2Slot"    , &ManagedAnimation::getVec2Slot)
741         .function("_setVec2Slot"    , optional_override([](ManagedAnimation& self, const std::string& key, WASMPointerF32 vPtr) {
742             float* twoFloats = reinterpret_cast<float*>(vPtr);
743             SkV2 vec2 = {twoFloats[0], twoFloats[1]};
744             return self.setVec2Slot(key, vec2);
745         }))
746         .function("getScalarSlot"    , &ManagedAnimation::getScalarSlot)
747         .function("setScalarSlot"    , &ManagedAnimation::setScalarSlot)
748         .function("attachEditor"         , &ManagedAnimation::attachEditor)
749         .function("enableEditor"         , &ManagedAnimation::enableEditor)
750         .function("dispatchEditorKey"    , &ManagedAnimation::dispatchEditorKey)
751         .function("dispatchEditorPointer", &ManagedAnimation::dispatchEditorPointer)
752         .function("setEditorCursorWeight", &ManagedAnimation::setEditorCursorWeight)
753         .function("getTextSlot"      , &ManagedAnimation::getTextSlot)
754         .function("_setTextSlot"     , &ManagedAnimation::setTextSlot)
755         .function("setImageSlot"     , &ManagedAnimation::setImageSlot);
756 
757     function("_MakeManagedAnimation", optional_override([](std::string json,
758                                                            size_t assetCount,
759                                                            WASMPointerU32 nptr,
760                                                            WASMPointerU32 dptr,
761                                                            WASMPointerU32 sptr,
762                                                            std::string prop_prefix,
763                                                            emscripten::val soundMap,
764                                                            emscripten::val logger)
765                                                         ->sk_sp<ManagedAnimation> {
766         const auto assetNames = reinterpret_cast<char**   >(nptr);
767         const auto assetDatas = reinterpret_cast<uint8_t**>(dptr);
768         const auto assetSizes = reinterpret_cast<size_t*  >(sptr);
769 
770         SkottieAssetProvider::AssetVec assets;
771         assets.reserve(assetCount);
772 
773         for (size_t i = 0; i < assetCount; i++) {
774             auto name  = SkString(assetNames[i]);
775             auto bytes = SkData::MakeFromMalloc(assetDatas[i], assetSizes[i]);
776             assets.push_back(std::make_pair(std::move(name), std::move(bytes)));
777         }
778 
779         // DataURIResourceProviderProxy needs codecs registered to try to process Base64 encoded
780         // images.
781         static SkOnce once;
782         once([] {
783 #if defined(SK_CODEC_DECODES_PNG)
784             SkCodecs::Register(SkPngDecoder::Decoder());
785 #endif
786 #if defined(SK_CODEC_DECODES_JPEG)
787             SkCodecs::Register(SkJpegDecoder::Decoder());
788 #endif
789 #if defined(SK_CODEC_DECODES_GIF)
790             SkCodecs::Register(SkGifDecoder::Decoder());
791 #endif
792 #if defined(SK_CODEC_DECODES_WEBP)
793             SkCodecs::Register(SkWebpDecoder::Decoder());
794 #endif
795         });
796 
797         sk_sp<SkFontMgr> fontmgr;
798 #if !defined(CK_NO_FONTS)
799         fontmgr = SkFontMgr_New_Custom_Empty();
800 #endif
801 
802         return ManagedAnimation::Make(json,
803                                       skresources::DataURIResourceProviderProxy::Make(
804                                           SkottieAssetProvider::Make(std::move(assets),
805                                                                      std::move(soundMap)),
806                                           skresources::ImageDecodeStrategy::kPreDecode,
807                                           std::move(fontmgr)),
808                                       prop_prefix, std::move(logger));
809     }));
810 
811     enum_<skui::InputState>("InputState")
812         .value("Down",  skui::InputState::kDown)
813         .value("Up",    skui::InputState::kUp)
814         .value("Move",  skui::InputState::kMove)
815         .value("Right", skui::InputState::kRight)
816         .value("Left",  skui::InputState::kLeft);
817 
818     enum_<skui::ModifierKey>("ModifierKey")
819         .value("None",       skui::ModifierKey::kNone)
820         .value("Shift",      skui::ModifierKey::kShift)
821         .value("Control",    skui::ModifierKey::kControl)
822         .value("Option",     skui::ModifierKey::kOption)
823         .value("Command",    skui::ModifierKey::kCommand)
824         .value("FirstPress", skui::ModifierKey::kFirstPress);
825 
826     enum_<skottie::Shaper::VAlign>("VerticalTextAlign")
827         .value("Top",          skottie::Shaper::VAlign::kTop)
828         .value("TopBaseline",  skottie::Shaper::VAlign::kTopBaseline)
829         .value("VisualTop",    skottie::Shaper::VAlign::kVisualTop)
830         .value("VisualCenter", skottie::Shaper::VAlign::kVisualCenter)
831         .value("VisualBottom", skottie::Shaper::VAlign::kVisualBottom);
832 
833     enum_<skottie::Shaper::ResizePolicy>("ResizePolicy")
834         .value("None",           skottie::Shaper::ResizePolicy::kNone)
835         .value("ScaleToFit",     skottie::Shaper::ResizePolicy::kScaleToFit)
836         .value("DownscaleToFit", skottie::Shaper::ResizePolicy::kDownscaleToFit);
837 
838     value_object<SimpleSlottableTextProperty>("SlottableTextProperty")
839         .field("typeface",          &SimpleSlottableTextProperty::typeface)
840         .field("text",              &SimpleSlottableTextProperty::text)
841         .field("textSize",          &SimpleSlottableTextProperty::textSize)
842         .field("minTextSize",       &SimpleSlottableTextProperty::minTextSize)
843         .field("maxTextSize",       &SimpleSlottableTextProperty::maxTextSize)
844         .field("strokeWidth",       &SimpleSlottableTextProperty::strokeWidth)
845         .field("lineHeight",        &SimpleSlottableTextProperty::lineHeight)
846         .field("lineShift",         &SimpleSlottableTextProperty::lineShift)
847         .field("ascent",            &SimpleSlottableTextProperty::ascent)
848         .field("maxLines",          &SimpleSlottableTextProperty::maxLines)
849         .field("horizAlign",        &SimpleSlottableTextProperty::horizAlign)
850         .field("vertAlign",         &SimpleSlottableTextProperty::vertAlign)
851         .field("strokeJoin",        &SimpleSlottableTextProperty::strokeJoin)
852         .field("direction",         &SimpleSlottableTextProperty::direction)
853         .field("linebreak",         &SimpleSlottableTextProperty::lineBreak)
854         .field("resize",            &SimpleSlottableTextProperty::resize)
855         .field("_fillColorPtr",     &SimpleSlottableTextProperty::fillColorPtr)
856         .field("_strokeColorPtr",   &SimpleSlottableTextProperty::strokeColorPtr)
857         .field("_boundingBoxPtr",   &SimpleSlottableTextProperty::boundingBoxPtr);
858 
859     constant("managed_skottie", true);
860 }
861