Bootstrap a very simple viewer implementation in CanvasKit

Adds a "viewer" option to the build system that brings in tooling code
and sample code. Adds a very simple "MakeSlide" binding that knows
how to create the WavyPathText sample slide. Adds viewer.html with
code to animate viewer slides.

This can hopefully be the starting point for future work on bringing
viewer to CanvasKit.

Change-Id: Ia26e08726384b40b3f544fe8254f430dc9db08db
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/278892
Reviewed-by: Kevin Lubick <kjlubick@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index b457731..9112d09 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1423,7 +1423,7 @@
 
     # We need the GLTestContext on Vulkan-only builds for the persistent GL context workaround in
     # in GrContextFactory. This only matters for OSes that can run Vulkan.
-    if (skia_use_gl || skia_use_vulkan) {
+    if ((skia_use_gl || skia_use_vulkan) && target_cpu != "wasm") {
       if (is_android || skia_use_egl) {
         sources += [ "tools/gpu/gl/egl/CreatePlatformGLTestContext_egl.cpp" ]
         libs += [ "EGL" ]
@@ -1531,7 +1531,6 @@
     sources = [
       "tools/AndroidSkDebugToStdOut.cpp",
       "tools/AutoreleasePool.h",
-      "tools/CrashHandler.cpp",
       "tools/DDLPromiseImageHelper.cpp",
       "tools/DDLTileHelper.cpp",
       "tools/LsanSuppressions.cpp",
@@ -1559,6 +1558,9 @@
       "tools/timer/TimeUtils.h",
       "tools/timer/Timer.cpp",
     ]
+    if (target_cpu != "wasm") {
+      sources += [ "tools/CrashHandler.cpp" ]
+    }
     libs = []
     if (is_ios) {
       sources += [ "tools/ios_utils.m" ]
@@ -1797,30 +1799,33 @@
     ]
   }
 
-  if (target_cpu != "wasm") {
-    import("gn/samples.gni")
-    test_lib("samples") {
-      sources = samples_sources
-      public_deps = [
-        ":tool_utils",
-      ]
-      deps = [
-        ":experimental_svg_model",
-        ":flags",
-        ":gpu_tool_utils",
-        ":xml",
-        "modules/skparagraph:samples",
-        "modules/skshaper",
-      ]
-
-      if (skia_use_lua) {
-        sources += [ "samplecode/SampleLua.cpp" ]
-        deps += [
-          ":lua",
-          "//third_party/lua",
-        ]
-      }
+  import("gn/samples.gni")
+  test_lib("samples") {
+    sources = samples_sources
+    if (skia_enable_ccpr) {
+      sources += samples_sources_ccpr
     }
+    public_deps = [
+      ":tool_utils",
+    ]
+    deps = [
+      ":experimental_svg_model",
+      ":flags",
+      ":gpu_tool_utils",
+      ":xml",
+      "modules/skparagraph:samples",
+      "modules/skshaper",
+    ]
+
+    if (skia_use_lua) {
+      sources += [ "samplecode/SampleLua.cpp" ]
+      deps += [
+        ":lua",
+        "//third_party/lua",
+      ]
+    }
+  }
+  if (target_cpu != "wasm") {
     test_app("imgcvt") {
       sources = [
         "tools/imgcvt.cpp",
@@ -2595,6 +2600,14 @@
       ":skia",
     ]
   }
+
+  group("modules_testonly") {
+    testonly = true
+    deps = []
+    if (target_cpu == "wasm") {
+      deps += [ "modules/canvaskit:viewer_wasm" ]
+    }
+  }
 }
 
 if (is_ios && skia_use_metal && !skia_enable_flutter_defines) {
diff --git a/gn/samples.gni b/gn/samples.gni
index 8b1ace3..33a9de2 100644
--- a/gn/samples.gni
+++ b/gn/samples.gni
@@ -24,7 +24,6 @@
   "$_samplecode/SampleAtlas.cpp",
   "$_samplecode/SampleBackdropBounds.cpp",
   "$_samplecode/SampleBitmapRect.cpp",
-  "$_samplecode/SampleCCPRGeometry.cpp",
   "$_samplecode/SampleCamera.cpp",
   "$_samplecode/SampleChart.cpp",
   "$_samplecode/SampleChineseFling.cpp",
@@ -97,3 +96,5 @@
   "$_samplecode/SampleXfermodesBlur.cpp",
   "$_samplecode/vertexdump.cpp",
 ]
+
+samples_sources_ccpr = [ "$_samplecode/SampleCCPRGeometry.cpp" ]
diff --git a/modules/canvaskit/BUILD.gn b/modules/canvaskit/BUILD.gn
new file mode 100644
index 0000000..1d2539d
--- /dev/null
+++ b/modules/canvaskit/BUILD.gn
@@ -0,0 +1,15 @@
+# Copyright 2020 Google Inc.
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+component("viewer_wasm") {
+  testonly = true
+  include_dirs = [ "../.." ]
+  sources = [
+    "../../tools/viewer/SampleSlide.cpp",
+  ]
+  deps = [
+    "../..:samples",
+  ]
+}
diff --git a/modules/canvaskit/Makefile b/modules/canvaskit/Makefile
index 8ceb100..7fde457 100644
--- a/modules/canvaskit/Makefile
+++ b/modules/canvaskit/Makefile
@@ -17,6 +17,13 @@
 	cp ../../out/canvaskit_wasm/canvaskit.js   ./canvaskit/bin
 	cp ../../out/canvaskit_wasm/canvaskit.wasm ./canvaskit/bin
 
+release_viewer:
+	# Does an incremental build where possible.
+	./compile.sh viewer
+	mkdir -p ./canvaskit/bin
+	cp ../../out/canvaskit_wasm/canvaskit.js   ./canvaskit/bin
+	cp ../../out/canvaskit_wasm/canvaskit.wasm ./canvaskit/bin
+
 debug:
 	# Does an incremental build where possible.
 	./compile.sh debug
@@ -33,6 +40,14 @@
 	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm ./canvaskit/bin
 	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm.map ./canvaskit/bin
 
+debug_viewer:
+	# Does an incremental build where possible.
+	./compile.sh debug viewer
+	mkdir -p ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_debug/canvaskit.js   ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm.map ./canvaskit/bin
+
 profile:
 	./compile.sh profiling
 	mkdir -p ./canvaskit/bin
diff --git a/modules/canvaskit/canvaskit/viewer.html b/modules/canvaskit/canvaskit/viewer.html
new file mode 100644
index 0000000..08da2b0
--- /dev/null
+++ b/modules/canvaskit/canvaskit/viewer.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<title>CanvasKit Viewer (Skia via Web Assembly)</title>
+<meta charset="utf-8" />
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<style>
+  html, body {
+    margin: 0;
+    padding: 0;
+  }
+</style>
+
+<canvas id=viewer_canvas></canvas>
+
+<script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script>
+
+<script type="text/javascript" charset="utf-8">
+  var CanvasKit = null;
+  CanvasKitInit({
+    locateFile: (file) => '/node_modules/canvaskit/bin/'+file,
+  }).ready().then((CK) => {
+    CanvasKit = CK;
+    ViewerMain(CanvasKit);
+  });
+
+  function ViewerMain(CanvasKit) {
+    if (!CanvasKit) {
+      console.error('CanvasKit not available.');
+      return;
+    }
+    if (!CanvasKit.MakeSlide) {
+      console.error('Not compiled with Viewer.');
+      return;
+    }
+
+    const htmlCanvas = document.getElementById('viewer_canvas');
+    htmlCanvas.width = window.innerWidth;
+    htmlCanvas.height = window.innerHeight;
+
+    const surface = CanvasKit.MakeCanvasSurface(htmlCanvas);
+    if (!surface) {
+      console.error('Could not make surface');
+      return;
+    }
+
+    const slideName = 'WavyPathText';
+    const slide = CanvasKit.MakeSlide(slideName);
+    if (!slide) {
+      console.error('Could not make slide ' + slideName);
+      return;
+    }
+
+    slide.load(htmlCanvas.width, htmlCanvas.height);
+    const now0 = Date.now();
+
+    surface.requestAnimationFrame(function(canvas) {
+      const nanos = (Date.now() - now0) * 1e6;
+      if (slide.animate(nanos)) {
+        surface.requestAnimationFrame(arguments.callee);
+      }
+      slide.draw(canvas);
+    });
+  }
+</script>
diff --git a/modules/canvaskit/compile.sh b/modules/canvaskit/compile.sh
index 5422295..ba15563 100755
--- a/modules/canvaskit/compile.sh
+++ b/modules/canvaskit/compile.sh
@@ -28,6 +28,7 @@
 RELEASE_CONF="-Oz --closure 1 --llvm-lto 1 -DSK_RELEASE --pre-js $BASE_DIR/release.js \
               -DGR_GL_CHECK_ALLOC_WITH_GET_ERROR=0"
 EXTRA_CFLAGS="\"-DSK_RELEASE\", \"-DGR_GL_CHECK_ALLOC_WITH_GET_ERROR=0\","
+IS_OFFICIAL_BUILD="true"
 
 # Tracing will be disabled in release/profiling unless this flag is seen. Tracing will
 # be on debug builds always.
@@ -89,6 +90,16 @@
   SKOTTIE_BINDINGS=""
 fi
 
+VIEWER_BINDINGS=""
+VIEWER_LIB=""
+
+if [[ $@ == *viewer* ]]; then
+  echo "Including viewer"
+  VIEWER_BINDINGS="$BASE_DIR/viewer_bindings.cpp"
+  VIEWER_LIB="$BUILD_DIR/libviewer_wasm.a"
+  IS_OFFICIAL_BUILD="false"
+fi
+
 MANAGED_SKOTTIE_BINDINGS="\
   -DSK_INCLUDE_MANAGED_SKOTTIE=1 \
   modules/skottie/utils/SkottieUtils.cpp"
@@ -171,12 +182,10 @@
 GN_SHAPER="skia_use_icu=true skia_use_system_icu=false skia_use_harfbuzz=true skia_use_system_harfbuzz=false"
 SHAPER_LIB="$BUILD_DIR/libharfbuzz.a \
             $BUILD_DIR/libicu.a"
-SHAPER_TARGETS="libharfbuzz.a libicu.a"
 if [[ $@ == *primitive_shaper* ]] || [[ $@ == *no_font* ]]; then
   echo "Using the primitive shaper instead of the harfbuzz/icu one"
   GN_SHAPER="skia_use_icu=false skia_use_harfbuzz=false"
   SHAPER_LIB=""
-  SHAPER_TARGETS=""
 fi
 
 PARAGRAPH_JS="--pre-js $BASE_DIR/paragraph.js"
@@ -246,7 +255,7 @@
     ${EXTRA_CFLAGS}
   ] \
   is_debug=false \
-  is_official_build=true \
+  is_official_build=${IS_OFFICIAL_BUILD} \
   is_component_build=false \
   werror=true \
   target_cpu=\"wasm\" \
@@ -286,9 +295,14 @@
   skia_enable_skparagraph=true \
   skia_enable_pdf=false"
 
-# Build all the libs, we'll link the appropriate ones down below
-${NINJA} -C ${BUILD_DIR} libskia.a libskottie.a libsksg.a \
-    libskparagraph.a libskshaper.a libparticles.a $SHAPER_TARGETS
+# Build all the libs we will need below
+parse_targets() {
+  for LIBPATH in $@; do
+    basename $LIBPATH
+  done
+}
+${NINJA} -C ${BUILD_DIR} libskia.a libskshaper.a \
+  $(parse_targets $SKOTTIE_LIB $VIEWER_LIB $PARTICLES_LIB $SHAPER_LIB $PARAGRAPH_LIB)
 
 export EMCC_CLOSURE_ARGS="--externs $BASE_DIR/externs.js "
 
@@ -328,9 +342,11 @@
     $BASE_DIR/canvaskit_bindings.cpp \
     $PARTICLES_BINDINGS \
     $SKOTTIE_BINDINGS \
+    $VIEWER_BINDINGS \
     $MANAGED_SKOTTIE_BINDINGS \
     $PARAGRAPH_BINDINGS \
     $SKOTTIE_LIB \
+    $VIEWER_LIB \
     $PARTICLES_LIB \
     $PARAGRAPH_LIB \
     $BUILD_DIR/libskshaper.a \
diff --git a/modules/canvaskit/viewer_bindings.cpp b/modules/canvaskit/viewer_bindings.cpp
new file mode 100644
index 0000000..9282d09
--- /dev/null
+++ b/modules/canvaskit/viewer_bindings.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <emscripten.h>
+#include <emscripten/bind.h>
+#include "tools/viewer/SampleSlide.h"
+#include <string>
+
+using namespace emscripten;
+
+EMSCRIPTEN_BINDINGS(Viewer) {
+    function("MakeSlide", optional_override([](std::string name)->sk_sp<Slide> {
+        if (name == "WavyPathText") {
+            extern Sample* MakeWavyPathTextSample();
+            return sk_make_sp<SampleSlide>(MakeWavyPathTextSample);
+        }
+        return nullptr;
+    }));
+    class_<Slide>("Slide")
+        .smart_ptr<sk_sp<Slide>>("sk_sp<Slide>")
+        .function("load", &Slide::load)
+        .function("animate", &Slide::animate)
+        .function("draw", optional_override([](Slide& self, SkCanvas& canvas) {
+            self.draw(&canvas);
+        }));
+}
diff --git a/samplecode/SamplePathText.cpp b/samplecode/SamplePathText.cpp
index c0d4d17..1f42b36 100644
--- a/samplecode/SamplePathText.cpp
+++ b/samplecode/SamplePathText.cpp
@@ -424,6 +424,8 @@
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 
-DEF_SAMPLE( return new WavyPathText; )
-DEF_SAMPLE( return new MovingPathText; )
 DEF_SAMPLE( return new PathText; )
+DEF_SAMPLE( return new MovingPathText; )
+
+Sample* MakeWavyPathTextSample() { return new WavyPathText; }
+static SampleRegistry WavyPathText(MakeWavyPathTextSample);