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