thorvg viewer: introduce thorvg viewer
authorShinwoo Kim <cinoo.kim@samsung.com>
Tue, 15 Sep 2020 06:40:41 +0000 (15:40 +0900)
committerHermet Park <chuneon.park@samsung.com>
Wed, 14 Oct 2020 08:04:59 +0000 (17:04 +0900)
Support Emscripten wasm build for thorvg viewer

Change-Id: I0a70a721acb6446733f714a6d30273949b61be2c

README.md
src/lib/tvgPictureImpl.h
src/loaders/svg/tvgSvgPath.cpp
src/loaders/svg/tvgSvgSceneBuilder.cpp
src/meson.build
src/wasm/meson.build [new file with mode: 0644]
src/wasm/thorvgwasm.cpp [new file with mode: 0644]
test/wasm_test.html [new file with mode: 0644]
wasm_build.sh [new file with mode: 0755]
wasm_cross.txt [new file with mode: 0644]

index ba4618c..b5d6304 100644 (file)
--- a/README.md
+++ b/README.md
@@ -167,6 +167,10 @@ Note that these examples are required EFL `elementary` package for launching. If
 <br />
 <br />
 ## 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.
 
index bf9f4ff..1ac191a 100644 (file)
@@ -22,6 +22,7 @@
 #ifndef _TVG_PICTURE_IMPL_H_
 #define _TVG_PICTURE_IMPL_H_
 
+#include <string>
 #include "tvgPaint.h"
 #include "tvgLoaderMgr.h"
 
index 9163d1a..45ed834 100644 (file)
@@ -21,6 +21,8 @@
  */
 #include <string.h>
 #include <math.h>
+#include <ctype.h>
+#include <locale.h>
 #include "tvgSvgPath.h"
 
 static char* _skipComma(const char* content)
index 5ba194e..3ee075f 100644 (file)
@@ -20,6 +20,7 @@
  * SOFTWARE.
  */
 #include <math.h>
+#include <string>
 #include "tvgSvgSceneBuilder.h"
 #include "tvgSvgPath.h"
 
index 8741ef2..7e19d03 100644 (file)
@@ -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 (file)
index 0000000..61bddc4
--- /dev/null
@@ -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 (file)
index 0000000..d227cb0
--- /dev/null
@@ -0,0 +1,189 @@
+#include <thorvg.h>
+
+#include <emscripten/bind.h>
+
+using namespace emscripten;
+using namespace std;
+using namespace tvg;
+
+string defaultData("<svg height=\"1000\" viewBox=\"0 0 1000 1000\" width=\"1000\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M1.171875.25H1004.687475V1005.6094H1.171875Z\" fill=\"#09bbf1\" stroke-width=\"3.910218\"/><g fill=\"#252f35\"><g stroke-width=\"3.864492\"><path d=\"M256.61221 100.51736H752.8963V386.99554H256.61221Z\"/><path d=\"M201.875 100.51736H238.366478V386.99554H201.875Z\"/><path d=\"M771.14203 100.51736H807.633508V386.99554H771.14203Z\"/></g><path d=\"M420.82388 380H588.68467V422.805317H420.82388Z\" stroke-width=\"3.227\"/><path d=\"m420.82403 440.7101v63.94623l167.86079 25.5782V440.7101Z\"/><path d=\"M420.82403 523.07258V673.47362L588.68482 612.59701V548.13942Z\"/></g><g fill=\"#222f35\"><path d=\"M420.82403 691.37851 588.68482 630.5019 589 834H421Z\"/><path d=\"m420.82403 852.52249h167.86079v28.64782H420.82403v-28.64782 0 0\"/><path d=\"m439.06977 879.17031c0 0-14.90282 8.49429-18.24574 15.8161-4.3792 9.59153 0 31.63185 0 31.63185h167.86079c0 0 4.3792-22.04032 0-31.63185-3.34292-7.32181-18.24574-15.8161-18.24574-15.8161z\"/></g><path d=\"m280 140h15v55l8 10 8-10v-55h15v60l-23 25-23-25z\" fill=\"#09bbf1\"/><path d=\"m335 140v80h45v-50h-25v10h10v30h-15v-57h18v-13z\" fill=\"#09bbf1\"/></svg>");
+
+class __attribute__((visibility("default"))) ThorvgWasm
+{
+public:
+    static unique_ptr<ThorvgWasm> create()
+    {
+        return unique_ptr<ThorvgWasm>(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<uint32_t>(w);
+        mDefaultHeight = static_cast<uint32_t>(h);
+
+        if (pw != mDefaultWidth || ph != mDefaultHeight) {
+            updateScale();
+        }
+
+        updateSize(width, height);
+
+        if (mSwCanvas->push(unique_ptr<Picture>(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<uint8_t>(0, nullptr));
+        }
+
+        if (mSwCanvas->draw() != Result::Success) {
+            mErrorMsg = "Draw failed";
+            return val(typed_memory_view<uint8_t>(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<uint8_t[]>(mWidth * mHeight * 4);
+        mSwCanvas->target((uint32_t *)mBuffer.get(), mWidth, mWidth, mHeight, SwCanvas::ABGR8888);
+
+        updateScale();
+    }
+
+    void updateScale()
+    {
+        if (!mPicture) return;
+
+        float scaleX = static_cast<float>(mWidth) / static_cast<float>(mDefaultWidth);
+        float scaleY = static_cast<float>(mHeight) / static_cast<float>(mDefaultHeight);
+        mScale = scaleX < scaleY ? scaleX : scaleY;
+
+        mPicture->scale(mScale);
+    }
+
+private:
+    string                 mErrorMsg;
+    unique_ptr< SwCanvas > mSwCanvas = nullptr;
+    Picture*               mPicture = nullptr;
+    unique_ptr<uint8_t[]>  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>("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 (file)
index 0000000..b383596
--- /dev/null
@@ -0,0 +1,25 @@
+<!doctype html>
+<html>
+  <canvas id="thorvg" width="100" height="100"></canvas>
+  <script>
+    var Module = {
+      onRuntimeInitialized: function() {
+
+        class SvgViewer {
+          constructor() {
+             var instance = new Module.ThorvgWasm();
+             this.canvas = document.getElementById("thorvg");
+             var context = this.canvas.getContext('2d');
+             var buffer = instance.render(this.canvas.width, this.canvas.height);
+             var clampedBuffer = Uint8ClampedArray.from(buffer);
+             var imageData = new ImageData(clampedBuffer, this.canvas.width, this.canvas.height);
+             context.putImageData(imageData, 0, 0);
+          }
+        }
+
+        var instance = new SvgViewer();
+      }
+    };
+  </script>
+  <script src="thorvg-wasm.js"></script>
+</html>
diff --git a/wasm_build.sh b/wasm_build.sh
new file mode 100755 (executable)
index 0000000..ae81960
--- /dev/null
@@ -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 (file)
index 0000000..eaf7e16
--- /dev/null
@@ -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