diff --git a/README.md b/README.md index ba4618ce..b5d63046 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,10 @@ Note that these examples are required EFL `elementary` package for launching. If

## Tools +### Online Viewer +Please visit [ThorVG online viewer](https://samsung.github.io/thorvg.viewer) + +[ThorVG online viewer](https://samsung.github.io/thorvg.viewer) uses ThorVG wasm library to render the resource locally in your browser. To test your SVG resource drag and drop it to the browser window. ### SVG to PNG ThorVG provides an executable `svg2png` converter which generate a PNG file from a SVG file. diff --git a/src/lib/tvgPictureImpl.h b/src/lib/tvgPictureImpl.h index bf9f4ff5..1ac191ad 100644 --- a/src/lib/tvgPictureImpl.h +++ b/src/lib/tvgPictureImpl.h @@ -22,6 +22,7 @@ #ifndef _TVG_PICTURE_IMPL_H_ #define _TVG_PICTURE_IMPL_H_ +#include #include "tvgPaint.h" #include "tvgLoaderMgr.h" diff --git a/src/loaders/svg/tvgSvgPath.cpp b/src/loaders/svg/tvgSvgPath.cpp index 9163d1ab..45ed8346 100644 --- a/src/loaders/svg/tvgSvgPath.cpp +++ b/src/loaders/svg/tvgSvgPath.cpp @@ -21,6 +21,8 @@ */ #include #include +#include +#include #include "tvgSvgPath.h" static char* _skipComma(const char* content) diff --git a/src/loaders/svg/tvgSvgSceneBuilder.cpp b/src/loaders/svg/tvgSvgSceneBuilder.cpp index 5ba194e5..3ee075f4 100644 --- a/src/loaders/svg/tvgSvgSceneBuilder.cpp +++ b/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -20,6 +20,7 @@ * SOFTWARE. */ #include +#include #include "tvgSvgSceneBuilder.h" #include "tvgSvgPath.h" diff --git a/src/meson.build b/src/meson.build index 8741ef28..7e19d039 100644 --- a/src/meson.build +++ b/src/meson.build @@ -34,6 +34,16 @@ thorvg_dep = declare_dependency( link_with : thorvg_lib ) +if (cc.get_id() == 'emscripten') + + subdir('wasm') + + executable('thorvg-wasm', + [], + dependencies : [thorvg_dep, thorvg_wasm_dep], + ) +endif + pkg_mod = import('pkgconfig') pkg_mod.generate( diff --git a/src/wasm/meson.build b/src/wasm/meson.build new file mode 100644 index 00000000..61bddc47 --- /dev/null +++ b/src/wasm/meson.build @@ -0,0 +1,5 @@ +source_file = files('thorvgwasm.cpp') + + thorvg_wasm_dep = declare_dependency(include_directories + : include_directories('.'), sources + : source_file) diff --git a/src/wasm/thorvgwasm.cpp b/src/wasm/thorvgwasm.cpp new file mode 100644 index 00000000..d227cb0d --- /dev/null +++ b/src/wasm/thorvgwasm.cpp @@ -0,0 +1,189 @@ +#include + +#include + +using namespace emscripten; +using namespace std; +using namespace tvg; + +string defaultData(""); + +class __attribute__((visibility("default"))) ThorvgWasm +{ +public: + static unique_ptr create() + { + return unique_ptr(new ThorvgWasm()); + } + + string getError() + { + return mErrorMsg; + } + + bool load(string data, int width, int height) + { + uint32_t pw, ph; + float w, h; + + mErrorMsg = "None"; + + if (!mSwCanvas) { + mErrorMsg = "Canvas is NULL"; + return false; + } + + mPicture = Picture::gen().release(); + if (!mPicture) { + mErrorMsg = "Picture get failed"; + return false; + } + + mSwCanvas->clear(); + + if (data.empty()) data = defaultData; + const char *cdata = data.c_str(); + if (mPicture->load(cdata, strlen(cdata)) != Result::Success) { + + /* mPicture is not handled as unique_ptr yet, so delete here */ + delete(mPicture); + mPicture = nullptr; + + mErrorMsg = "Load failed"; + return false; + } + + /* get default size */ + mPicture->viewbox(nullptr, nullptr, &w, &h); + + pw = mDefaultWidth; + ph = mDefaultHeight; + mDefaultWidth = static_cast(w); + mDefaultHeight = static_cast(h); + + if (pw != mDefaultWidth || ph != mDefaultHeight) { + updateScale(); + } + + updateSize(width, height); + + if (mSwCanvas->push(unique_ptr(mPicture)) != Result::Success) { + mErrorMsg = "Push failed"; + return false; + } + + return true; + } + + void update(int width, int height) + { + mErrorMsg = "None"; + + if (!mSwCanvas) { + mErrorMsg = "Canvas is NULL"; + return; + } + + if (!mPicture) { + mErrorMsg = "Picture is NULL"; + return; + } + + if (mWidth == width && mHeight == height) { + return; + } + + updateSize(width, height); + + if (mSwCanvas->update(mPicture) != Result::Success) { + mErrorMsg = "Update failed"; + return; + } + + return; + } + + val render() + { + mErrorMsg = "None"; + + if (!mSwCanvas) { + mErrorMsg = "Canvas is NULL"; + return val(typed_memory_view(0, nullptr)); + } + + if (mSwCanvas->draw() != Result::Success) { + mErrorMsg = "Draw failed"; + return val(typed_memory_view(0, nullptr)); + } + + mSwCanvas->sync(); + + return val(typed_memory_view(mWidth * mHeight * 4, mBuffer.get())); + } + + float getScale() + { + return mScale; + } + +private: + explicit ThorvgWasm() + { + mErrorMsg = "None"; + + Initializer::init(CanvasEngine::Sw, 0); + mSwCanvas = SwCanvas::gen(); + if (!mSwCanvas) { + mErrorMsg = "Canvas get failed"; + return; + } + } + + void updateSize(int width, int height) + { + if (!mSwCanvas) return; + if (mWidth == width && mHeight == height) return; + + mWidth = width; + mHeight = height; + mBuffer = make_unique(mWidth * mHeight * 4); + mSwCanvas->target((uint32_t *)mBuffer.get(), mWidth, mWidth, mHeight, SwCanvas::ABGR8888); + + updateScale(); + } + + void updateScale() + { + if (!mPicture) return; + + float scaleX = static_cast(mWidth) / static_cast(mDefaultWidth); + float scaleY = static_cast(mHeight) / static_cast(mDefaultHeight); + mScale = scaleX < scaleY ? scaleX : scaleY; + + mPicture->scale(mScale); + } + +private: + string mErrorMsg; + unique_ptr< SwCanvas > mSwCanvas = nullptr; + Picture* mPicture = nullptr; + unique_ptr mBuffer = nullptr; + + uint32_t mWidth{0}; + uint32_t mHeight{0}; + uint32_t mDefaultWidth{0}; + uint32_t mDefaultHeight{0}; + float mScale{0}; +}; + +// Binding code +EMSCRIPTEN_BINDINGS(thorvg_bindings) { + class_("ThorvgWasm") + .constructor(&ThorvgWasm::create) + .function("getError", &ThorvgWasm::getError, allow_raw_pointers()) + .function("load", &ThorvgWasm::load, allow_raw_pointers()) + .function("update", &ThorvgWasm::update) + .function("render", &ThorvgWasm::render) + .function("getScale", &ThorvgWasm::getScale); +} diff --git a/test/wasm_test.html b/test/wasm_test.html new file mode 100644 index 00000000..b3835965 --- /dev/null +++ b/test/wasm_test.html @@ -0,0 +1,25 @@ + + + + + + diff --git a/wasm_build.sh b/wasm_build.sh new file mode 100755 index 00000000..ae819605 --- /dev/null +++ b/wasm_build.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +if [ -z "$1" ]; then + echo "Emscripten SDK PATH is not provided" + echo "Usage: wasm_build EMSDK_PATH" + exit 1; +fi + +if [ ! -d "./builddir_wasm" ]; then + sed "s|EMSDK:|$1|g" wasm_cross.txt > /tmp/.wasm_cross.txt + meson -Dbindings=[''] -Db_lto=true -Ddefault_library=static --cross-file /tmp/.wasm_cross.txt builddir_wasm + cp ./test/wasm_test.html builddir_wasm/src/index.html +fi + +ninja -C builddir_wasm/ +echo "RESULT:" +echo " thorvg-wasm.wasm and thorvg-wasm.js can be found in builddir_wasm/src folder" +ls -lrt builddir_wasm/src/thorvg-wasm.* diff --git a/wasm_cross.txt b/wasm_cross.txt new file mode 100644 index 00000000..eaf7e162 --- /dev/null +++ b/wasm_cross.txt @@ -0,0 +1,19 @@ +[binaries] +c = 'EMSDK:upstream/emscripten/emcc.py' +cpp = 'EMSDK:upstream/emscripten/em++.py' +ar = 'EMSDK:upstream/emscripten/emar.py' + +[properties] +root = 'EMSDK:upstream/emscripten/system' +cpp_args = ['--bind' , '-s' , 'WASM=1' , '-s' , 'ALLOW_MEMORY_GROWTH=1' , '-s' , 'FILESYSTEM=0' , '-O2'] +cpp_link_args = ['--bind' , '-s' , 'WASM=1' , '-s' , 'ALLOW_MEMORY_GROWTH=1' , '-s' , 'FILESYSTEM=0' , '-O2'] +shared_lib_suffix = 'js' +static_lib_suffix = 'js' +shared_module_suffix = 'js' +exe_suffix = 'js' + +[host_machine] +system = 'emscripten' +cpu_family = 'x86' +cpu = 'i686' +endian = 'little' \ No newline at end of file