| /* |
| * Copyright 2019 Google LLC |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "include/codec/SkCodec.h" |
| #include "include/core/SkCanvas.h" |
| #include "include/core/SkFontMgr.h" |
| #include "include/core/SkImage.h" |
| #include "include/core/SkStream.h" |
| #include "include/core/SkString.h" |
| #include "include/core/SkTypes.h" |
| #include "include/private/base/SkOnce.h" |
| #include "modules/canvaskit/WasmCommon.h" |
| #include "modules/skottie/include/Skottie.h" |
| #include "modules/skottie/include/SkottieProperty.h" |
| #include "modules/skottie/include/SlotManager.h" |
| #include "modules/skottie/utils/SkottieUtils.h" |
| #include "modules/skottie/utils/TextEditor.h" |
| #include "modules/skparagraph/include/Paragraph.h" |
| #include "modules/skresources/include/SkResources.h" |
| #include "modules/sksg/include/SkSGInvalidationController.h" |
| #include "modules/skshaper/utils/FactoryHelpers.h" |
| #include "modules/skunicode/include/SkUnicode.h" |
| #include "src/base/SkUTF.h" |
| #include "src/ports/SkTypeface_FreeType.h" |
| #include "tools/skui/InputState.h" |
| #include "tools/skui/ModifierKey.h" |
| |
| #include <string> |
| #include <vector> |
| #include <emscripten.h> |
| #include <emscripten/bind.h> |
| |
| #if defined(SK_CODEC_DECODES_GIF) |
| #include "include/codec/SkGifDecoder.h" |
| #endif |
| #if defined(SK_CODEC_DECODES_JPEG) |
| #include "include/codec/SkJpegDecoder.h" |
| #endif |
| #if defined(SK_CODEC_DECODES_PNG) |
| #include "include/codec/SkPngDecoder.h" |
| #endif |
| #if defined(SK_CODEC_DECODES_WEBP) |
| #include "include/codec/SkWebpDecoder.h" |
| #endif |
| |
| #if !defined(CK_NO_FONTS) |
| #include "include/ports/SkFontMgr_empty.h" |
| #endif |
| |
| using namespace emscripten; |
| namespace para = skia::textlayout; |
| namespace { |
| |
| struct SimpleSlottableTextProperty { |
| sk_sp<SkTypeface> typeface; |
| std::string text; |
| |
| float textSize; |
| float minTextSize; |
| float maxTextSize; |
| float strokeWidth; |
| float lineHeight; |
| float lineShift; |
| float ascent; |
| float maxLines; |
| |
| para::TextAlign horizAlign; |
| skottie::Shaper::VAlign vertAlign; |
| skottie::Shaper::ResizePolicy resize; |
| SkUnicode::LineBreakType lineBreak; |
| para::TextDirection direction; |
| SkPaint::Join strokeJoin; |
| |
| WASMPointerF32 boundingBoxPtr; |
| WASMPointerF32 fillColorPtr; |
| WASMPointerF32 strokeColorPtr; |
| |
| operator skottie::TextPropertyValue() const { |
| skottie::TextPropertyValue textProperty; |
| |
| textProperty.fTypeface = this->typeface; |
| textProperty.fText = SkString(this->text); |
| textProperty.fTextSize = this->textSize; |
| textProperty.fMinTextSize = this->minTextSize; |
| textProperty.fMaxTextSize = this->maxTextSize; |
| textProperty.fStrokeWidth = this->strokeWidth; |
| textProperty.fLineHeight = this->lineHeight; |
| textProperty.fLineShift = this->lineShift; |
| textProperty.fAscent = this->ascent; |
| textProperty.fMaxLines = this->maxLines; |
| |
| switch (this->horizAlign) { |
| case para::TextAlign::kLeft: |
| textProperty.fHAlign = SkTextUtils::Align::kLeft_Align; |
| break; |
| case para::TextAlign::kCenter: |
| textProperty.fHAlign = SkTextUtils::Align::kCenter_Align; |
| break; |
| case para::TextAlign::kRight: |
| textProperty.fHAlign = SkTextUtils::Align::kRight_Align; |
| break; |
| default: |
| textProperty.fHAlign = SkTextUtils::Align::kLeft_Align; |
| break; |
| } |
| |
| textProperty.fVAlign = this->vertAlign; |
| textProperty.fResize = this->resize; |
| |
| if (this->lineBreak == SkUnicode::LineBreakType::kSoftLineBreak) { |
| textProperty.fLineBreak = skottie::Shaper::LinebreakPolicy::kParagraph; |
| } else { |
| textProperty.fLineBreak = skottie::Shaper::LinebreakPolicy::kExplicit; |
| } |
| |
| if (this->direction == para::TextDirection::kRtl) { |
| textProperty.fDirection = skottie::Shaper::Direction::kRTL; |
| } else { |
| textProperty.fDirection = skottie::Shaper::Direction::kLTR; |
| } |
| |
| textProperty.fStrokeJoin = this->strokeJoin; |
| |
| textProperty.fBox = reinterpret_cast<SkRect*>(this->boundingBoxPtr)[0]; |
| textProperty.fFillColor = ptrToSkColor4f(this->fillColorPtr).toSkColor(); |
| textProperty.fStrokeColor = ptrToSkColor4f(this->strokeColorPtr).toSkColor(); |
| |
| return textProperty; |
| } |
| }; |
| |
| // WebTrack wraps a JS object that has a 'seek' method. |
| // Playback logic is kept there. |
| class WebTrack final : public skresources::ExternalTrackAsset { |
| public: |
| explicit WebTrack(emscripten::val player) : fPlayer(std::move(player)) {} |
| |
| private: |
| void seek(float t) override { |
| fPlayer.call<void>("seek", val(t)); |
| } |
| |
| const emscripten::val fPlayer; |
| }; |
| |
| class SkottieAssetProvider : public skottie::ResourceProvider { |
| public: |
| ~SkottieAssetProvider() override = default; |
| |
| // Tried using a map, but that gave strange errors like |
| // https://emscripten.org/docs/porting/guidelines/function_pointer_issues.html |
| // Not entirely sure why, but perhaps the iterator in the map was |
| // confusing enscripten. |
| using AssetVec = std::vector<std::pair<SkString, sk_sp<SkData>>>; |
| |
| static sk_sp<SkottieAssetProvider> Make(AssetVec assets, emscripten::val soundMap) { |
| return sk_sp<SkottieAssetProvider>(new SkottieAssetProvider(std::move(assets), |
| std::move(soundMap))); |
| } |
| |
| sk_sp<skottie::ImageAsset> loadImageAsset(const char[] /* path */, |
| const char name[], |
| const char[] /* id */) const override { |
| // For CK/Skottie we ignore paths & IDs, and identify images based solely on name. |
| if (auto data = this->findAsset(name)) { |
| auto codec = DecodeImageData(data); |
| if (!codec) { |
| return nullptr; |
| } |
| return skresources::MultiFrameImageAsset::Make(std::move(codec)); |
| } |
| |
| return nullptr; |
| } |
| |
| sk_sp<skresources::ExternalTrackAsset> loadAudioAsset(const char[] /* path */, |
| const char[] /* name */, |
| const char id[]) override { |
| emscripten::val player = this->findSoundAsset(id); |
| if (player.as<bool>()) { |
| return sk_make_sp<WebTrack>(std::move(player)); |
| } |
| |
| return nullptr; |
| } |
| |
| sk_sp<SkTypeface> loadTypeface(const char name[], const char[] /* url */) const override { |
| sk_sp<SkData> faceData = this->findAsset(name); |
| if (!faceData) { |
| return nullptr; |
| } |
| auto stream = std::make_unique<SkMemoryStream>(faceData); |
| return SkTypeface_FreeType::MakeFromStream(std::move(stream), SkFontArguments()); |
| } |
| |
| sk_sp<SkData> load(const char[]/*path*/, const char name[]) const override { |
| // Ignore paths. |
| return this->findAsset(name); |
| } |
| |
| private: |
| explicit SkottieAssetProvider(AssetVec assets, emscripten::val soundMap) |
| : fAssets(std::move(assets)) |
| , fSoundMap(std::move(soundMap)) {} |
| |
| sk_sp<SkData> findAsset(const char name[]) const { |
| for (const auto& asset : fAssets) { |
| if (asset.first.equals(name)) { |
| return asset.second; |
| } |
| } |
| |
| SkDebugf("Could not find %s\n", name); |
| return nullptr; |
| } |
| |
| emscripten::val findSoundAsset(const char name[]) const { |
| if (fSoundMap.as<bool>() && fSoundMap.hasOwnProperty("getPlayer")) { |
| emscripten::val player = fSoundMap.call<emscripten::val>("getPlayer", val(name)); |
| if (player.as<bool>() && player.hasOwnProperty("seek")) { |
| return player; |
| } |
| } |
| return emscripten::val::null(); |
| } |
| |
| const AssetVec fAssets; |
| const emscripten::val fSoundMap; |
| }; |
| |
| // Wraps a JS object with 'onError' and 'onWarning' methods. |
| class JSLogger final : public skottie::Logger { |
| public: |
| static sk_sp<JSLogger> Make(emscripten::val logger) { |
| return logger.as<bool>() |
| && logger.hasOwnProperty(kWrnFunc) |
| && logger.hasOwnProperty(kErrFunc) |
| ? sk_sp<JSLogger>(new JSLogger(std::move(logger))) |
| : nullptr; |
| } |
| |
| private: |
| explicit JSLogger(emscripten::val logger) : fLogger(std::move(logger)) {} |
| |
| void log(Level lvl, const char msg[], const char* json) override { |
| const auto* func = lvl == Level::kError ? kErrFunc : kWrnFunc; |
| fLogger.call<void>(func, std::string(msg), std::string(json)); |
| } |
| |
| inline static constexpr char kWrnFunc[] = "onWarning", |
| kErrFunc[] = "onError"; |
| |
| const emscripten::val fLogger; |
| }; |
| |
| class ManagedAnimation final : public SkRefCnt { |
| public: |
| static sk_sp<ManagedAnimation> Make(const std::string& json, |
| sk_sp<skottie::ResourceProvider> rp, |
| std::string prop_prefix, |
| emscripten::val logger) { |
| auto mgr = std::make_unique<skottie_utils::CustomPropertyManager>( |
| skottie_utils::CustomPropertyManager::Mode::kCollapseProperties, |
| prop_prefix.c_str()); |
| static constexpr char kInterceptPrefix[] = "__"; |
| auto pinterceptor = |
| sk_make_sp<skottie_utils::ExternalAnimationPrecompInterceptor>(rp, kInterceptPrefix); |
| skottie::Animation::Builder builder; |
| builder.setMarkerObserver(mgr->getMarkerObserver()) |
| .setPropertyObserver(mgr->getPropertyObserver()) |
| .setResourceProvider(rp) |
| .setPrecompInterceptor(std::move(pinterceptor)) |
| .setTextShapingFactory(SkShapers::BestAvailable()) |
| .setLogger(JSLogger::Make(std::move(logger))); |
| auto animation = builder.make(json.c_str(), json.size()); |
| auto slotManager = builder.getSlotManager(); |
| |
| return animation |
| ? sk_sp<ManagedAnimation>(new ManagedAnimation(std::move(animation), std::move(mgr), |
| std::move(slotManager), std::move(rp))) |
| : nullptr; |
| } |
| |
| ~ManagedAnimation() override = default; |
| |
| // skottie::Animation API |
| void render(SkCanvas* canvas, const SkRect* dst) const { fAnimation->render(canvas, dst); } |
| // Returns a damage rect. |
| SkRect seek(SkScalar t) { |
| sksg::InvalidationController ic; |
| fAnimation->seek(t, &ic); |
| return ic.bounds(); |
| } |
| // Returns a damage rect. |
| SkRect seekFrame(double t) { |
| sksg::InvalidationController ic; |
| fAnimation->seekFrame(t, &ic); |
| return ic.bounds(); |
| } |
| double duration() const { return fAnimation->duration(); } |
| double fps() const { return fAnimation->fps(); } |
| const SkSize& size() const { return fAnimation->size(); } |
| std::string version() const { return std::string(fAnimation->version().c_str()); } |
| |
| // CustomPropertyManager API |
| JSArray getColorProps() const { |
| JSArray props = emscripten::val::array(); |
| |
| for (const auto& cp : fPropMgr->getColorProps()) { |
| JSObject prop = emscripten::val::object(); |
| prop.set("key", cp); |
| prop.set("value", fPropMgr->getColor(cp)); |
| props.call<void>("push", prop); |
| } |
| |
| return props; |
| } |
| |
| JSArray getOpacityProps() const { |
| JSArray props = emscripten::val::array(); |
| |
| for (const auto& op : fPropMgr->getOpacityProps()) { |
| JSObject prop = emscripten::val::object(); |
| prop.set("key", op); |
| prop.set("value", fPropMgr->getOpacity(op)); |
| props.call<void>("push", prop); |
| } |
| |
| return props; |
| } |
| |
| JSArray getTextProps() const { |
| JSArray props = emscripten::val::array(); |
| |
| for (const auto& key : fPropMgr->getTextProps()) { |
| const auto txt = fPropMgr->getText(key); |
| JSObject txt_val = emscripten::val::object(); |
| txt_val.set("text", txt.fText.c_str()); |
| txt_val.set("size", txt.fTextSize); |
| |
| JSObject prop = emscripten::val::object(); |
| prop.set("key", key); |
| prop.set("value", std::move(txt_val)); |
| |
| props.call<void>("push", prop); |
| } |
| |
| return props; |
| } |
| |
| JSArray getTransformProps() const { |
| JSArray props = emscripten::val::array(); |
| |
| for (const auto& key : fPropMgr->getTransformProps()) { |
| const auto transform = fPropMgr->getTransform(key); |
| JSObject trans_val = emscripten::val::object(); |
| const float anchor[] = {transform.fAnchorPoint.fX, transform.fAnchorPoint.fY}; |
| const float position[] = {transform.fPosition.fX, transform.fPosition.fY}; |
| const float scale[] = {transform.fScale.fX, transform.fScale.fY}; |
| trans_val.set("anchor", MakeTypedArray(2, anchor)); |
| trans_val.set("position", MakeTypedArray(2, position)); |
| trans_val.set("scale", MakeTypedArray(2, scale)); |
| trans_val.set("rotation", transform.fRotation); |
| trans_val.set("skew", transform.fSkew); |
| trans_val.set("skew_axis", transform.fSkewAxis); |
| |
| JSObject prop = emscripten::val::object(); |
| prop.set("key", key); |
| prop.set("value", trans_val); |
| props.call<void>("push", prop); |
| } |
| |
| return props; |
| } |
| |
| bool setColor(const std::string& key, SkColor c) { |
| return fPropMgr->setColor(key, c); |
| } |
| |
| bool setOpacity(const std::string& key, float o) { |
| return fPropMgr->setOpacity(key, o); |
| } |
| |
| bool setText(const std::string& key, std::string text, float size) { |
| // preserve all other text fields |
| auto t = fPropMgr->getText(key); |
| |
| t.fText = SkString(text); |
| t.fTextSize = size; |
| |
| return fPropMgr->setText(key, t); |
| } |
| |
| bool setTransform(const std::string& key, SkScalar anchorX, SkScalar anchorY, |
| SkScalar posX, SkScalar posY, |
| SkScalar scaleX, SkScalar scaleY, |
| SkScalar rotation, SkScalar skew, SkScalar skewAxis) { |
| skottie::TransformPropertyValue transform; |
| transform.fAnchorPoint = {anchorX, anchorY}; |
| transform.fPosition = {posX, posY}; |
| transform.fScale = {scaleX, scaleY}; |
| transform.fRotation = rotation; |
| transform.fSkew = skew; |
| transform.fSkewAxis = skewAxis; |
| return fPropMgr->setTransform(key, transform); |
| } |
| |
| JSArray getMarkers() const { |
| JSArray markers = emscripten::val::array(); |
| for (const auto& m : fPropMgr->markers()) { |
| JSObject marker = emscripten::val::object(); |
| marker.set("name", m.name); |
| marker.set("t0" , m.t0); |
| marker.set("t1" , m.t1); |
| markers.call<void>("push", marker); |
| } |
| return markers; |
| } |
| |
| JSArray copyStringArrayToJSArray(skia_private::TArray<SkString> slotIDs) const { |
| JSArray retVal = emscripten::val::array(); |
| for (auto slotID : slotIDs) { |
| retVal.call<void>("push", emscripten::val(slotID.c_str())); |
| } |
| return retVal; |
| } |
| |
| // Slot Manager API |
| JSObject getSlotInfo() const { |
| JSObject slotInfoJS = emscripten::val::object(); |
| auto slotInfo = fSlotMgr->getSlotInfo(); |
| |
| slotInfoJS.set("colorSlotIDs", copyStringArrayToJSArray(slotInfo.fColorSlotIDs)); |
| slotInfoJS.set("scalarSlotIDs", copyStringArrayToJSArray(slotInfo.fScalarSlotIDs)); |
| slotInfoJS.set("vec2SlotIDs", copyStringArrayToJSArray(slotInfo.fVec2SlotIDs)); |
| slotInfoJS.set("imageSlotIDs", copyStringArrayToJSArray(slotInfo.fImageSlotIDs)); |
| slotInfoJS.set("textSlotIDs", copyStringArrayToJSArray(slotInfo.fTextSlotIDs)); |
| |
| return slotInfoJS; |
| } |
| |
| void getColorSlot(const std::string& slotID, WASMPointerF32 outPtr) { |
| SkColor4f c4f; |
| if (auto c = fSlotMgr->getColorSlot(SkString(slotID))) { |
| c4f = SkColor4f::FromColor(*c); |
| } else { |
| c4f = {-1, -1, -1, -1}; |
| } |
| memcpy(reinterpret_cast<float*>(outPtr), &c4f, 4 * sizeof(float)); |
| } |
| |
| emscripten::val getScalarSlot(const std::string& slotID) { |
| if (auto s = fSlotMgr->getScalarSlot(SkString(slotID))) { |
| return emscripten::val(*s); |
| } |
| return emscripten::val::null(); |
| } |
| |
| void getVec2Slot(const std::string& slotID, WASMPointerF32 outPtr) { |
| // [x, y, sentinel] |
| SkV3 vec3; |
| if (auto v = fSlotMgr->getVec2Slot(SkString(slotID))) { |
| vec3 = {v->x, v->y, 1}; |
| } else { |
| vec3 = {0, 0, -1}; |
| } |
| memcpy(reinterpret_cast<float*>(outPtr), vec3.ptr(), 3 * sizeof(float)); |
| } |
| |
| JSObject getTextSlot(const std::string& slotID) const { |
| if (auto textProp = fSlotMgr->getTextSlot(SkString(slotID))){ |
| JSObject text_val = emscripten::val::object(); |
| |
| text_val.set("typeface", textProp->fTypeface); |
| text_val.set("text", emscripten::val(textProp->fText.c_str())); |
| text_val.set("textSize", textProp->fTextSize); |
| text_val.set("minTextSize", textProp->fMinTextSize); |
| text_val.set("maxTextSize", textProp->fMaxTextSize); |
| text_val.set("strokeWidth", textProp->fStrokeWidth); |
| text_val.set("lineHeight", textProp->fLineHeight); |
| text_val.set("lineShift", textProp->fLineShift); |
| text_val.set("ascent", textProp->fAscent); |
| text_val.set("maxLines", textProp->fMaxLines); |
| |
| switch (textProp->fHAlign) { |
| case SkTextUtils::Align::kLeft_Align: |
| text_val.set("horizAlign", para::TextAlign::kLeft); |
| break; |
| case SkTextUtils::Align::kRight_Align: |
| text_val.set("horizAlign", para::TextAlign::kRight); |
| break; |
| case SkTextUtils::Align::kCenter_Align: |
| text_val.set("horizAlign", para::TextAlign::kCenter); |
| break; |
| default: |
| text_val.set("horizAlign", para::TextAlign::kLeft); |
| break; |
| } |
| |
| text_val.set("vertAlign", textProp->fVAlign); |
| text_val.set("resize", textProp->fResize); |
| |
| if (textProp->fLineBreak == skottie::Shaper::LinebreakPolicy::kParagraph) { |
| text_val.set("linebreak", SkUnicode::LineBreakType::kSoftLineBreak); |
| } else { |
| text_val.set("linebreak", SkUnicode::LineBreakType::kHardLineBreak); |
| } |
| |
| if (textProp->fDirection == skottie::Shaper::Direction::kLTR) { |
| text_val.set("direction", para::TextDirection::kLtr); |
| } else { |
| text_val.set("direction", para::TextDirection::kRtl); |
| } |
| text_val.set("strokeJoin", textProp->fStrokeJoin); |
| |
| text_val.set("fillColor", MakeTypedArray(4, SkColor4f::FromColor(textProp->fFillColor) |
| .vec())); |
| |
| text_val.set("strokeColor", MakeTypedArray(4, SkColor4f::FromColor(textProp->fStrokeColor) |
| .vec())); |
| |
| const float box[] = {textProp->fBox.fLeft, textProp->fBox.fTop, |
| textProp->fBox.fRight, textProp->fBox.fBottom}; |
| text_val.set("boundingBox", MakeTypedArray(4, box)); |
| return text_val; |
| } |
| return emscripten::val::null(); |
| } |
| |
| bool setImageSlot(const std::string& slotID, const std::string& assetName) { |
| // look for resource in preloaded SkottieAssetProvider |
| return fSlotMgr->setImageSlot(SkString(slotID), fResourceProvider->loadImageAsset(nullptr, |
| assetName.data(), |
| nullptr)); |
| } |
| |
| bool setColorSlot(const std::string& slotID, SkColor c) { |
| return fSlotMgr->setColorSlot(SkString(slotID), c); |
| } |
| |
| bool setScalarSlot(const std::string& slotID, float s) { |
| return fSlotMgr->setScalarSlot(SkString(slotID), s); |
| } |
| |
| bool setVec2Slot(const std::string& slotID, SkV2 v) { |
| return fSlotMgr->setVec2Slot(SkString(slotID), v); |
| } |
| |
| bool attachEditor(const std::string& layerID, size_t layerIndex) { |
| if (fTextEditor) { |
| fTextEditor->setEnabled(false); |
| fTextEditor = nullptr; |
| } |
| |
| if (layerID.empty()) { |
| return true; |
| } |
| |
| auto txt_handle = fPropMgr->getTextHandle(layerID, layerIndex); |
| if (!txt_handle) { |
| return false; |
| } |
| |
| std::vector<std::unique_ptr<skottie::TextPropertyHandle>> deps; |
| for (size_t i = 0; ; ++i) { |
| if (i == layerIndex) { |
| continue; |
| } |
| |
| auto dep_handle = fPropMgr->getTextHandle(layerID, i); |
| if (!dep_handle) { |
| break; |
| } |
| deps.push_back(std::move(dep_handle)); |
| } |
| |
| fTextEditor = sk_make_sp<skottie_utils::TextEditor>(std::move(txt_handle), |
| std::move(deps)); |
| return true; |
| } |
| |
| void enableEditor(bool enable) { |
| if (fTextEditor) { |
| fTextEditor->setEnabled(enable); |
| } |
| } |
| |
| bool dispatchEditorKey(const std::string& key) { |
| // Map some useful keys to the current (odd) text editor bindings. |
| // TODO: Add support for custom bindings in the editor. |
| auto key2char = [](const std::string& key) -> SkUnichar { |
| // Special keys. |
| if (key == "ArrowLeft") return '['; |
| if (key == "ArrowRight") return ']'; |
| if (key == "Backspace") return '\\'; |
| |
| const char* str = key.c_str(); |
| const char* end = str + key.size(); |
| const SkUnichar uch = SkUTF::NextUTF8(&str, end); |
| |
| // Pass through single code points, ignore everything else. |
| return str == end ? uch : -1; |
| }; |
| |
| if (fTextEditor) { |
| const auto uch = key2char(key); |
| if (uch != -1) { |
| return fTextEditor->onCharInput(uch); |
| } |
| } |
| |
| return false; |
| } |
| |
| bool dispatchEditorPointer(float x, float y, skui::InputState state, skui::ModifierKey mod) { |
| return fTextEditor |
| ? fTextEditor->onMouseInput(x, y, state, mod) |
| : false; |
| } |
| |
| void setEditorCursorWeight(float w) { |
| if (fTextEditor) { |
| fTextEditor->setCursorWeight(w); |
| } |
| } |
| |
| bool setTextSlot(const std::string& slotID, SimpleSlottableTextProperty t) { |
| return fSlotMgr->setTextSlot(SkString(slotID), t); |
| } |
| |
| private: |
| ManagedAnimation(sk_sp<skottie::Animation> animation, |
| std::unique_ptr<skottie_utils::CustomPropertyManager> propMgr, |
| sk_sp<skottie::SlotManager> slotMgr, |
| sk_sp<skresources::ResourceProvider> rp) |
| : fAnimation(std::move(animation)) |
| , fPropMgr(std::move(propMgr)) |
| , fSlotMgr(std::move(slotMgr)) |
| , fResourceProvider(std::move(rp)) |
| {} |
| |
| const sk_sp<skottie::Animation> fAnimation; |
| const std::unique_ptr<skottie_utils::CustomPropertyManager> fPropMgr; |
| const sk_sp<skottie::SlotManager> fSlotMgr; |
| const sk_sp<skresources::ResourceProvider> fResourceProvider; |
| |
| sk_sp<skottie_utils::TextEditor> fTextEditor; |
| }; |
| |
| } // anonymous ns |
| |
| EMSCRIPTEN_BINDINGS(Skottie) { |
| // Animation things (may eventually go in own library) |
| class_<skottie::Animation>("Animation") |
| .smart_ptr<sk_sp<skottie::Animation>>("sk_sp<Animation>") |
| .function("version", optional_override([](skottie::Animation& self)->std::string { |
| return std::string(self.version().c_str()); |
| })) |
| .function("_size", optional_override([](skottie::Animation& self, |
| WASMPointerF32 oPtr)->void { |
| SkSize* output = reinterpret_cast<SkSize*>(oPtr); |
| *output = self.size(); |
| })) |
| .function("duration", &skottie::Animation::duration) |
| .function("fps" , &skottie::Animation::fps) |
| .function("seek", optional_override([](skottie::Animation& self, SkScalar t)->void { |
| self.seek(t); |
| })) |
| .function("seekFrame", optional_override([](skottie::Animation& self, double t)->void { |
| self.seekFrame(t); |
| })) |
| .function("_render", optional_override([](skottie::Animation& self, SkCanvas* canvas, |
| WASMPointerF32 fPtr)->void { |
| const SkRect* dst = reinterpret_cast<const SkRect*>(fPtr); |
| self.render(canvas, dst); |
| }), allow_raw_pointers()); |
| |
| function("MakeAnimation", optional_override([](std::string json)->sk_sp<skottie::Animation> { |
| return skottie::Animation::Make(json.c_str(), json.length()); |
| })); |
| constant("skottie", true); |
| |
| class_<ManagedAnimation>("ManagedAnimation") |
| .smart_ptr<sk_sp<ManagedAnimation>>("sk_sp<ManagedAnimation>") |
| .function("version" , &ManagedAnimation::version) |
| .function("_size", optional_override([](ManagedAnimation& self, |
| WASMPointerF32 oPtr)->void { |
| SkSize* output = reinterpret_cast<SkSize*>(oPtr); |
| *output = self.size(); |
| })) |
| .function("duration" , &ManagedAnimation::duration) |
| .function("fps" , &ManagedAnimation::fps) |
| .function("_render", optional_override([](ManagedAnimation& self, SkCanvas* canvas, |
| WASMPointerF32 fPtr)->void { |
| const SkRect* dst = reinterpret_cast<const SkRect*>(fPtr); |
| self.render(canvas, dst); |
| }), allow_raw_pointers()) |
| .function("_seek", optional_override([](ManagedAnimation& self, SkScalar t, |
| WASMPointerF32 fPtr) { |
| SkRect* damageRect = reinterpret_cast<SkRect*>(fPtr); |
| damageRect[0] = self.seek(t); |
| })) |
| .function("_seekFrame", optional_override([](ManagedAnimation& self, double frame, |
| WASMPointerF32 fPtr) { |
| SkRect* damageRect = reinterpret_cast<SkRect*>(fPtr); |
| damageRect[0] = self.seekFrame(frame); |
| })) |
| .function("seekFrame" , &ManagedAnimation::seekFrame) |
| .function("_setColor" , optional_override([](ManagedAnimation& self, const std::string& key, WASMPointerF32 cPtr) { |
| float* fourFloats = reinterpret_cast<float*>(cPtr); |
| SkColor4f color = { fourFloats[0], fourFloats[1], fourFloats[2], fourFloats[3] }; |
| return self.setColor(key, color.toSkColor()); |
| })) |
| .function("_setTransform" , optional_override([](ManagedAnimation& self, |
| const std::string& key, |
| WASMPointerF32 transformData) { |
| // transform value info is passed in as an array of 9 scalars in the following order: |
| // anchor xy, position xy, scalexy, rotation, skew, skew axis |
| auto transform = reinterpret_cast<SkScalar*>(transformData); |
| return self.setTransform(key, transform[0], transform[1], transform[2], transform[3], |
| transform[4], transform[5], transform[6], transform[7], transform[8]); |
| })) |
| .function("getMarkers" , &ManagedAnimation::getMarkers) |
| .function("getColorProps" , &ManagedAnimation::getColorProps) |
| .function("getOpacityProps" , &ManagedAnimation::getOpacityProps) |
| .function("setOpacity" , &ManagedAnimation::setOpacity) |
| .function("getTextProps" , &ManagedAnimation::getTextProps) |
| .function("setText" , &ManagedAnimation::setText) |
| .function("getTransformProps", &ManagedAnimation::getTransformProps) |
| .function("getSlotInfo" , &ManagedAnimation::getSlotInfo) |
| .function("_getColorSlot" , &ManagedAnimation::getColorSlot) |
| .function("_setColorSlot" , optional_override([](ManagedAnimation& self, const std::string& key, WASMPointerF32 cPtr) { |
| SkColor4f color = ptrToSkColor4f(cPtr); |
| return self.setColorSlot(key, color.toSkColor()); |
| })) |
| .function("_getVec2Slot" , &ManagedAnimation::getVec2Slot) |
| .function("_setVec2Slot" , optional_override([](ManagedAnimation& self, const std::string& key, WASMPointerF32 vPtr) { |
| float* twoFloats = reinterpret_cast<float*>(vPtr); |
| SkV2 vec2 = {twoFloats[0], twoFloats[1]}; |
| return self.setVec2Slot(key, vec2); |
| })) |
| .function("getScalarSlot" , &ManagedAnimation::getScalarSlot) |
| .function("setScalarSlot" , &ManagedAnimation::setScalarSlot) |
| .function("attachEditor" , &ManagedAnimation::attachEditor) |
| .function("enableEditor" , &ManagedAnimation::enableEditor) |
| .function("dispatchEditorKey" , &ManagedAnimation::dispatchEditorKey) |
| .function("dispatchEditorPointer", &ManagedAnimation::dispatchEditorPointer) |
| .function("setEditorCursorWeight", &ManagedAnimation::setEditorCursorWeight) |
| .function("getTextSlot" , &ManagedAnimation::getTextSlot) |
| .function("_setTextSlot" , &ManagedAnimation::setTextSlot) |
| .function("setImageSlot" , &ManagedAnimation::setImageSlot); |
| |
| function("_MakeManagedAnimation", optional_override([](std::string json, |
| size_t assetCount, |
| WASMPointerU32 nptr, |
| WASMPointerU32 dptr, |
| WASMPointerU32 sptr, |
| std::string prop_prefix, |
| emscripten::val soundMap, |
| emscripten::val logger) |
| ->sk_sp<ManagedAnimation> { |
| const auto assetNames = reinterpret_cast<char** >(nptr); |
| const auto assetDatas = reinterpret_cast<uint8_t**>(dptr); |
| const auto assetSizes = reinterpret_cast<size_t* >(sptr); |
| |
| SkottieAssetProvider::AssetVec assets; |
| assets.reserve(assetCount); |
| |
| for (size_t i = 0; i < assetCount; i++) { |
| auto name = SkString(assetNames[i]); |
| auto bytes = SkData::MakeFromMalloc(assetDatas[i], assetSizes[i]); |
| assets.push_back(std::make_pair(std::move(name), std::move(bytes))); |
| } |
| |
| // DataURIResourceProviderProxy needs codecs registered to try to process Base64 encoded |
| // images. |
| static SkOnce once; |
| once([] { |
| #if defined(SK_CODEC_DECODES_PNG) |
| SkCodecs::Register(SkPngDecoder::Decoder()); |
| #endif |
| #if defined(SK_CODEC_DECODES_JPEG) |
| SkCodecs::Register(SkJpegDecoder::Decoder()); |
| #endif |
| #if defined(SK_CODEC_DECODES_GIF) |
| SkCodecs::Register(SkGifDecoder::Decoder()); |
| #endif |
| #if defined(SK_CODEC_DECODES_WEBP) |
| SkCodecs::Register(SkWebpDecoder::Decoder()); |
| #endif |
| }); |
| |
| sk_sp<SkFontMgr> fontmgr; |
| #if !defined(CK_NO_FONTS) |
| fontmgr = SkFontMgr_New_Custom_Empty(); |
| #endif |
| |
| return ManagedAnimation::Make(json, |
| skresources::DataURIResourceProviderProxy::Make( |
| SkottieAssetProvider::Make(std::move(assets), |
| std::move(soundMap)), |
| skresources::ImageDecodeStrategy::kPreDecode, |
| std::move(fontmgr)), |
| prop_prefix, std::move(logger)); |
| })); |
| |
| enum_<skui::InputState>("InputState") |
| .value("Down", skui::InputState::kDown) |
| .value("Up", skui::InputState::kUp) |
| .value("Move", skui::InputState::kMove) |
| .value("Right", skui::InputState::kRight) |
| .value("Left", skui::InputState::kLeft); |
| |
| enum_<skui::ModifierKey>("ModifierKey") |
| .value("None", skui::ModifierKey::kNone) |
| .value("Shift", skui::ModifierKey::kShift) |
| .value("Control", skui::ModifierKey::kControl) |
| .value("Option", skui::ModifierKey::kOption) |
| .value("Command", skui::ModifierKey::kCommand) |
| .value("FirstPress", skui::ModifierKey::kFirstPress); |
| |
| enum_<skottie::Shaper::VAlign>("VerticalTextAlign") |
| .value("Top", skottie::Shaper::VAlign::kTop) |
| .value("TopBaseline", skottie::Shaper::VAlign::kTopBaseline) |
| .value("VisualTop", skottie::Shaper::VAlign::kVisualTop) |
| .value("VisualCenter", skottie::Shaper::VAlign::kVisualCenter) |
| .value("VisualBottom", skottie::Shaper::VAlign::kVisualBottom); |
| |
| enum_<skottie::Shaper::ResizePolicy>("ResizePolicy") |
| .value("None", skottie::Shaper::ResizePolicy::kNone) |
| .value("ScaleToFit", skottie::Shaper::ResizePolicy::kScaleToFit) |
| .value("DownscaleToFit", skottie::Shaper::ResizePolicy::kDownscaleToFit); |
| |
| value_object<SimpleSlottableTextProperty>("SlottableTextProperty") |
| .field("typeface", &SimpleSlottableTextProperty::typeface) |
| .field("text", &SimpleSlottableTextProperty::text) |
| .field("textSize", &SimpleSlottableTextProperty::textSize) |
| .field("minTextSize", &SimpleSlottableTextProperty::minTextSize) |
| .field("maxTextSize", &SimpleSlottableTextProperty::maxTextSize) |
| .field("strokeWidth", &SimpleSlottableTextProperty::strokeWidth) |
| .field("lineHeight", &SimpleSlottableTextProperty::lineHeight) |
| .field("lineShift", &SimpleSlottableTextProperty::lineShift) |
| .field("ascent", &SimpleSlottableTextProperty::ascent) |
| .field("maxLines", &SimpleSlottableTextProperty::maxLines) |
| .field("horizAlign", &SimpleSlottableTextProperty::horizAlign) |
| .field("vertAlign", &SimpleSlottableTextProperty::vertAlign) |
| .field("strokeJoin", &SimpleSlottableTextProperty::strokeJoin) |
| .field("direction", &SimpleSlottableTextProperty::direction) |
| .field("linebreak", &SimpleSlottableTextProperty::lineBreak) |
| .field("resize", &SimpleSlottableTextProperty::resize) |
| .field("_fillColorPtr", &SimpleSlottableTextProperty::fillColorPtr) |
| .field("_strokeColorPtr", &SimpleSlottableTextProperty::strokeColorPtr) |
| .field("_boundingBoxPtr", &SimpleSlottableTextProperty::boundingBoxPtr); |
| |
| constant("managed_skottie", true); |
| } |