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