changeset 1591:5887a4f8594b

moving platform-specific files out of the "OrthancStone" folder
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 23 Oct 2020 13:15:03 +0200
parents 7b963bccafef
children 0d4b11ba86df
files Applications/Platforms/Sdl/OrthancStoneSdlConfiguration.cmake Applications/Platforms/Sdl/OrthancStoneSdlParameters.cmake Applications/Platforms/Sdl/SdlConfiguration.cmake Applications/Platforms/Sdl/SdlOpenGLContext.cpp Applications/Platforms/Sdl/SdlOpenGLContext.h Applications/Platforms/Sdl/SdlViewport.cpp Applications/Platforms/Sdl/SdlViewport.h Applications/Platforms/Sdl/SdlWindow.cpp Applications/Platforms/Sdl/SdlWindow.h Applications/Platforms/WebAssembly/OrthancStoneWebAssemblyConfiguration.cmake Applications/Platforms/WebAssembly/OrthancStoneWebAssemblyParameters.cmake Applications/Platforms/WebAssembly/SharedLibrary/CMakeLists.txt Applications/Platforms/WebAssembly/SharedLibrary/NOTES.txt Applications/Platforms/WebAssembly/WebAssemblyCairoViewport.cpp Applications/Platforms/WebAssembly/WebAssemblyCairoViewport.h Applications/Platforms/WebAssembly/WebAssemblyLoadersContext.cpp Applications/Platforms/WebAssembly/WebAssemblyLoadersContext.h Applications/Platforms/WebAssembly/WebAssemblyOpenGLContext.cpp Applications/Platforms/WebAssembly/WebAssemblyOpenGLContext.h Applications/Platforms/WebAssembly/WebAssemblyOracle.cpp Applications/Platforms/WebAssembly/WebAssemblyOracle.h Applications/Platforms/WebAssembly/WebAssemblyOracle_Includes.h Applications/Platforms/WebAssembly/WebAssemblyViewport.cpp Applications/Platforms/WebAssembly/WebAssemblyViewport.h Applications/Platforms/WebAssembly/WebGLViewport.cpp Applications/Platforms/WebAssembly/WebGLViewport.h Applications/Platforms/WebAssembly/WebGLViewportsRegistry.cpp Applications/Platforms/WebAssembly/WebGLViewportsRegistry.h Applications/Resources/Graveyard/LoaderCache.cpp Applications/Resources/Graveyard/LoaderCache.h Applications/Samples/Sdl/CMakeLists.txt Applications/Samples/Sdl/RtViewer/RtViewerSdl.cpp Applications/Samples/Sdl/SdlHelpers.h Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp Applications/Samples/WebAssembly/CMakeLists.txt Applications/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp Applications/StoneWebViewer/WebAssembly/CMakeLists.txt Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake OrthancStone/Resources/CMake/OrthancStoneParameters.cmake OrthancStone/Resources/CMake/SdlConfiguration.cmake OrthancStone/SharedLibrary/WebAssembly/CMakeLists.txt OrthancStone/SharedLibrary/WebAssembly/NOTES.txt OrthancStone/Sources/Loaders/LoaderCache.cpp OrthancStone/Sources/Loaders/LoaderCache.h OrthancStone/Sources/Loaders/WebAssemblyLoadersContext.cpp OrthancStone/Sources/Loaders/WebAssemblyLoadersContext.h OrthancStone/Sources/OpenGL/OpenGLIncludes.h OrthancStone/Sources/OpenGL/SdlOpenGLContext.cpp OrthancStone/Sources/OpenGL/SdlOpenGLContext.h OrthancStone/Sources/OpenGL/WebAssemblyOpenGLContext.cpp OrthancStone/Sources/OpenGL/WebAssemblyOpenGLContext.h OrthancStone/Sources/Oracle/WebAssemblyOracle.cpp OrthancStone/Sources/Oracle/WebAssemblyOracle.h OrthancStone/Sources/Oracle/WebAssemblyOracle_Includes.h OrthancStone/Sources/StoneInitialization.cpp OrthancStone/Sources/StoneInitialization.h OrthancStone/Sources/Toolbox/GenericToolbox.cpp OrthancStone/Sources/Toolbox/GenericToolbox.h OrthancStone/Sources/Viewport/SdlViewport.cpp OrthancStone/Sources/Viewport/SdlViewport.h OrthancStone/Sources/Viewport/SdlWindow.cpp OrthancStone/Sources/Viewport/SdlWindow.h OrthancStone/Sources/Viewport/WebAssemblyCairoViewport.cpp OrthancStone/Sources/Viewport/WebAssemblyCairoViewport.h OrthancStone/Sources/Viewport/WebAssemblyViewport.cpp OrthancStone/Sources/Viewport/WebAssemblyViewport.h OrthancStone/Sources/Viewport/WebGLViewport.cpp OrthancStone/Sources/Viewport/WebGLViewport.h OrthancStone/Sources/Viewport/WebGLViewportsRegistry.cpp OrthancStone/Sources/Viewport/WebGLViewportsRegistry.h UnitTestsSources/UnitTestsMain.cpp
diffstat 73 files changed, 4680 insertions(+), 4551 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/Sdl/OrthancStoneSdlConfiguration.cmake	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,74 @@
+# Stone of Orthanc
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+
+#####################################################################
+## Sanity check of the configuration
+#####################################################################
+
+include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
+
+if (ORTHANC_SANDBOXED)
+  message(FATAL_ERROR "Cannot enable SDL in sandboxed environments")
+endif()
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten" OR
+    CMAKE_SYSTEM_NAME STREQUAL "PNaCl" OR
+    CMAKE_SYSTEM_NAME STREQUAL "NaCl32" OR
+    CMAKE_SYSTEM_NAME STREQUAL "NaCl64")
+  message(FATAL_ERROR "Trying to use a Web compiler for a native build")
+endif()
+
+
+
+#####################################################################
+## Configure SDL
+#####################################################################
+
+message("SDL is enabled")
+include(${CMAKE_CURRENT_LIST_DIR}/SdlConfiguration.cmake)
+
+add_definitions(
+  -DORTHANC_ENABLE_SDL=1
+  -DORTHANC_ENABLE_WASM=0
+  )
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows" AND
+    NOT MSVC)
+  # This is necessary when compiling EXE for Windows using MinGW
+  link_libraries(mingw32)
+endif()
+
+
+
+#####################################################################
+## Additional source files for SDL
+#####################################################################
+
+list(APPEND ORTHANC_STONE_SOURCES
+  ${CMAKE_CURRENT_LIST_DIR}/SdlWindow.cpp
+  ${SDL_SOURCES}
+  )
+
+if (ENABLE_OPENGL)
+  list(APPEND ORTHANC_STONE_SOURCES
+    ${CMAKE_CURRENT_LIST_DIR}/SdlOpenGLContext.cpp
+    ${CMAKE_CURRENT_LIST_DIR}/SdlViewport.cpp
+    )
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/Sdl/OrthancStoneSdlParameters.cmake	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,41 @@
+# Stone of Orthanc
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+
+#####################################################################
+## Load generic parameters
+#####################################################################
+
+include(${CMAKE_CURRENT_LIST_DIR}/../../../OrthancStone/Resources/CMake/OrthancStoneParameters.cmake)
+
+
+#####################################################################
+## CMake parameters tunable by the user
+#####################################################################
+
+# Advanced parameters to fine-tune linking against system libraries
+set(USE_SYSTEM_SDL ON CACHE BOOL "Use the system version of SDL2")
+
+
+#####################################################################
+## Internal CMake parameters to enable the optional subcomponents of
+## the Stone of Orthanc
+#####################################################################
+
+set(ENABLE_THREADS ON CACHE INTERNAL "")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/Sdl/SdlConfiguration.cmake	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,224 @@
+# Stone of Orthanc
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_SDL)
+  SET(SDL_SOURCES_DIR ${CMAKE_BINARY_DIR}/SDL2-2.0.4)
+  SET(SDL_URL "http://orthanc.osimis.io/ThirdPartyDownloads/SDL2-2.0.4.tar.gz")
+  SET(SDL_MD5 "44fc4a023349933e7f5d7a582f7b886e")
+  DownloadPackage(${SDL_MD5} ${SDL_URL} "${SDL_SOURCES_DIR}")
+
+  include_directories(${SDL_SOURCES_DIR}/include)
+
+  set(TMP "${SDL_SOURCES_DIR}/include/SDL_config_premake.h")
+  if (NOT EXISTS "${TMP}")
+    file(WRITE "${TMP}" "
+#include \"SDL_platform.h\"
+#define HAVE_STDARG_H 1
+#define HAVE_STDDEF_H 1
+#define HAVE_STDINT_H 1
+")
+  endif()
+
+  # General source files
+  file(GLOB SDL_SOURCES
+    ${SDL_SOURCES_DIR}/src/*.c
+    ${SDL_SOURCES_DIR}/src/atomic/*.c
+    ${SDL_SOURCES_DIR}/src/audio/*.c
+    ${SDL_SOURCES_DIR}/src/cpuinfo/*.c
+    ${SDL_SOURCES_DIR}/src/dynapi/*.c
+    ${SDL_SOURCES_DIR}/src/events/*.c
+    ${SDL_SOURCES_DIR}/src/file/*.c
+    ${SDL_SOURCES_DIR}/src/haptic/*.c
+    ${SDL_SOURCES_DIR}/src/joystick/*.c
+    ${SDL_SOURCES_DIR}/src/libm/*.c
+    ${SDL_SOURCES_DIR}/src/power/*.c
+    ${SDL_SOURCES_DIR}/src/render/*.c
+    ${SDL_SOURCES_DIR}/src/stdlib/*.c
+    ${SDL_SOURCES_DIR}/src/thread/*.c
+    ${SDL_SOURCES_DIR}/src/timer/*.c
+    ${SDL_SOURCES_DIR}/src/video/*.c
+
+    ${SDL_SOURCES_DIR}/src/loadso/dummy/*.c
+    #${SDL_SOURCES_DIR}/src/timer/dummy/*.c
+    ${SDL_SOURCES_DIR}/src/audio/dummy/*.c
+    ${SDL_SOURCES_DIR}/src/filesystem/dummy/*.c
+    ${SDL_SOURCES_DIR}/src/haptic/dummy/*.c
+    ${SDL_SOURCES_DIR}/src/joystick/dummy/*.c
+    #${SDL_SOURCES_DIR}/src/main/dummy/*.c
+    ${SDL_SOURCES_DIR}/src/video/dummy/*.c
+    )
+
+  add_definitions(
+    -DUSING_PREMAKE_CONFIG_H=1
+
+    -DSDL_AUDIO_DISABLED=1
+    -DSDL_AUDIO_DRIVER_DUMMY=1
+    -DSDL_FILESYSTEM_DISABLED=1
+    -DSDL_FILESYSTEM_DUMMY=1
+    -DSDL_FILE_DISABLED=1
+    -DSDL_HAPTIC_DISABLED=1
+    -DSDL_JOYSTICK_DISABLED=1
+
+    #-DSDL_THREADS_DISABLED=1
+    )
+
+  if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
+    file(GLOB TMP
+      ${SDL_SOURCES_DIR}/src/core/linux/*.c
+      ${SDL_SOURCES_DIR}/src/loadso/dlopen/*.c
+      ${SDL_SOURCES_DIR}/src/render/software/*.c
+      ${SDL_SOURCES_DIR}/src/thread/pthread/*.c
+      ${SDL_SOURCES_DIR}/src/timer/unix/*.c
+      ${SDL_SOURCES_DIR}/src/video/x11/*.c
+      )
+
+    list(APPEND SDL_SOURCES ${TMP})
+
+    add_definitions(
+      -DSDL_LOADSO_DLOPEN=1
+      -DSDL_THREAD_PTHREAD=1
+      -DSDL_TIMER_UNIX=1
+      -DSDL_POWER_DISABLED=1
+
+      -DSDL_VIDEO_DRIVER_X11=1
+
+      -DSDL_ASSEMBLY_ROUTINES=1
+      -DSDL_THREAD_PTHREAD_RECURSIVE_MUTEX=1
+      -DSDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS=1
+      -DHAVE_GCC_SYNC_LOCK_TEST_AND_SET=1
+      )
+
+    link_libraries(X11 Xext)
+
+    if (NOT CMAKE_SYSTEM_VERSION STREQUAL "Raspberry")
+      # Raspberry Pi has no support for OpenGL
+      file(GLOB TMP
+        ${SDL_SOURCES_DIR}/src/render/opengl/*.c
+        ${SDL_SOURCES_DIR}/src/render/opengles2/*.c
+        )
+
+      list(APPEND SDL_SOURCES ${TMP})
+
+      add_definitions(
+        -DSDL_VIDEO_OPENGL=1
+        -DSDL_VIDEO_OPENGL_ES2=1
+        -DSDL_VIDEO_RENDER_OGL=1
+        -DSDL_VIDEO_RENDER_OGL_ES2=1
+        -DSDL_VIDEO_OPENGL_GLX=1
+        -DSDL_VIDEO_OPENGL_EGL=1
+        )
+    endif()
+
+  elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows")
+    file(GLOB TMP
+      ${SDL_SOURCES_DIR}/src/audio/directsound/*.c
+      ${SDL_SOURCES_DIR}/src/audio/disk/*.c
+      ${SDL_SOURCES_DIR}/src/audio/winmm/*.c
+      ${SDL_SOURCES_DIR}/src/joystick/windows/*.c
+      ${SDL_SOURCES_DIR}/src/haptic/windows/*.c
+      ${SDL_SOURCES_DIR}/src/power/windows/*.c
+
+      ${SDL_SOURCES_DIR}/src/main/windows/*.c
+      ${SDL_SOURCES_DIR}/src/core/windows/*.c
+      ${SDL_SOURCES_DIR}/src/loadso/windows/*.c
+      ${SDL_SOURCES_DIR}/src/render/direct3d/*.c
+      ${SDL_SOURCES_DIR}/src/render/direct3d11/*.c
+      ${SDL_SOURCES_DIR}/src/render/opengl/*.c
+      ${SDL_SOURCES_DIR}/src/render/psp/*.c
+      ${SDL_SOURCES_DIR}/src/render/opengles/*.c
+      ${SDL_SOURCES_DIR}/src/render/opengles2/*.c
+      ${SDL_SOURCES_DIR}/src/render/software/*.c
+      ${SDL_SOURCES_DIR}/src/thread/generic/SDL_syscond.c   # Don't include more files from "thread/generic/*.c"!
+      ${SDL_SOURCES_DIR}/src/thread/windows/*.c
+      ${SDL_SOURCES_DIR}/src/timer/windows/*.c
+      ${SDL_SOURCES_DIR}/src/video/windows/*.c
+      ${SDL_SOURCES_DIR}/src/windows/dlopen/*.c
+      )
+
+    list(APPEND SDL_SOURCES ${TMP})
+
+    # NB: OpenGL ES headers are not available in MinGW-W64
+    add_definitions(
+      -DSDL_LOADSO_WINDOWS=1
+      -DSDL_THREAD_WINDOWS=1
+      -DSDL_TIMER_WINDOWS=1
+      -DSDL_POWER_WINDOWS=1
+
+      -DSDL_VIDEO_OPENGL=1
+      -DSDL_VIDEO_OPENGL_WGL=1
+      -DSDL_VIDEO_RENDER_D3D=1
+      -DSDL_VIDEO_RENDER_OGL=1
+      -DSDL_VIDEO_DRIVER_WINDOWS=1
+      )
+
+    if (MSVC)
+      add_definitions(
+        -D__FLTUSED__
+        -DHAVE_LIBC=1
+      )
+    else()
+      add_definitions(
+        -DHAVE_GCC_ATOMICS=1
+        -DSDL_ASSEMBLY_ROUTINES=1
+        )
+    endif()
+    
+    link_libraries(imm32 winmm version)
+
+  elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+    file(GLOB TMP
+      ${SDL_SOURCES_DIR}/src/loadso/dlopen/*.c
+      ${SDL_SOURCES_DIR}/src/render/opengl/*.c
+      ${SDL_SOURCES_DIR}/src/render/opengles2/*.c
+      ${SDL_SOURCES_DIR}/src/render/software/*.c
+      ${SDL_SOURCES_DIR}/src/thread/pthread/*.c
+      ${SDL_SOURCES_DIR}/src/timer/unix/*.c
+      ${SDL_SOURCES_DIR}/src/video/cocoa/*.m
+      )
+
+    list(APPEND SDL_SOURCES ${TMP})
+
+    add_definitions(
+      -DSDL_LOADSO_DLOPEN=1
+      -DSDL_THREAD_PTHREAD=1
+      -DSDL_TIMER_UNIX=1
+      -DSDL_POWER_DISABLED=1
+
+      -DSDL_VIDEO_DRIVER_COCOA=1
+      -DSDL_VIDEO_OPENGL=1
+      -DSDL_VIDEO_OPENGL_CGL=1
+      -DSDL_VIDEO_RENDER_OGL=1
+      
+      -DSDL_ASSEMBLY_ROUTINES=1
+      -DSDL_THREAD_PTHREAD_RECURSIVE_MUTEX=1
+      )
+
+    find_library(CARBON_LIBRARY Carbon)
+    find_library(COCOA_LIBRARY Cocoa)
+    find_library(IOKIT_LIBRARY IOKit)
+    find_library(QUARTZ_LIBRARY QuartzCore)
+    link_libraries(${CARBON_LIBRARY} ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${QUARTZ_LIBRARY})
+
+  endif()
+
+else()
+  pkg_search_module(SDL2 REQUIRED sdl2)
+  include_directories(${SDL2_INCLUDE_DIRS})
+  link_libraries(${SDL2_LIBRARIES})
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/Sdl/SdlOpenGLContext.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,127 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SdlOpenGLContext.h"
+#include "../../../OrthancStone/Sources/StoneException.h"
+
+#if ORTHANC_ENABLE_SDL == 1
+
+#if !defined(ORTHANC_ENABLE_GLEW)
+#  error Macro ORTHANC_ENABLE_GLEW must be defined
+#endif
+
+#if ORTHANC_ENABLE_GLEW == 1
+#  include <GL/glew.h>
+#endif
+
+#include <OrthancException.h>
+
+namespace OrthancStone
+{
+  SdlOpenGLContext::SdlOpenGLContext(const char* title,
+                                     unsigned int width,
+                                     unsigned int height,
+                                     bool allowDpiScaling) 
+    : window_(title, width, height, true /* enable OpenGL */, allowDpiScaling)
+    , context_(NULL)
+  {
+    context_ = SDL_GL_CreateContext(window_.GetObject());
+    
+    if (context_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                      "Cannot initialize OpenGL");
+    }
+
+#if ORTHANC_ENABLE_GLEW == 1
+    // The initialization function of glew (i.e. "glewInit()") can
+    // only be called once an OpenGL is setup.
+    // https://stackoverflow.com/a/45033669/881731
+    {
+      static boost::mutex  mutex_;
+      static bool          isGlewInitialized_ = false;
+  
+      boost::mutex::scoped_lock lock(mutex_);
+
+      if (!isGlewInitialized_)
+      {
+        LOG(INFO) << "Initializing glew";
+        
+        GLenum err = glewInit();
+        if (GLEW_OK != err)
+        {
+          LOG(ERROR) << glewGetErrorString(err);
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                          "Cannot initialize glew");
+        }
+
+        isGlewInitialized_ = true;
+      }
+    }    
+#endif
+  }
+
+  
+  SdlOpenGLContext::~SdlOpenGLContext()
+  {
+    SDL_GL_DeleteContext(context_);
+  }
+
+  void SdlOpenGLContext::MakeCurrent()
+  {
+    if (SDL_GL_MakeCurrent(window_.GetObject(), context_) != 0)
+    {
+      const char* errText = SDL_GetError();
+      std::stringstream ss;
+      ss << "Cannot set current OpenGL context. SDL error text: " << errText;
+      std::string errStr = ss.str();
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, errStr.c_str());
+    }
+
+    // This makes our buffer swap synchronized with the monitor's vertical refresh
+    SDL_GL_SetSwapInterval(1);
+  }
+
+  
+  void SdlOpenGLContext::SwapBuffer()
+  {
+    // Swap our buffer to display the current contents of buffer on screen
+    SDL_GL_SwapWindow(window_.GetObject());
+  }
+
+  
+  unsigned int SdlOpenGLContext::GetCanvasWidth() const
+  {
+    int w = 0;
+    SDL_GL_GetDrawableSize(window_.GetObject(), &w, NULL);
+    return static_cast<unsigned int>(w);
+  }
+
+  
+  unsigned int SdlOpenGLContext::GetCanvasHeight() const
+  {
+    int h = 0;
+    SDL_GL_GetDrawableSize(window_.GetObject(), NULL, &h);
+    return static_cast<unsigned int>(h);
+  }
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/Sdl/SdlOpenGLContext.h	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,77 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_SDL == 1
+
+#include "../../../OrthancStone/Sources/OpenGL/IOpenGLContext.h"
+#include "SdlWindow.h"
+
+#include <Compatibility.h>  // For ORTHANC_OVERRIDE
+
+#include <SDL_render.h>
+
+#include <Enumerations.h>
+
+namespace OrthancStone
+{
+  class SdlOpenGLContext : public OpenGL::IOpenGLContext
+  {
+  private:
+    SdlWindow      window_;
+    SDL_GLContext  context_;
+
+  public:
+    SdlOpenGLContext(const char* title,
+                     unsigned int width,
+                     unsigned int height,
+                     bool allowDpiScaling = true);
+
+    ~SdlOpenGLContext();
+
+    SdlWindow& GetWindow()
+    {
+      return window_;
+    }
+
+    virtual bool IsContextLost() ORTHANC_OVERRIDE
+    {
+      // On desktop applications, an OpenGL context should never be lost
+      return false;
+    }
+
+    virtual void MakeCurrent() ORTHANC_OVERRIDE;
+
+    virtual void SwapBuffer() ORTHANC_OVERRIDE;
+
+    unsigned int GetCanvasWidth() const;
+
+    unsigned int GetCanvasHeight() const;
+
+    void ToggleMaximize()
+    {
+      window_.ToggleMaximize();
+    }
+  };
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/Sdl/SdlViewport.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,236 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "SdlViewport.h"
+
+#include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h"
+
+#include <OrthancException.h>
+
+#include <boost/make_shared.hpp>
+
+namespace OrthancStone
+{
+  ICompositor& SdlViewport::SdlLock::GetCompositor()
+  {
+    if (that_.compositor_.get() == NULL)
+    {
+      // The derived class should have called "AcquireCompositor()"
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    else
+    {
+      return *that_.compositor_;
+    }
+  }
+
+
+  void SdlViewport::AcquireCompositor(ICompositor* compositor /* takes ownership */)
+  {
+    if (compositor == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    compositor_.reset(compositor);
+  }
+
+
+  SdlViewport::SdlViewport()
+  {
+    refreshEvent_ = SDL_RegisterEvents(1);
+
+    if (refreshEvent_ == static_cast<uint32_t>(-1))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+  
+  void SdlViewport::PostConstructor()
+  {
+    controller_ = boost::make_shared<ViewportController>(shared_from_this());
+  }
+    
+  void SdlViewport::SendRefreshEvent()
+  {
+    SDL_Event event;
+    SDL_memset(&event, 0, sizeof(event));
+    event.type = refreshEvent_;
+    SDL_PushEvent(&event);  // This function is thread-safe, and can be called from other threads safely.
+  }
+
+  
+  void SdlViewport::UpdateSize(unsigned int width, unsigned int height)
+  {
+    SdlLock lock(*this);
+    lock.GetCompositor().SetCanvasSize(width, height);
+    lock.Invalidate();
+  }
+
+
+  SdlOpenGLViewport::SdlOpenGLViewport(const std::string& title,
+                                       unsigned int width,
+                                       unsigned int height,
+                                       bool allowDpiScaling) :
+    context_(title.c_str(), width, height, allowDpiScaling)
+  {
+    AcquireCompositor(new OpenGLCompositor(context_));  // (*)
+  }
+
+
+  void SdlOpenGLViewport::RefreshCanvasSize()
+  {
+    UpdateSize(context_.GetCanvasWidth(), context_.GetCanvasHeight());
+  }
+
+
+  boost::shared_ptr<SdlOpenGLViewport> SdlOpenGLViewport::Create(
+    const std::string& title,
+    unsigned int width,
+    unsigned int height,
+    bool allowDpiScaling)
+  {
+    boost::shared_ptr<SdlOpenGLViewport> that =
+      boost::shared_ptr<SdlOpenGLViewport>(new SdlOpenGLViewport(title, width, height, allowDpiScaling));
+    that->SdlViewport::PostConstructor();
+    return that;
+  }
+
+  uint32_t SdlOpenGLViewport::GetSdlWindowId()
+  {
+    const SdlWindow& sdlWindowWrapper = context_.GetWindow();
+    SDL_Window* sdlWindow = sdlWindowWrapper.GetObject();
+    Uint32 sdlWindowId = SDL_GetWindowID(sdlWindow);
+    return sdlWindowId;
+  }
+
+  SdlOpenGLViewport::~SdlOpenGLViewport()
+  {
+    // Make sure that the "OpenGLCompositor" is destroyed BEFORE the
+    // "OpenGLContext" it references (*)
+    ClearCompositor();
+  }
+
+
+  void SdlOpenGLViewport::Paint()
+  {
+    SdlLock lock(*this);
+    lock.GetCompositor().Refresh(lock.GetController().GetScene());
+  }
+
+
+  void SdlOpenGLViewport::ToggleMaximize()
+  {
+    // No need to call "Invalidate()" here, as "UpdateSize()" will
+    // be invoked after event "SDL_WINDOWEVENT_SIZE_CHANGED"
+    SdlLock lock(*this);
+    context_.ToggleMaximize();
+  }
+
+
+
+  void SdlCairoViewport::RefreshCanvasSize()
+  {
+    UpdateSize(window_.GetWidth(), window_.GetHeight());
+  }
+
+  SdlCairoViewport::SdlCairoViewport(const char* title,
+                                     unsigned int width,
+                                     unsigned int height,
+                                     bool allowDpiScaling) :
+    window_(title, width, height, false /* enable OpenGL */, allowDpiScaling),
+    sdlSurface_(NULL)
+  {
+    AcquireCompositor(new CairoCompositor(width, height));
+  }
+
+  SdlCairoViewport::~SdlCairoViewport()
+  {
+    if (sdlSurface_)
+    {
+      SDL_FreeSurface(sdlSurface_);
+    }
+  }
+  
+  uint32_t SdlCairoViewport::GetSdlWindowId()
+  {
+    SDL_Window* sdlWindow = window_.GetObject();
+    Uint32 sdlWindowId = SDL_GetWindowID(sdlWindow);
+    return sdlWindowId;
+  }
+
+  void SdlCairoViewport::Paint()
+  {
+    SdlLock lock(*this);
+
+    lock.GetCompositor().Refresh(lock.GetController().GetScene());
+    CreateSdlSurfaceFromCompositor(dynamic_cast<CairoCompositor&>(lock.GetCompositor()));
+    
+    if (sdlSurface_ != NULL)
+    {
+      window_.Render(sdlSurface_);
+    }
+  }
+
+
+  void SdlCairoViewport::ToggleMaximize()
+  {
+    // No need to call "Invalidate()" here, as "UpdateSize()" will
+    // be invoked after event "SDL_WINDOWEVENT_SIZE_CHANGED"
+    SdlLock lock(*this);
+    window_.ToggleMaximize();
+  }
+
+  
+  // Assumes that the mutex is locked
+  void SdlCairoViewport::CreateSdlSurfaceFromCompositor(const CairoCompositor& compositor)
+  {
+    static const uint32_t rmask = 0x00ff0000;
+    static const uint32_t gmask = 0x0000ff00;
+    static const uint32_t bmask = 0x000000ff;
+
+    const unsigned int width = compositor.GetCanvas().GetWidth();
+    const unsigned int height = compositor.GetCanvas().GetHeight();
+
+    if (sdlSurface_ != NULL)
+    {
+      if (sdlSurface_->pixels == compositor.GetCanvas().GetBuffer() &&
+          sdlSurface_->w == static_cast<int>(width) &&
+          sdlSurface_->h == static_cast<int>(height) &&
+          sdlSurface_->pitch == static_cast<int>(compositor.GetCanvas().GetPitch()))
+      {
+        // The image from the compositor has not changed, no need to update the surface
+        return;
+      }
+      else
+      {
+        SDL_FreeSurface(sdlSurface_);
+      }
+    }
+
+    sdlSurface_ = SDL_CreateRGBSurfaceFrom((void*)(compositor.GetCanvas().GetBuffer()), width, height, 32,
+                                           compositor.GetCanvas().GetPitch(), rmask, gmask, bmask, 0);
+    if (!sdlSurface_)
+    {
+      LOG(ERROR) << "Cannot create a SDL surface from a Cairo surface";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/Sdl/SdlViewport.h	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,203 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#if !defined(ORTHANC_ENABLE_SDL)
+#  error Macro ORTHANC_ENABLE_SDL must be defined
+#endif
+
+#if ORTHANC_ENABLE_SDL != 1
+#  error SDL must be enabled to use this file
+#endif
+
+#if !defined(ORTHANC_ENABLE_OPENGL)
+#  error The macro ORTHANC_ENABLE_OPENGL must be defined
+#endif
+
+#if ORTHANC_ENABLE_OPENGL != 1
+#  error Support for OpenGL is disabled
+#endif
+
+#include "SdlOpenGLContext.h"
+#include "../../../OrthancStone/Sources/Scene2D/OpenGLCompositor.h"
+#include "../../../OrthancStone/Sources/Scene2D/CairoCompositor.h"
+#include "../../../OrthancStone/Sources/Viewport/IViewport.h"
+
+#include <SDL_events.h>
+
+// TODO: required for UndoStack injection
+// I don't like it either :)
+#include <boost/weak_ptr.hpp>
+
+#include <boost/thread/recursive_mutex.hpp>
+#include <boost/enable_shared_from_this.hpp>
+
+namespace OrthancStone
+{
+  class UndoStack;
+
+  class SdlViewport : public IViewport,
+                      public boost::enable_shared_from_this<SdlViewport>
+  {
+  private:
+    boost::recursive_mutex                 mutex_;
+    uint32_t                               refreshEvent_;
+    boost::shared_ptr<ViewportController>  controller_;
+    std::unique_ptr<ICompositor>           compositor_;
+
+    void SendRefreshEvent();
+
+  protected:
+    class SdlLock : public ILock
+    {
+    private:
+      SdlViewport&                        that_;
+      boost::recursive_mutex::scoped_lock lock_;
+
+    public:
+      explicit SdlLock(SdlViewport& that) :
+        that_(that),
+        lock_(that.mutex_)
+      {
+      }
+
+      virtual bool HasCompositor() const ORTHANC_OVERRIDE
+      {
+        return true;
+      }
+
+      virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE;
+      
+      virtual ViewportController& GetController() ORTHANC_OVERRIDE
+      {
+        return *that_.controller_;
+      }
+
+      virtual void Invalidate() ORTHANC_OVERRIDE
+      {
+        that_.SendRefreshEvent();
+      }
+      
+      virtual void RefreshCanvasSize() ORTHANC_OVERRIDE
+      {
+        that_.RefreshCanvasSize();
+      }
+    };
+
+    void ClearCompositor()
+    {
+      compositor_.reset();
+    }
+
+    void AcquireCompositor(ICompositor* compositor /* takes ownership */);
+
+    virtual void RefreshCanvasSize() = 0;
+    
+  protected:
+    SdlViewport();
+
+    void PostConstructor();
+
+  public:
+    bool IsRefreshEvent(const SDL_Event& event) const
+    {
+      return (event.type == refreshEvent_);
+    }
+
+    virtual ILock* Lock() ORTHANC_OVERRIDE
+    {
+      return new SdlLock(*this);
+    }
+
+    virtual uint32_t GetSdlWindowId() = 0;
+
+    void UpdateSize(unsigned int width,
+                    unsigned int height);
+
+    virtual void ToggleMaximize() = 0;
+
+    // Must be invoked from the main SDL thread
+    virtual void Paint() = 0;
+  };
+
+
+  class SdlOpenGLViewport : public SdlViewport
+  {
+  private:
+    SdlOpenGLContext  context_;
+
+    SdlOpenGLViewport(const std::string& title,
+                      unsigned int       width,
+                      unsigned int       height,
+                      bool               allowDpiScaling = true);
+
+  protected:
+    virtual void RefreshCanvasSize() ORTHANC_OVERRIDE;
+    
+  public:
+    static boost::shared_ptr<SdlOpenGLViewport> Create(const std::string&,
+                                                       unsigned int width,
+                                                       unsigned int height,
+                                                       bool allowDpiScaling = true);
+
+
+    virtual ~SdlOpenGLViewport();
+
+    virtual uint32_t GetSdlWindowId() ORTHANC_OVERRIDE;
+
+    virtual void Paint() ORTHANC_OVERRIDE;
+
+    virtual void ToggleMaximize() ORTHANC_OVERRIDE;
+  };
+
+
+  class SdlCairoViewport : public SdlViewport
+  {
+  private:
+    SdlWindow     window_;
+    SDL_Surface*  sdlSurface_;
+
+    void CreateSdlSurfaceFromCompositor(const CairoCompositor& compositor);
+
+    SdlCairoViewport(const char* title,
+                     unsigned int width,
+                     unsigned int height,
+                     bool allowDpiScaling = true);
+
+  protected:
+    virtual void RefreshCanvasSize() ORTHANC_OVERRIDE;
+    
+  public:
+    static boost::shared_ptr<SdlCairoViewport> Create(const char* title,
+                     unsigned int width,
+                     unsigned int height,
+                     bool allowDpiScaling = true);
+
+
+    virtual ~SdlCairoViewport();
+
+    virtual uint32_t GetSdlWindowId() ORTHANC_OVERRIDE;
+
+    virtual void Paint() ORTHANC_OVERRIDE;
+
+    virtual void ToggleMaximize() ORTHANC_OVERRIDE;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/Sdl/SdlWindow.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,210 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SdlWindow.h"
+
+#if ORTHANC_ENABLE_SDL == 1
+
+#include <Logging.h>
+#include <OrthancException.h>
+
+#ifdef WIN32 
+#include <windows.h> // for SetProcessDpiAware
+#endif 
+// WIN32
+
+#include <SDL_render.h>
+#include <SDL_video.h>
+#include <SDL.h>
+
+namespace OrthancStone
+{
+  SdlWindow::SdlWindow(const char* title,
+                       unsigned int width,
+                       unsigned int height,
+                       bool enableOpenGl,
+                       bool allowDpiScaling) :
+    maximized_(false)
+  {
+    // TODO Understand why, with SDL_WINDOW_OPENGL + MinGW32 + Release
+    // build mode, the application crashes whenever the SDL window is
+    // resized or maximized
+
+    uint32_t windowFlags, rendererFlags;
+    if (enableOpenGl)
+    {
+      windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
+      rendererFlags = SDL_RENDERER_ACCELERATED;
+    }
+    else
+    {
+      windowFlags = SDL_WINDOW_RESIZABLE;
+      rendererFlags = SDL_RENDERER_SOFTWARE;
+    }
+
+// TODO: probably required on MacOS X, too
+#if defined(WIN32) && (_WIN32_WINNT >= 0x0600)
+    if (!allowDpiScaling)
+    {
+      // if we do NOT allow DPI scaling, it means an SDL pixel will be a real
+      // monitor pixel. This is needed for high-DPI applications
+
+      // Enable high-DPI support on Windows
+
+      // THE FOLLOWING HAS BEEN COMMENTED OUT BECAUSE IT WILL CRASH UNDER 
+      // OLD WINDOWS VERSIONS
+      // ADD THIS AT THE TOP TO ENABLE IT:
+      // 
+      //#pragma comment(lib, "Shcore.lib") THIS IS ONLY REQUIRED FOR SetProcessDpiAwareness
+      //#include <windows.h>
+      //#include <ShellScalingAPI.h> THIS IS ONLY REQUIRED FOR SetProcessDpiAwareness
+      //#include <comdef.h> THIS IS ONLY REQUIRED FOR SetProcessDpiAwareness
+      // SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
+      
+      // This is supported on Vista+
+      SetProcessDPIAware();
+
+      windowFlags |= SDL_WINDOW_ALLOW_HIGHDPI;
+    }
+#endif 
+// WIN32
+    
+    window_ = SDL_CreateWindow(title,
+                               SDL_WINDOWPOS_UNDEFINED,
+                               SDL_WINDOWPOS_UNDEFINED,
+                               width, height, windowFlags);
+
+    if (window_ == NULL) 
+    {
+      LOG(ERROR) << "Cannot create the SDL window: " << SDL_GetError();
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    renderer_ = SDL_CreateRenderer(window_, -1, rendererFlags);
+    if (!renderer_)
+    {
+      LOG(ERROR) << "Cannot create the SDL renderer: " << SDL_GetError();
+      SDL_DestroyWindow(window_);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  SdlWindow::~SdlWindow()
+  {
+    if (renderer_ != NULL)
+    { 
+      SDL_DestroyRenderer(renderer_);
+    }
+
+    if (window_ != NULL)
+    { 
+      SDL_DestroyWindow(window_);
+    }
+  }
+
+
+  unsigned int SdlWindow::GetWidth() const
+  {
+    int w = -1;
+    SDL_GetWindowSize(window_, &w, NULL);
+
+    if (w < 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    else
+    {
+      return static_cast<unsigned int>(w);
+    }
+  }
+
+
+  unsigned int SdlWindow::GetHeight() const
+  {
+    int h = -1;
+    SDL_GetWindowSize(window_, NULL, &h);
+
+    if (h < 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    else
+    {
+      return static_cast<unsigned int>(h);
+    }
+  }
+
+
+  void SdlWindow::Render(SDL_Surface* surface)
+  {
+    /**
+     * "You are strongly encouraged to call SDL_RenderClear() to
+     * initialize the backbuffer before starting each new frame's
+     * drawing, even if you plan to overwrite every pixel."
+     * https://wiki.libsdl.org/SDL_RenderPresent
+     **/
+    SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
+    SDL_RenderClear(renderer_);  // Clear the entire screen to our selected color
+
+    SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer_, surface);
+    if (texture != NULL)
+    {
+      SDL_RenderCopy(renderer_, texture, NULL, NULL);
+      SDL_DestroyTexture(texture);
+    }
+
+    SDL_RenderPresent(renderer_);
+  }
+
+
+  void SdlWindow::ToggleMaximize()
+  {
+    if (maximized_)
+    {
+      SDL_RestoreWindow(window_);
+      maximized_ = false;
+    }
+    else
+    {
+      SDL_MaximizeWindow(window_);
+      maximized_ = true;
+    }
+  }
+
+
+  void SdlWindow::GlobalInitialize()
+  {
+    if (SDL_Init(SDL_INIT_VIDEO) != 0)
+    {
+      LOG(ERROR) << "Cannot initialize SDL";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  void SdlWindow::GlobalFinalize()
+  {
+    SDL_Quit();
+  }
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/Sdl/SdlWindow.h	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,76 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_SDL == 1
+
+#include <boost/noncopyable.hpp>
+
+// Forward declaration of SDL type to avoid clashes with DCMTK headers
+// on "typedef Sint8", in "StoneInitialization.cpp"
+struct SDL_Window;
+struct SDL_Renderer;
+struct SDL_Surface;
+
+namespace OrthancStone
+{
+  class SdlWindow : public boost::noncopyable
+  {
+  private:
+    struct SDL_Window   *window_;
+    struct SDL_Renderer *renderer_;
+    bool                 maximized_;
+
+  public:
+    SdlWindow(const char* title,
+              unsigned int width,
+              unsigned int height,
+              bool enableOpenGl,
+              bool allowDpiScaling = true);
+
+    ~SdlWindow();
+
+    SDL_Window *GetObject() const
+    {
+      return window_;
+    }
+
+    unsigned int GetWidth() const;
+
+    unsigned int GetHeight() const;
+
+    /**
+     * WARNING: "Refresh()" cannot only be called from the main SDL
+     * thread, in which the window was created. Otherwise, the
+     * renderer displays nothing!
+     **/
+    void Render(struct SDL_Surface* surface);
+
+    void ToggleMaximize();
+
+    static void GlobalInitialize();
+
+    static void GlobalFinalize();
+  };
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/OrthancStoneWebAssemblyConfiguration.cmake	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,59 @@
+# Stone of Orthanc
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+
+#####################################################################
+## Sanity check of the configuration
+#####################################################################
+
+set(ENABLE_WEB_CLIENT OFF)
+include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
+
+if (NOT ORTHANC_SANDBOXED)
+  message(FATAL_ERROR "WebAssembly target must me configured as sandboxed")
+endif()
+
+if (NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
+  message(FATAL_ERROR "WebAssembly target requires the emscripten compiler")    
+endif()
+
+add_definitions(
+  -DORTHANC_ENABLE_SDL=0
+  -DORTHANC_ENABLE_WASM=1
+  )
+
+
+#####################################################################
+## Additional source files for WebAssembly
+#####################################################################
+
+list(APPEND ORTHANC_STONE_SOURCES
+  ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyCairoViewport.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyLoadersContext.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyOracle.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyViewport.cpp
+  )
+
+if (ENABLE_OPENGL)
+  list(APPEND ORTHANC_STONE_SOURCES
+    ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyOpenGLContext.cpp
+    ${CMAKE_CURRENT_LIST_DIR}/WebGLViewport.cpp
+    ${CMAKE_CURRENT_LIST_DIR}/WebGLViewportsRegistry.cpp
+    )
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/OrthancStoneWebAssemblyParameters.cmake	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,40 @@
+# Stone of Orthanc
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+
+#####################################################################
+## Load generic parameters
+#####################################################################
+
+include(${CMAKE_CURRENT_LIST_DIR}/../../../OrthancStone/Resources/CMake/OrthancStoneParameters.cmake)
+
+
+#####################################################################
+## CMake parameters tunable by the user
+#####################################################################
+
+# None for now, but might contain stuff like memory settings
+
+
+#####################################################################
+## Internal CMake parameters to enable the optional subcomponents of
+## the Stone of Orthanc
+#####################################################################
+
+set(ENABLE_THREADS OFF CACHE INTERNAL "")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/SharedLibrary/CMakeLists.txt	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,211 @@
+# Stone of Orthanc
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+cmake_minimum_required(VERSION 2.8.3)
+
+project(OrthancStoneModule)
+
+
+# Warning message related to WebAssembly modules: We know that 1.38.41
+# DOES NOT work, but that 1.39.17 works.
+message("")
+message("=== IMPORTANT: Make sure to use a recent version of Emscripten (preferably >= 2.0.0) ===")
+message("")
+
+
+set(ORTHANC_STONE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/../../../wasm-binaries/OrthancStoneModule" CACHE PATH "Where to put the WebAssembly binaries")
+
+
+# Ask for the generation of a side module
+set(WASM_FLAGS "-s SIDE_MODULE=1 -s EXPORT_ALL=1")  # Must be before "Compiler.cmake"
+
+
+# Configuration of the Emscripten compiler for WebAssembly target
+# ---------------------------------------------------------------
+
+set(USE_WASM ON CACHE BOOL "")
+
+set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "")
+
+set(WASM_FLAGS "${WASM_FLAGS} -s WASM=1 -s FETCH=1")
+if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+  set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1")
+endif()
+
+set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0")
+set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1")
+add_definitions(
+  -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1
+)
+
+
+# Stone of Orthanc configuration
+# ---------------------------------------------------------------
+
+include(${CMAKE_SOURCE_DIR}/../OrthancStoneWebAssemblyParameters.cmake)
+
+SET(ENABLE_DCMTK ON)
+SET(ENABLE_DCMTK_NETWORKING OFF)
+SET(ENABLE_DCMTK_TRANSCODING OFF)
+SET(ENABLE_GOOGLE_TEST OFF)
+SET(ENABLE_LOCALE ON)  # Necessary for text rendering
+SET(ORTHANC_SANDBOXED ON)
+
+include(${CMAKE_SOURCE_DIR}/../OrthancStoneWebAssemblyConfiguration.cmake)
+
+
+
+
+################################################################################
+
+# The source files that register a callback cannot be part of a side
+# module, and must be compiled in the main module. The following
+# command can be used to identify such files:
+#  $ grep -lrE 'emscripten_' ../../Sources/
+
+set(SOURCES_WITH_EMSCRIPTEN_CALLBACKS
+  ${ORTHANC_STONE_ROOT}/Sources/Oracle/WebAssemblyOracle.cpp
+  ${ORTHANC_STONE_ROOT}/Sources/Viewport/WebAssemblyViewport.cpp
+  ${ORTHANC_STONE_ROOT}/Sources/Viewport/WebAssemblyCairoViewport.cpp
+  )
+
+list(REMOVE_ITEM ORTHANC_STONE_SOURCES
+  ${SOURCES_WITH_EMSCRIPTEN_CALLBACKS}
+  )
+
+configure_file(
+  ${CMAKE_SOURCE_DIR}/../OrthancStone.h.in
+  ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-stone/OrthancStone.h
+  )
+
+configure_file(
+  ${ORTHANC_FRAMEWORK_ROOT}/../SharedLibrary/OrthancFramework.h.in
+  ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework/OrthancFramework.h
+  )
+
+file(
+  COPY ${CMAKE_SOURCE_DIR}/../../Sources/
+  DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-stone
+  NO_SOURCE_PERMISSIONS
+  FILES_MATCHING
+  PATTERN "*.h"
+  PATTERN OrthancStone.h EXCLUDE
+  PATTERN "Deprecated*" EXCLUDE
+  )
+
+file(
+  COPY ${ORTHANC_FRAMEWORK_ROOT}/
+  DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework
+  NO_SOURCE_PERMISSIONS
+  FILES_MATCHING
+  PATTERN "*.h"
+  PATTERN OrthancFramework.h EXCLUDE
+  )
+
+add_executable(OrthancStoneModule
+  ${ORTHANC_STONE_SOURCES}
+  ${AUTOGENERATED_SOURCES}  
+  ${CAIRO_SOURCES}
+  ${PIXMAN_SOURCES}
+  ${FREETYPE_SOURCES}
+  )
+
+set_target_properties(OrthancStoneModule
+  PROPERTIES
+  COMPILE_FLAGS "${WASM_FLAGS}"
+  LINK_FLAGS "${WASM_LINKER_FLAGS}"
+  )
+
+# CMake does not natively handle SIDE_MODULE, and believes that
+# Emscripten produces a ".js" file (whereas it creates only the
+# ".wasm"). Create a dummy ".js" for target to work.
+add_custom_command(
+  TARGET OrthancStoneModule POST_BUILD
+  COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/OrthancStoneModule.js
+  )
+
+file(
+  COPY ${BOOST_SOURCES_DIR}/boost/
+  DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/boost/
+  NO_SOURCE_PERMISSIONS
+  FILES_MATCHING
+  PATTERN "*.h"
+  PATTERN "*.hpp"
+  PATTERN "*.ipp"
+  )
+
+file(
+  COPY ${JSONCPP_SOURCES_DIR}/include/json/
+  DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/json/
+  NO_SOURCE_PERMISSIONS
+  FILES_MATCHING
+  PATTERN "*.h"
+  )
+
+file(
+  COPY ${CAIRO_SOURCES_DIR}/src/
+  DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/cairo/
+  NO_SOURCE_PERMISSIONS
+  FILES_MATCHING
+  PATTERN "*.h"
+  )
+
+set(DCMTK_MODULES
+  dcmdata
+  config
+  ofstd
+  oflog
+  )
+
+foreach (module IN LISTS DCMTK_MODULES)
+  file(
+    COPY ${DCMTK_SOURCES_DIR}/ofstd/include/dcmtk/${module}/
+    DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/dcmtk/${module}/
+    NO_SOURCE_PERMISSIONS
+    FILES_MATCHING
+    PATTERN "*.h"
+    )
+endforeach()
+
+    
+install(
+  TARGETS OrthancStoneModule
+  DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/lib
+  )
+
+install(FILES
+  ${CMAKE_CURRENT_BINARY_DIR}/OrthancStoneModule.wasm
+  DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/lib
+  )
+
+install(
+  DIRECTORY
+  ${CMAKE_CURRENT_BINARY_DIR}/Include/boost
+  ${CMAKE_CURRENT_BINARY_DIR}/Include/cairo
+  ${CMAKE_CURRENT_BINARY_DIR}/Include/dcmtk
+  ${CMAKE_CURRENT_BINARY_DIR}/Include/json
+  ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework
+  ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-stone
+  DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/include/
+  )
+
+install(FILES
+  ${SOURCES_WITH_EMSCRIPTEN_CALLBACKS}
+  DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/src/orthanc-stone
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/SharedLibrary/NOTES.txt	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,18 @@
+
+Install Emscripten:
+https://emscripten.org/docs/getting_started/downloads.html
+
+# cd ~/Downloads
+# git clone https://github.com/emscripten-core/emsdk.git
+# cd emsdk
+# ./emsdk install 2.0.0
+# ./emsdk activate 2.0.0
+
+
+Then, if the installation path was "~/Downloads/emsdk/":
+
+# source ~/Downloads/emsdk/emsdk_env.sh
+# cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DALLOW_DOWNLOADS=ON -G Ninja
+# ninja install
+
+=> The binaries will be put in "../../../../wasm-binaries/"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/WebAssemblyCairoViewport.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,113 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1
+#  include "WebAssemblyCairoViewport.h"
+#  include "../../../OrthancStone/Sources/Scene2D/CairoCompositor.h"
+#  include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h"
+#else
+// This is the case when using the WebAssembly side module, and this
+// source file must be compiled within the WebAssembly main module
+#  include <Viewport/WebAssemblyCairoViewport.h>
+#  include <Scene2D/CairoCompositor.h>
+#  include <Scene2DViewport/ViewportController.h>
+#endif
+
+
+#include <Images/Image.h>
+
+namespace OrthancStone
+{
+  void WebAssemblyCairoViewport::Paint(ICompositor& compositor,
+                                       ViewportController& controller)
+  {
+    compositor.Refresh(controller.GetScene());
+
+    // Create a temporary memory buffer for the canvas in JavaScript
+    Orthanc::ImageAccessor cairo;
+    dynamic_cast<CairoCompositor&>(compositor).GetCanvas().GetReadOnlyAccessor(cairo);
+
+    const unsigned int width = cairo.GetWidth();
+    const unsigned int height = cairo.GetHeight();
+
+    if (javascript_.get() == NULL ||
+        javascript_->GetWidth() != width ||
+        javascript_->GetHeight() != height)
+    {
+      javascript_.reset(new Orthanc::Image(Orthanc::PixelFormat_RGBA32, width, height,
+                                           true /* force minimal pitch */));
+    }
+      
+    // Convert from BGRA32 memory layout (only color mode supported
+    // by Cairo, which corresponds to CAIRO_FORMAT_ARGB32) to RGBA32
+    // (as expected by HTML5 canvas). This simply amounts to
+    // swapping the B and R channels. Alpha channel is also set to
+    // full opacity (255).
+    uint8_t* q = reinterpret_cast<uint8_t*>(javascript_->GetBuffer());
+    for (unsigned int y = 0; y < height; y++)
+    {
+      const uint8_t* p = reinterpret_cast<const uint8_t*>(cairo.GetConstRow(y));
+      for (unsigned int x = 0; x < width; x++)
+      {
+        q[0] = p[2];  // R
+        q[1] = p[1];  // G
+        q[2] = p[0];  // B
+        q[3] = 255;   // A
+
+        p += 4;
+        q += 4;
+      }
+    }
+
+    // Execute JavaScript commands to blit the image buffer onto the
+    // 2D drawing context of the HTML5 canvas
+    EM_ASM({
+        const data = new Uint8ClampedArray(Module.HEAP8.buffer, $1, 4 * $2 * $3);
+        const img = new ImageData(data, $2, $3);
+        const ctx = document.getElementById(UTF8ToString($0)).getContext('2d');
+        ctx.putImageData(img, 0, 0);
+      },
+      GetCanvasId().c_str(), // $0
+      javascript_->GetBuffer(),   // $1
+      javascript_->GetWidth(),    // $2
+      javascript_->GetHeight());  // $3
+  }
+    
+
+  WebAssemblyCairoViewport::WebAssemblyCairoViewport(const std::string& canvasId,
+                                                     bool enableEmscriptenMouseEvents) :
+    WebAssemblyViewport(canvasId,enableEmscriptenMouseEvents)
+  {
+    RefreshCanvasSize();
+    AcquireCompositor(new CairoCompositor(GetCanvasWidth(), GetCanvasHeight()));
+  }
+  
+
+  boost::shared_ptr<WebAssemblyCairoViewport> WebAssemblyCairoViewport::Create(
+    const std::string& canvasId, bool enableEmscriptenMouseEvents)
+  {
+    boost::shared_ptr<WebAssemblyCairoViewport> that = boost::shared_ptr<WebAssemblyCairoViewport>(
+        new WebAssemblyCairoViewport(canvasId, enableEmscriptenMouseEvents));
+    
+    that->WebAssemblyViewport::PostConstructor();
+    return that;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/WebAssemblyCairoViewport.h	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,49 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "WebAssemblyViewport.h"
+
+namespace OrthancStone
+{
+  class WebAssemblyCairoViewport : public WebAssemblyViewport
+  {
+  private:
+    std::unique_ptr<Orthanc::ImageAccessor>  javascript_;
+        
+    WebAssemblyCairoViewport(const std::string& canvasId,
+                             bool enableEmscriptenMouseEvents);
+
+  protected:
+    virtual void Paint(ICompositor& compositor,
+                       ViewportController& controller) ORTHANC_OVERRIDE;
+    
+  public:
+    static boost::shared_ptr<WebAssemblyCairoViewport> Create(const std::string& canvasId,
+                                                              bool enableEmscriptenMouseEvents = true);
+
+    virtual ~WebAssemblyCairoViewport()
+    {
+      ClearCompositor();
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/WebAssemblyLoadersContext.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,97 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "WebAssemblyLoadersContext.h"
+
+namespace OrthancStone
+{
+  class WebAssemblyLoadersContext::Locker : public ILoadersContext::ILock
+  {
+  private:
+    WebAssemblyLoadersContext&  that_;
+
+  public:
+    explicit Locker(WebAssemblyLoadersContext& that) :
+      that_(that)
+    {
+    }      
+      
+    virtual ILoadersContext& GetContext() const ORTHANC_OVERRIDE
+    {
+      return that_;
+    }
+
+    virtual IObservable& GetOracleObservable() const ORTHANC_OVERRIDE
+    {
+      return that_.oracle_.GetOracleObservable();
+    }
+
+    virtual void Schedule(boost::shared_ptr<IObserver> receiver,
+                          int priority,
+                          IOracleCommand* command /* Takes ownership */) ORTHANC_OVERRIDE
+    {
+      that_.scheduler_->Schedule(receiver, priority, command);
+    }
+
+    virtual void CancelRequests(boost::shared_ptr<IObserver> receiver) ORTHANC_OVERRIDE
+    {
+      that_.scheduler_->CancelRequests(receiver);
+    }
+
+    virtual void CancelAllRequests() ORTHANC_OVERRIDE
+    {
+      that_.scheduler_->CancelAllRequests();
+    }
+
+    virtual void AddLoader(boost::shared_ptr<IObserver> loader) ORTHANC_OVERRIDE
+    {
+      that_.loaders_.push_back(loader);
+    }
+
+    virtual void GetStatistics(uint64_t& scheduledCommands,
+                               uint64_t& processedCommands) ORTHANC_OVERRIDE
+    {
+      scheduledCommands = that_.scheduler_->GetTotalScheduled();
+      processedCommands = that_.scheduler_->GetTotalProcessed();
+    }
+  };
+    
+
+  WebAssemblyLoadersContext::WebAssemblyLoadersContext(unsigned int maxHighPriority,
+                                                       unsigned int maxStandardPriority,
+                                                       unsigned int maxLowPriority)
+  {
+    oracle_.GetOracleObservable();
+    scheduler_ = OracleScheduler::Create(oracle_, oracle_.GetOracleObservable(), oracle_,
+                                         maxHighPriority, maxStandardPriority, maxLowPriority);
+
+    if (!scheduler_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  ILoadersContext::ILock* WebAssemblyLoadersContext::Lock()
+  {
+    return new Locker(*this);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/WebAssemblyLoadersContext.h	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,63 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../../OrthancStone/Sources/Loaders/ILoadersContext.h"
+#include "../../../OrthancStone/Sources/Loaders/OracleScheduler.h"
+#include "WebAssemblyOracle.h"
+
+#include <list>
+
+namespace OrthancStone
+{
+  class WebAssemblyLoadersContext : public ILoadersContext
+  {
+  private:
+    class Locker;
+    
+    WebAssemblyOracle                          oracle_;
+    boost::shared_ptr<OracleScheduler>         scheduler_;
+    std::list< boost::shared_ptr<IObserver> >  loaders_;
+    
+  public:
+    WebAssemblyLoadersContext(unsigned int maxHighPriority,
+                              unsigned int maxStandardPriority,
+                              unsigned int maxLowPriority);
+
+    void SetLocalOrthanc(const std::string& root)
+    {
+      oracle_.SetLocalOrthanc(root);
+    }
+
+    void SetRemoteOrthanc(const Orthanc::WebServiceParameters& orthanc)
+    {
+      oracle_.SetRemoteOrthanc(orthanc);
+    }
+
+    void SetDicomCacheSize(size_t size)
+    {
+      oracle_.SetDicomCacheSize(size);
+    }
+
+    virtual ILock* Lock() ORTHANC_OVERRIDE;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/WebAssemblyOpenGLContext.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,199 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "WebAssemblyOpenGLContext.h"
+
+#include "../../../OrthancStone/Sources/StoneException.h"
+
+#include <OrthancException.h>
+
+#include <emscripten/html5.h>
+#include <emscripten/em_asm.h>
+
+#include <boost/math/special_functions/round.hpp>
+
+namespace OrthancStone
+{
+  namespace OpenGL
+  {
+    class WebAssemblyOpenGLContext::PImpl
+    {
+    private:
+      std::string                     canvasSelector_;
+      EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context_;
+      bool                            isContextLost_;
+
+    public:
+      explicit PImpl(const std::string& canvasSelector) :
+        canvasSelector_(canvasSelector),
+        isContextLost_(false)
+      {
+        // Context configuration
+        EmscriptenWebGLContextAttributes attr; 
+        emscripten_webgl_init_context_attributes(&attr);
+
+        // The next line might be necessary to print using
+        // WebGL. Sometimes, if set to "false" (the default value),
+        // the canvas was rendered as a fully white or black
+        // area. UNCONFIRMED.
+        attr.preserveDrawingBuffer = true;
+
+        context_ = emscripten_webgl_create_context(canvasSelector.c_str(), &attr);
+        if (context_ == 0)
+        {
+          std::string message("Cannot create an OpenGL context for the element with the following CSS selector: \"");
+          message += canvasSelector;
+          message += "\"  Please make sure the -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 flag has been passed to Emscripten when building.";
+          LOG(ERROR) << message;
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, message);
+        }
+      }
+
+      void* DebugGetInternalContext() const
+      {
+        return reinterpret_cast<void*>(context_);
+      }
+
+      bool IsContextLost()
+      {
+        //LOG(TRACE) << "IsContextLost() for context " << std::hex << context_ << std::dec;
+        bool apiFlag = (emscripten_is_webgl_context_lost(context_) != 0);
+        isContextLost_ = apiFlag;
+        return isContextLost_;
+      }
+
+      void SetLostContext()
+      {
+        isContextLost_ = true;
+      }
+
+      ~PImpl()
+      {
+        try
+        {
+          EMSCRIPTEN_RESULT result = emscripten_webgl_destroy_context(context_);
+          if (result != EMSCRIPTEN_RESULT_SUCCESS)
+          {
+            LOG(ERROR) << "emscripten_webgl_destroy_context returned code " << result;
+          }
+        }
+        catch (const Orthanc::OrthancException& e)
+        {
+          if (e.HasDetails())
+          {
+            LOG(ERROR) << "OrthancException in WebAssemblyOpenGLContext::~PImpl: " << e.What() << " Details: " << e.GetDetails();
+          }
+          else
+          {
+            LOG(ERROR) << "OrthancException in WebAssemblyOpenGLContext::~PImpl: " << e.What();
+          }
+        }
+        catch (const std::exception& e)
+        {
+          LOG(ERROR) << "std::exception in WebAssemblyOpenGLContext::~PImpl: " << e.what();
+        }
+        catch (...)
+        {
+          LOG(ERROR) << "Unknown exception in WebAssemblyOpenGLContext::~PImpl";
+        }
+      }
+
+      const std::string& GetCanvasSelector() const
+      {
+        return canvasSelector_;
+      }
+
+      void MakeCurrent()
+      {
+        if (IsContextLost())
+        {
+          LOG(ERROR) << "MakeCurrent() called on lost context " << context_;
+          throw StoneException(ErrorCode_WebGLContextLost);
+        }
+
+        if (emscripten_is_webgl_context_lost(context_))
+        {
+          LOG(ERROR) << "OpenGL context has been lost for canvas selector: " << canvasSelector_;
+          SetLostContext();
+          throw StoneException(ErrorCode_WebGLContextLost);
+        }
+
+        if (emscripten_webgl_make_context_current(context_) != EMSCRIPTEN_RESULT_SUCCESS)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                          "Cannot set the OpenGL context");
+        }
+      }
+
+      void SwapBuffer() 
+      {
+        /**
+         * "Rendered WebGL content is implicitly presented (displayed to
+         * the user) on the canvas when the event handler that renders with
+         * WebGL returns back to the browser event loop."
+         * https://emscripten.org/docs/api_reference/html5.h.html#webgl-context
+         *
+         * Could call "emscripten_webgl_commit_frame()" if
+         * "explicitSwapControl" option were set to "true".
+         **/
+      }
+    };
+
+
+    WebAssemblyOpenGLContext::WebAssemblyOpenGLContext(const std::string& canvasSelector) :
+      pimpl_(new PImpl(canvasSelector))
+    {
+    }
+
+    bool WebAssemblyOpenGLContext::IsContextLost()
+    {
+      return pimpl_->IsContextLost();
+    }
+
+    void WebAssemblyOpenGLContext::SetLostContext()
+    {
+      pimpl_->SetLostContext();
+    }
+
+    void* WebAssemblyOpenGLContext::DebugGetInternalContext() const
+    {
+      return pimpl_->DebugGetInternalContext();
+    }
+    
+    void WebAssemblyOpenGLContext::MakeCurrent()
+    {
+      assert(pimpl_.get() != NULL);
+      pimpl_->MakeCurrent();
+    }
+
+    void WebAssemblyOpenGLContext::SwapBuffer() 
+    {
+      assert(pimpl_.get() != NULL);
+      pimpl_->SwapBuffer();
+    }
+
+    const std::string& WebAssemblyOpenGLContext::GetCanvasSelector() const
+    {
+      assert(pimpl_.get() != NULL);
+      return pimpl_->GetCanvasSelector();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/WebAssemblyOpenGLContext.h	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,80 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if !defined(ORTHANC_ENABLE_WASM)
+#  error Macro ORTHANC_ENABLE_WASM must be defined
+#endif
+
+#if ORTHANC_ENABLE_WASM != 1
+#  error This file can only be used if targeting WebAssembly
+#endif
+
+#if !defined(ORTHANC_ENABLE_OPENGL)
+#  error The macro ORTHANC_ENABLE_OPENGL must be defined
+#endif
+
+#if ORTHANC_ENABLE_OPENGL != 1
+#  error Support for OpenGL is disabled
+#endif
+
+#include "../../../OrthancStone/Sources/OpenGL/IOpenGLContext.h"
+
+#include <Compatibility.h>  // For ORTHANC_OVERRIDE
+
+#include <boost/shared_ptr.hpp>
+
+namespace OrthancStone
+{
+  namespace OpenGL
+  {
+    class WebAssemblyOpenGLContext : public OpenGL::IOpenGLContext
+    {
+    private:
+      class PImpl;
+      boost::shared_ptr<PImpl>  pimpl_;
+
+    public:
+      explicit WebAssemblyOpenGLContext(const std::string& canvasSelector);
+
+      virtual bool IsContextLost() ORTHANC_OVERRIDE;
+
+      virtual void MakeCurrent() ORTHANC_OVERRIDE;
+
+      virtual void SwapBuffer() ORTHANC_OVERRIDE;
+
+      /**
+      Returns true if the underlying context has been successfully recreated
+      */
+      //bool TryRecreate();
+
+      const std::string& GetCanvasSelector() const;
+
+
+      /**
+       * This is for manual context loss (debug purposes)
+       **/
+      void* DebugGetInternalContext() const;
+      void SetLostContext();
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/WebAssemblyOracle.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,818 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1
+#  include "WebAssemblyOracle_Includes.h"
+#else
+// This is the case when using the WebAssembly side module, and this
+// source file must be compiled within the WebAssembly main module
+#  include <Oracle/WebAssemblyOracle_Includes.h>
+#endif
+
+#include <OrthancException.h>
+#include <Toolbox.h>
+
+#include <emscripten.h>
+#include <emscripten/html5.h>
+#include <emscripten/fetch.h>
+
+
+#if ORTHANC_ENABLE_DCMTK == 1
+static unsigned int BUCKET_SOP = 1;
+#endif
+
+
+namespace OrthancStone
+{
+  class WebAssemblyOracle::TimeoutContext
+  {
+  private:
+    WebAssemblyOracle&                 oracle_;
+    boost::weak_ptr<IObserver>         receiver_;
+    std::unique_ptr<SleepOracleCommand>  command_;
+
+  public:
+    TimeoutContext(WebAssemblyOracle& oracle,
+                   boost::weak_ptr<IObserver> receiver,
+                   IOracleCommand* command) :
+      oracle_(oracle),
+      receiver_(receiver)
+    {
+      if (command == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+      else
+      {
+        command_.reset(dynamic_cast<SleepOracleCommand*>(command));
+      }
+    }
+
+    void EmitMessage()
+    {      
+      assert(command_.get() != NULL);
+
+      SleepOracleCommand::TimeoutMessage message(*command_);
+      oracle_.EmitMessage(receiver_, message);
+    }
+
+    static void Callback(void *userData)
+    {
+      std::unique_ptr<TimeoutContext> context(reinterpret_cast<TimeoutContext*>(userData));
+      context->EmitMessage();
+    }
+  };
+    
+
+  /**
+  This object is created on the heap for every http request.
+  It is deleted in the success (or error) callbacks.
+
+  This object references the receiver of the request. Since this is a raw
+  reference, we need additional checks to make sure we send the response to
+  the same object, for the object can be deleted and a new one recreated at the
+  same address (it often happens in the [single-threaded] browser context).
+  */
+  class WebAssemblyOracle::FetchContext : public boost::noncopyable
+  {
+  private:
+    WebAssemblyOracle&             oracle_;
+    boost::weak_ptr<IObserver>     receiver_;
+    std::unique_ptr<IOracleCommand>  command_;
+    std::string                    expectedContentType_;
+
+  public:
+    FetchContext(WebAssemblyOracle& oracle,
+                 boost::weak_ptr<IObserver> receiver,
+                 IOracleCommand* command,
+                 const std::string& expectedContentType) :
+      oracle_(oracle),
+      receiver_(receiver),
+      command_(command),
+      expectedContentType_(expectedContentType)
+    {
+      if (Orthanc::Logging::IsTraceLevelEnabled())
+      {
+        // Calling "receiver.lock()" is expensive, hence the quick check if TRACE is enabled
+        LOG(TRACE) << "WebAssemblyOracle::FetchContext::FetchContext() | "
+                   << "receiver address = " << std::hex << receiver.lock().get();
+      }
+
+      if (command == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+    }
+
+    const std::string& GetExpectedContentType() const
+    {
+      return expectedContentType_;
+    }
+
+    IMessageEmitter& GetEmitter() const
+    {
+      return oracle_;
+    }
+
+    boost::weak_ptr<IObserver> GetReceiver() const
+    {
+      return receiver_;
+    }
+
+    void EmitMessage(const IMessage& message)
+    {
+      if (Orthanc::Logging::IsTraceLevelEnabled())
+      {
+        // Calling "receiver_.lock()" is expensive, hence the quick check if TRACE is enabled
+        LOG(TRACE) << "WebAssemblyOracle::FetchContext::EmitMessage receiver_ = "
+                   << std::hex << receiver_.lock().get() << std::dec;
+      }
+      
+      oracle_.EmitMessage(receiver_, message);
+    }
+
+    IOracleCommand& GetCommand() const
+    {
+      return *command_;
+    }
+
+    template <typename T>
+    const T& GetTypedCommand() const
+    {
+      return dynamic_cast<T&>(*command_);
+    }
+
+#if ORTHANC_ENABLE_DCMTK == 1
+    void StoreInCache(const std::string& sopInstanceUid,
+                      std::unique_ptr<Orthanc::ParsedDicomFile>& dicom,
+                      size_t fileSize)
+    {
+      if (oracle_.dicomCache_.get())
+      {
+        // Store it into the cache for future use
+        oracle_.dicomCache_->Acquire(BUCKET_SOP, sopInstanceUid,
+                                     dicom.release(), fileSize, true);
+      }              
+    }
+#endif
+
+    static void SuccessCallback(emscripten_fetch_t *fetch)
+    {
+      /**
+       * Firstly, make a local copy of the fetched information, and
+       * free data associated with the fetch.
+       **/
+      
+      if (fetch->userData == NULL)
+      {
+        LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback fetch->userData is NULL!!!!!!!";
+        return;
+      }
+
+      std::unique_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData));
+
+      std::string answer;
+      if (fetch->numBytes > 0)
+      {
+        answer.assign(fetch->data, fetch->numBytes);
+      }
+
+
+      /**
+       * Retrieving the headers of the HTTP answer.
+       **/
+      HttpHeaders headers;
+
+#if (__EMSCRIPTEN_major__ < 1 ||                                        \
+     (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ < 38) ||        \
+     (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ == 38 && __EMSCRIPTEN_tiny__ < 37))
+#  warning Consider upgrading Emscripten to a version above 1.38.37, incomplete support of Fetch API
+
+      /**
+       * HACK - If emscripten < 1.38.37, the fetch API does not
+       * contain a way to retrieve the HTTP headers of the answer. We
+       * make the assumption that the "Content-Type" header of the
+       * response is the same as the "Accept" header of the
+       * query. This is fixed thanks to the
+       * "emscripten_fetch_get_response_headers()" function that was
+       * added to "fetch.h" at emscripten-1.38.37 on 2019-06-26.
+       *
+       * https://github.com/emscripten-core/emscripten/blob/1.38.37/system/include/emscripten/fetch.h
+       * https://github.com/emscripten-core/emscripten/pull/8486
+       **/
+      if (fetch->userData != NULL)
+      {
+        if (!context->GetExpectedContentType().empty())
+        {
+          headers["Content-Type"] = context->GetExpectedContentType();
+        }
+      }
+#else
+      {
+        size_t size = emscripten_fetch_get_response_headers_length(fetch);
+
+        std::string plainHeaders(size + 1, '\0');
+        emscripten_fetch_get_response_headers(fetch, &plainHeaders[0], size + 1);
+
+        std::vector<std::string> tokens;
+        Orthanc::Toolbox::TokenizeString(tokens, plainHeaders, '\n');
+
+        for (size_t i = 0; i < tokens.size(); i++)
+        {
+          size_t p = tokens[i].find(':');
+          if (p != std::string::npos)
+          {
+            std::string key = Orthanc::Toolbox::StripSpaces(tokens[i].substr(0, p));
+            std::string value = Orthanc::Toolbox::StripSpaces(tokens[i].substr(p + 1));
+            headers[key] = value;
+          }
+        }
+      }
+#endif
+      
+      LOG(TRACE) << "About to call emscripten_fetch_close";
+      emscripten_fetch_close(fetch);
+      LOG(TRACE) << "Successfully called emscripten_fetch_close";
+
+      /**
+       * Secondly, use the retrieved data.
+       * IMPORTANT NOTE: the receiver might be dead. This is prevented 
+       * by the object responsible for zombie check, later on.
+       **/
+      try
+      {
+        if (context.get() == NULL)
+        {
+          LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback: (context.get() == NULL)";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+        }
+        else
+        {
+          switch (context->GetCommand().GetType())
+          {
+            case IOracleCommand::Type_Http:
+            {
+              HttpCommand::SuccessMessage message(context->GetTypedCommand<HttpCommand>(), headers, answer);
+              context->EmitMessage(message);
+              break;
+            }
+
+            case IOracleCommand::Type_OrthancRestApi:
+            {
+              LOG(TRACE) << "WebAssemblyOracle::FetchContext::SuccessCallback. About to call context->EmitMessage(message);";
+              OrthancRestApiCommand::SuccessMessage message
+                (context->GetTypedCommand<OrthancRestApiCommand>(), headers, answer);
+              context->EmitMessage(message);
+              break;
+            }
+
+            case IOracleCommand::Type_GetOrthancImage:
+            {
+              context->GetTypedCommand<GetOrthancImageCommand>().ProcessHttpAnswer
+                (context->GetReceiver(), context->GetEmitter(), answer, headers);
+              break;
+            }
+
+            case IOracleCommand::Type_GetOrthancWebViewerJpeg:
+            {
+              context->GetTypedCommand<GetOrthancWebViewerJpegCommand>().ProcessHttpAnswer
+                (context->GetReceiver(), context->GetEmitter(), answer);
+              break;
+            }
+
+            case IOracleCommand::Type_ParseDicomFromWado:
+            {
+#if ORTHANC_ENABLE_DCMTK == 1
+              const ParseDicomFromWadoCommand& command =
+                context->GetTypedCommand<ParseDicomFromWadoCommand>();
+              
+              size_t fileSize;
+              std::unique_ptr<Orthanc::ParsedDicomFile> dicom
+                (ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, headers));
+
+              {
+                ParseDicomSuccessMessage message(command, command.GetSource(), *dicom, fileSize, true);
+                context->EmitMessage(message);
+              }
+
+              context->StoreInCache(command.GetSopInstanceUid(), dicom, fileSize);
+#else
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+#endif
+              break;
+            }
+
+            default:
+              LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in SuccessCallback): "
+                         << context->GetCommand().GetType();
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+          }
+        }
+      }
+      catch (Orthanc::OrthancException& e)
+      {
+        LOG(INFO) << "Error while processing a fetch answer in the oracle: " << e.What();
+
+        {
+          OracleCommandExceptionMessage message(context->GetCommand(), e);
+          context->EmitMessage(message);
+        }
+      }
+    }
+
+    static void FailureCallback(emscripten_fetch_t *fetch)
+    {
+      std::unique_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData));
+
+#if 0
+      {
+        const size_t kEmscriptenStatusTextSize = sizeof(emscripten_fetch_t::statusText);
+        char message[kEmscriptenStatusTextSize + 1];
+        memcpy(message, fetch->statusText, kEmscriptenStatusTextSize);
+        message[kEmscriptenStatusTextSize] = 0;
+
+        LOG(ERROR) << "Fetching " << fetch->url
+                   << " failed, HTTP failure status code: " << fetch->status
+                   << " | statusText = " << message
+                   << " | numBytes = " << fetch->numBytes
+                   << " | totalBytes = " << fetch->totalBytes
+                   << " | readyState = " << fetch->readyState;
+      }
+#endif
+
+      {
+        OracleCommandExceptionMessage message
+          (context->GetCommand(), Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol));
+        context->EmitMessage(message);
+      }
+      
+      /**
+       * TODO - The following code leads to an infinite recursion, at
+       * least with Firefox running on incognito mode => WHY?
+       **/      
+      emscripten_fetch_close(fetch); // Also free data on failure.
+    }
+  };
+    
+
+
+  class WebAssemblyOracle::FetchCommand : public boost::noncopyable
+  {
+  private:
+    WebAssemblyOracle&             oracle_;
+    boost::weak_ptr<IObserver>     receiver_;
+    std::unique_ptr<IOracleCommand>  command_;
+    Orthanc::HttpMethod            method_;
+    std::string                    url_;
+    std::string                    body_;
+    HttpHeaders                    headers_;
+    unsigned int                   timeout_;
+    std::string                    expectedContentType_;
+    bool                           hasCredentials_;
+    std::string                    username_;
+    std::string                    password_;
+
+  public:
+    FetchCommand(WebAssemblyOracle& oracle,
+                 boost::weak_ptr<IObserver> receiver,
+                 IOracleCommand* command) :
+      oracle_(oracle),
+      receiver_(receiver),
+      command_(command),
+      method_(Orthanc::HttpMethod_Get),
+      timeout_(0),
+      hasCredentials_(false)
+    {
+      if (command == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+    }
+
+    void SetMethod(Orthanc::HttpMethod method)
+    {
+      method_ = method;
+    }
+
+    void SetUrl(const std::string& url)
+    {
+      url_ = url;
+    }
+
+    void SetBody(std::string& body /* will be swapped */)
+    {
+      body_.swap(body);
+    }
+
+    void AddHttpHeaders(const HttpHeaders& headers)
+    {
+      for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it)
+      {
+        headers_[it->first] = it->second;
+      }
+    }
+
+    void SetTimeout(unsigned int timeout)
+    {
+      timeout_ = timeout;
+    }
+
+    void SetCredentials(const std::string& username,
+                        const std::string& password)
+    {
+      hasCredentials_ = true;
+      username_ = username;
+      password_ = password;
+    }
+
+    void Execute()
+    {
+      if (command_.get() == NULL)
+      {
+        // Cannot call Execute() twice
+        LOG(ERROR) << "WebAssemblyOracle::Execute(): (command_.get() == NULL)";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+
+      emscripten_fetch_attr_t attr;
+      emscripten_fetch_attr_init(&attr);
+
+      const char* method;
+      
+      switch (method_)
+      {
+        case Orthanc::HttpMethod_Get:
+          method = "GET";
+          break;
+
+        case Orthanc::HttpMethod_Post:
+          method = "POST";
+          break;
+
+        case Orthanc::HttpMethod_Delete:
+          method = "DELETE";
+          break;
+
+        case Orthanc::HttpMethod_Put:
+          method = "PUT";
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+
+      strcpy(attr.requestMethod, method);
+
+      attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_REPLACE;
+      attr.onsuccess = FetchContext::SuccessCallback;
+      attr.onerror = FetchContext::FailureCallback;
+      attr.timeoutMSecs = timeout_ * 1000;
+
+      if (hasCredentials_)
+      {
+        attr.withCredentials = EM_TRUE;
+        attr.userName = username_.c_str();
+        attr.password = password_.c_str();
+      }
+      
+      std::vector<const char*> headers;
+      headers.reserve(2 * headers_.size() + 1);
+
+      std::string expectedContentType;
+        
+      for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it)
+      {
+        std::string key;
+        Orthanc::Toolbox::ToLowerCase(key, it->first);
+          
+        if (key == "accept")
+        {
+          expectedContentType = it->second;
+        }
+
+        if (key != "accept-encoding")  // Web browsers forbid the modification of this HTTP header
+        {
+          headers.push_back(it->first.c_str());
+          headers.push_back(it->second.c_str());
+        }
+      }
+        
+      headers.push_back(NULL);  // Termination of the array of HTTP headers
+
+      attr.requestHeaders = &headers[0];
+
+      char* requestData = NULL;
+      if (!body_.empty())
+        requestData = reinterpret_cast<char*>(malloc(body_.size()));
+        
+      try 
+      {
+        if (!body_.empty())
+        {
+          memcpy(requestData, &(body_[0]), body_.size());
+          attr.requestDataSize = body_.size();
+          attr.requestData = requestData;
+        }
+        attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType);
+
+        // Must be the last call to prevent memory leak on error
+        emscripten_fetch(&attr, url_.c_str());
+      }        
+      catch(...)
+      {
+        if(requestData != NULL)
+          free(requestData);
+        throw;
+      }
+    }
+  };
+
+
+  void WebAssemblyOracle::SetOrthancUrl(FetchCommand& command,
+                                        const std::string& uri) const
+  {
+    if (isLocalOrthanc_)
+    {
+      command.SetUrl(localOrthancRoot_ + uri);
+    }
+    else
+    {
+      command.SetUrl(remoteOrthanc_.GetUrl() + uri);
+      command.AddHttpHeaders(remoteOrthanc_.GetHttpHeaders());
+      
+      if (!remoteOrthanc_.GetUsername().empty())
+      {
+        command.SetCredentials(remoteOrthanc_.GetUsername(), remoteOrthanc_.GetPassword());
+      }
+    }
+  }
+    
+
+  void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
+                                  HttpCommand* command)
+  {
+    FetchCommand fetch(*this, receiver, command);
+    
+    fetch.SetMethod(command->GetMethod());
+    fetch.SetUrl(command->GetUrl());
+    fetch.AddHttpHeaders(command->GetHttpHeaders());
+    fetch.SetTimeout(command->GetTimeout());
+    
+    if (command->GetMethod() == Orthanc::HttpMethod_Post ||
+        command->GetMethod() == Orthanc::HttpMethod_Put)
+    {
+      std::string body;
+      command->SwapBody(body);
+      fetch.SetBody(body);
+    }
+    
+    fetch.Execute();
+  }
+  
+
+  void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
+                                  OrthancRestApiCommand* command)
+  {
+    try
+    {
+      //LOG(TRACE) << "*********** WebAssemblyOracle::Execute.";
+      //LOG(TRACE) << "WebAssemblyOracle::Execute | command = " << command;
+      FetchCommand fetch(*this, receiver, command);
+
+      fetch.SetMethod(command->GetMethod());
+      SetOrthancUrl(fetch, command->GetUri());
+      fetch.AddHttpHeaders(command->GetHttpHeaders());
+      fetch.SetTimeout(command->GetTimeout());
+
+      if (command->GetMethod() == Orthanc::HttpMethod_Post ||
+        command->GetMethod() == Orthanc::HttpMethod_Put)
+      {
+        std::string body;
+        command->SwapBody(body);
+        fetch.SetBody(body);
+      }
+
+      fetch.Execute();
+      //LOG(TRACE) << "*********** successful end of WebAssemblyOracle::Execute.";
+    }
+    catch (const Orthanc::OrthancException& e)
+    {
+      if (e.HasDetails())
+      {
+        LOG(ERROR) << "OrthancException in WebAssemblyOracle::Execute: " << e.What() << " Details: " << e.GetDetails();
+      }
+      else
+      {
+        LOG(ERROR) << "OrthancException in WebAssemblyOracle::Execute: " << e.What();
+      }
+      //LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute.";
+      throw;
+    }
+    catch (const std::exception& e)
+    {
+      LOG(ERROR) << "std::exception in WebAssemblyOracle::Execute: " << e.what();
+//       LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute.";
+      throw;
+    }
+    catch (...)
+    {
+      LOG(ERROR) << "Unknown exception in WebAssemblyOracle::Execute";
+//       LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute.";
+      throw;
+    }
+  }
+    
+    
+  void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
+                                  GetOrthancImageCommand* command)
+  {
+    FetchCommand fetch(*this, receiver, command);
+
+    SetOrthancUrl(fetch, command->GetUri());
+    fetch.AddHttpHeaders(command->GetHttpHeaders());
+    fetch.SetTimeout(command->GetTimeout());
+      
+    fetch.Execute();
+  }
+    
+    
+  void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
+                                  GetOrthancWebViewerJpegCommand* command)
+  {
+    FetchCommand fetch(*this, receiver, command);
+
+    SetOrthancUrl(fetch, command->GetUri());
+    fetch.AddHttpHeaders(command->GetHttpHeaders());
+    fetch.SetTimeout(command->GetTimeout());
+      
+    fetch.Execute();
+  }
+
+
+  void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
+                                  ParseDicomFromWadoCommand* command)
+  {
+    std::unique_ptr<ParseDicomFromWadoCommand> protection(command);
+    
+#if ORTHANC_ENABLE_DCMTK == 1
+    if (dicomCache_.get())
+    {
+      ParsedDicomCache::Reader reader(*dicomCache_, BUCKET_SOP, protection->GetSopInstanceUid());
+      if (reader.IsValid() &&
+          reader.HasPixelData())
+      {
+        // Reuse the DICOM file from the cache
+        ParseDicomSuccessMessage message(*protection, protection->GetSource(), reader.GetDicom(),
+                                         reader.GetFileSize(), reader.HasPixelData());
+        EmitMessage(receiver, message);
+        return;
+      }
+    }
+#endif
+
+    switch (command->GetRestCommand().GetType())
+    {
+      case IOracleCommand::Type_Http:
+      {
+        const HttpCommand& rest =
+          dynamic_cast<const HttpCommand&>(protection->GetRestCommand());
+        
+        FetchCommand fetch(*this, receiver, protection.release());
+    
+        fetch.SetMethod(rest.GetMethod());
+        fetch.SetUrl(rest.GetUrl());
+        fetch.AddHttpHeaders(rest.GetHttpHeaders());
+        fetch.SetTimeout(rest.GetTimeout());
+    
+        if (rest.GetMethod() == Orthanc::HttpMethod_Post ||
+            rest.GetMethod() == Orthanc::HttpMethod_Put)
+        {
+          std::string body = rest.GetBody();
+          fetch.SetBody(body);
+        }
+    
+        fetch.Execute();
+        break;
+      }
+
+      case IOracleCommand::Type_OrthancRestApi:
+      {
+        const OrthancRestApiCommand& rest =
+          dynamic_cast<const OrthancRestApiCommand&>(protection->GetRestCommand());
+        
+        FetchCommand fetch(*this, receiver, protection.release());
+
+        fetch.SetMethod(rest.GetMethod());
+        SetOrthancUrl(fetch, rest.GetUri());
+        fetch.AddHttpHeaders(rest.GetHttpHeaders());
+        fetch.SetTimeout(rest.GetTimeout());
+
+        if (rest.GetMethod() == Orthanc::HttpMethod_Post ||
+            rest.GetMethod() == Orthanc::HttpMethod_Put)
+        {
+          std::string body = rest.GetBody();
+          fetch.SetBody(body);
+        }
+
+        fetch.Execute();
+        break;
+      }
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+
+
+  bool WebAssemblyOracle::Schedule(boost::shared_ptr<IObserver> receiver,
+                                   IOracleCommand* command)
+  {
+    LOG(TRACE) << "WebAssemblyOracle::Schedule : receiver = "
+               << std::hex << receiver.get();
+    
+    std::unique_ptr<IOracleCommand> protection(command);
+
+    if (command == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    switch (command->GetType())
+    {
+      case IOracleCommand::Type_Http:
+        Execute(receiver, dynamic_cast<HttpCommand*>(protection.release()));
+        break;
+        
+      case IOracleCommand::Type_OrthancRestApi:
+        Execute(receiver, dynamic_cast<OrthancRestApiCommand*>(protection.release()));
+        break;
+        
+      case IOracleCommand::Type_GetOrthancImage:
+        Execute(receiver, dynamic_cast<GetOrthancImageCommand*>(protection.release()));
+        break;
+
+      case IOracleCommand::Type_GetOrthancWebViewerJpeg:
+        Execute(receiver, dynamic_cast<GetOrthancWebViewerJpegCommand*>(protection.release()));
+        break;          
+            
+      case IOracleCommand::Type_Sleep:
+      {
+        unsigned int timeoutMS = dynamic_cast<SleepOracleCommand*>(command)->GetDelay();
+        emscripten_set_timeout(TimeoutContext::Callback, timeoutMS,
+                               new TimeoutContext(*this, receiver, protection.release()));
+        break;
+      }
+            
+      case IOracleCommand::Type_ParseDicomFromWado:
+#if ORTHANC_ENABLE_DCMTK == 1
+        Execute(receiver, dynamic_cast<ParseDicomFromWadoCommand*>(protection.release()));
+#else
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
+                                          "DCMTK must be enabled to parse DICOM files");
+#endif
+        break;
+            
+      default:
+        LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in Schedule): "
+                   << command->GetType();
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    return true;
+  }
+
+
+  void WebAssemblyOracle::SetDicomCacheSize(size_t size)
+  {
+#if ORTHANC_ENABLE_DCMTK == 1
+    if (size == 0)
+    {
+      dicomCache_.reset();
+    }
+    else
+    {
+      dicomCache_.reset(new ParsedDicomCache(size));
+    }
+#else
+    LOG(INFO) << "DCMTK support is disabled, the DICOM cache is disabled";
+#endif
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/WebAssemblyOracle.h	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,127 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../../OrthancStone/Sources/OrthancStone.h"
+
+#if !defined(ORTHANC_ENABLE_WASM)
+#  error The macro ORTHANC_ENABLE_WASM must be defined
+#endif
+
+#if ORTHANC_ENABLE_WASM != 1
+#  error This file can only compiled for WebAssembly
+#endif
+
+#include "../../../OrthancStone/Sources/Messages/IObservable.h"
+#include "../../../OrthancStone/Sources/Messages/IMessageEmitter.h"
+#include "../../../OrthancStone/Sources/Oracle/IOracle.h"
+
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include "../../../OrthancStone/Sources/Toolbox/ParsedDicomCache.h"
+#endif
+
+#include <Compatibility.h>  // For ORTHANC_OVERRIDE
+#include <WebServiceParameters.h>
+
+#include <Enumerations.h>
+
+namespace OrthancStone
+{
+  class GetOrthancImageCommand;
+  class GetOrthancWebViewerJpegCommand;
+  class HttpCommand;
+  class OrthancRestApiCommand;
+  class ParseDicomFromWadoCommand;
+  
+  class WebAssemblyOracle :
+    public IOracle,
+    public IMessageEmitter
+  {
+  private:
+    typedef std::map<std::string, std::string>  HttpHeaders;
+    
+    class TimeoutContext;
+    class FetchContext;
+    class FetchCommand;
+
+    void SetOrthancUrl(FetchCommand& command,
+                       const std::string& uri) const;
+    
+    void Execute(boost::weak_ptr<IObserver> receiver,
+                 HttpCommand* command);    
+    
+    void Execute(boost::weak_ptr<IObserver> receiver,
+                 OrthancRestApiCommand* command);    
+    
+    void Execute(boost::weak_ptr<IObserver> receiver,
+                 GetOrthancImageCommand* command);    
+    
+    void Execute(boost::weak_ptr<IObserver> receiver,
+                 GetOrthancWebViewerJpegCommand* command);
+    
+    void Execute(boost::weak_ptr<IObserver> receiver,
+                 ParseDicomFromWadoCommand* command);
+
+    IObservable                    oracleObservable_;
+    bool                           isLocalOrthanc_;
+    std::string                    localOrthancRoot_;
+    Orthanc::WebServiceParameters  remoteOrthanc_;
+
+#if ORTHANC_ENABLE_DCMTK == 1
+    std::unique_ptr<ParsedDicomCache>  dicomCache_;
+#endif
+
+  public:
+    WebAssemblyOracle() :
+      isLocalOrthanc_(false)
+    {
+    }
+    
+    virtual void EmitMessage(boost::weak_ptr<IObserver> observer,
+                             const IMessage& message) ORTHANC_OVERRIDE
+    {
+      oracleObservable_.EmitMessage(observer, message);
+    }
+    
+    virtual bool Schedule(boost::shared_ptr<IObserver> receiver,
+                          IOracleCommand* command) ORTHANC_OVERRIDE;
+
+    IObservable& GetOracleObservable()
+    {
+      return oracleObservable_;
+    }
+
+    void SetLocalOrthanc(const std::string& root)
+    {
+      isLocalOrthanc_ = true;
+      localOrthancRoot_ = root;
+    }
+
+    void SetRemoteOrthanc(const Orthanc::WebServiceParameters& orthanc)
+    {
+      isLocalOrthanc_ = false;
+      remoteOrthanc_ = orthanc;
+    }
+
+    void SetDicomCacheSize(size_t size);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/WebAssemblyOracle_Includes.h	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,42 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+/**
+ * This file serves as an indirection to avoid large "#if
+ * ORTHANC_BUILDING_STONE_LIBRARY == 1" in "WebAssemblyOracle.cpp"
+ **/
+
+#include "WebAssemblyOracle.h"
+
+#include "../../../OrthancStone/Sources/Oracle/OracleCommandExceptionMessage.h"
+
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include "../../../OrthancStone/Sources/Oracle/ParseDicomSuccessMessage.h"
+#endif
+
+#include "../../../OrthancStone/Sources/Oracle/GetOrthancImageCommand.h"
+#include "../../../OrthancStone/Sources/Oracle/GetOrthancWebViewerJpegCommand.h"
+#include "../../../OrthancStone/Sources/Oracle/HttpCommand.h"
+#include "../../../OrthancStone/Sources/Oracle/OrthancRestApiCommand.h"
+#include "../../../OrthancStone/Sources/Oracle/ParseDicomFromWadoCommand.h"
+#include "../../../OrthancStone/Sources/Oracle/SleepOracleCommand.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/WebAssemblyViewport.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,399 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1
+#  include "WebAssemblyViewport.h"
+#  include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h"
+#  include "../../../OrthancStone/Sources/Toolbox/GenericToolbox.h"
+#  include "../../../OrthancStone/Sources/Viewport/DefaultViewportInteractor.h"
+#else
+// This is the case when using the WebAssembly side module, and this
+// source file must be compiled within the WebAssembly main module
+#  include <Viewport/WebAssemblyViewport.h>
+#  include <Toolbox/GenericToolbox.h>
+#  include <Scene2DViewport/ViewportController.h>
+#  include <Viewport/DefaultViewportInteractor.h>
+#endif
+
+
+#include <OrthancException.h>
+
+#include <boost/make_shared.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/math/special_functions/round.hpp>
+
+namespace OrthancStone
+{
+  static void ConvertMouseEvent(PointerEvent& target,
+                                const EmscriptenMouseEvent& source,
+                                const ICompositor& compositor)
+  {
+    int x = static_cast<int>(source.targetX);
+    int y = static_cast<int>(source.targetY);
+
+    switch (source.button)
+    {
+      case 0:
+        target.SetMouseButton(MouseButton_Left);
+        break;
+
+      case 1:
+        target.SetMouseButton(MouseButton_Middle);
+        break;
+
+      case 2:
+        target.SetMouseButton(MouseButton_Right);
+        break;
+
+      default:
+        target.SetMouseButton(MouseButton_None);
+        break;
+    }
+      
+    target.AddPosition(compositor.GetPixelCenterCoordinates(x, y));
+    target.SetAltModifier(source.altKey);
+    target.SetControlModifier(source.ctrlKey);
+    target.SetShiftModifier(source.shiftKey);
+  }
+
+
+  class WebAssemblyViewport::WasmLock : public ILock
+  {
+  private:
+    WebAssemblyViewport& that_;
+
+  public:
+    WasmLock(WebAssemblyViewport& that) :
+      that_(that)
+    {
+    }
+
+    virtual bool HasCompositor() const ORTHANC_OVERRIDE
+    {
+      return that_.compositor_.get() != NULL;
+    }
+
+    virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE
+    {
+      if (that_.compositor_.get() == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        return *that_.compositor_;
+      }
+    }
+
+    virtual ViewportController& GetController() ORTHANC_OVERRIDE
+    {
+      assert(that_.controller_);
+      return *that_.controller_;
+    }
+
+    virtual void Invalidate() ORTHANC_OVERRIDE
+    {
+      that_.Invalidate();
+    }
+
+    virtual void RefreshCanvasSize() ORTHANC_OVERRIDE
+    {
+      that_.RefreshCanvasSize();
+    }
+  };
+
+
+  EM_BOOL WebAssemblyViewport::OnRequestAnimationFrame(double time, void *userData)
+  {
+    LOG(TRACE) << __func__;
+    WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData);
+
+    if (that->compositor_.get() != NULL &&
+        that->controller_ /* should always be true */)
+    {
+      that->Paint(*that->compositor_, *that->controller_);
+    }
+      
+    LOG(TRACE) << "Exiting: " << __func__;
+    return true;
+  }
+
+  EM_BOOL WebAssemblyViewport::OnResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData)
+  {
+    LOG(TRACE) << __func__;
+    WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData);
+
+    if (that->compositor_.get() != NULL)
+    {
+      that->RefreshCanvasSize();
+      that->Invalidate();
+    }
+      
+    LOG(TRACE) << "Exiting: " << __func__;
+    return true;
+  }
+
+
+  EM_BOOL WebAssemblyViewport::OnMouseDown(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
+  {
+    WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData);
+
+    LOG(TRACE) << "mouse down: " << that->GetCanvasCssSelector();      
+
+    if (that->compositor_.get() != NULL &&
+        that->interactor_.get() != NULL)
+    {
+      PointerEvent pointer;
+      ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_);
+
+      that->controller_->HandleMousePress(*that->interactor_, pointer,
+                                         that->compositor_->GetCanvasWidth(),
+                                         that->compositor_->GetCanvasHeight());        
+      that->Invalidate();
+    }
+
+    LOG(TRACE) << "Exiting: " << __func__;
+    return true;
+  }
+
+    
+  EM_BOOL WebAssemblyViewport::OnMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
+  {
+    WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData);
+
+    if (that->compositor_.get() != NULL &&
+        that->controller_->HasActiveTracker())
+    {
+      PointerEvent pointer;
+      ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_);
+      if (that->controller_->HandleMouseMove(pointer))
+      {
+        that->Invalidate();
+      }
+    }
+
+    LOG(TRACE) << "Exiting: " << __func__;
+    return true;
+  }
+    
+  EM_BOOL WebAssemblyViewport::OnMouseUp(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
+  {
+    LOG(TRACE) << __func__;
+    WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData);
+
+    if (that->compositor_.get() != NULL)
+    {
+      PointerEvent pointer;
+      ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_);
+      that->controller_->HandleMouseRelease(pointer);
+      that->Invalidate();
+    }
+
+    LOG(TRACE) << "Exiting: " << __func__;
+    return true;
+  }
+
+  void WebAssemblyViewport::Invalidate()
+  {
+    emscripten_request_animation_frame(OnRequestAnimationFrame, reinterpret_cast<void*>(this));
+  }
+
+  void WebAssemblyViewport::FitForPrint()
+  {
+    if (compositor_.get() != NULL &&
+        controller_ /* should always be true */)
+    {
+      RefreshCanvasSize();
+      compositor_->FitContent(controller_->GetScene());
+      OnRequestAnimationFrame(0, reinterpret_cast<void*>(this));  // Mandatory to work with Firefox
+    }
+  }
+
+  void WebAssemblyViewport::AcquireCompositor(ICompositor* compositor /* takes ownership */)
+  {
+    if (compositor == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+    else
+    {
+      compositor_.reset(compositor);
+    }
+  }
+
+#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR == 1
+// everything OK..... we're using the new setting
+#else
+#pragma message("WARNING: DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR is not defined or equal to 0. Stone will use the OLD Emscripten rules for DOM element selection.")
+#endif
+
+  WebAssemblyViewport::WebAssemblyViewport(
+    const std::string& canvasId, bool enableEmscriptenMouseEvents) :
+    canvasId_(canvasId),
+#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR == 1
+    canvasCssSelector_("#" + canvasId),
+#else
+    canvasCssSelector_(canvasId),
+#endif
+    interactor_(new DefaultViewportInteractor),
+    enableEmscriptenMouseEvents_(enableEmscriptenMouseEvents),
+    canvasWidth_(0),
+    canvasHeight_(0)
+  {
+  }
+
+  void WebAssemblyViewport::PostConstructor()
+  {
+    boost::shared_ptr<IViewport> viewport = shared_from_this();
+    controller_.reset(new ViewportController(viewport));
+
+    LOG(INFO) << "Initializing Stone viewport on HTML canvas: " 
+              << canvasId_;
+
+    if (canvasId_.empty() ||
+        canvasId_[0] == '#')
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+        "The canvas identifier must not start with '#'");
+    }
+
+    // Disable right-click on the canvas (i.e. context menu)
+    EM_ASM({
+        document.getElementById(UTF8ToString($0)).oncontextmenu = 
+        function(event)
+        {
+          event.preventDefault();
+        }
+      },
+      canvasId_.c_str()   // $0
+      );
+
+    // It is not possible to monitor the resizing of individual
+    // canvas, so we track the full window of the browser
+    emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW,
+                                   reinterpret_cast<void*>(this),
+                                   false,
+                                   OnResize);
+
+    if (enableEmscriptenMouseEvents_)
+    {
+
+      // if any of this function causes an error in the console, please
+      // make sure you are using the new (as of 1.39.x) version of 
+      // emscripten element lookup rules( pass 
+      // "-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1" to the linker.
+
+      emscripten_set_mousedown_callback(canvasCssSelector_.c_str(),
+                                        reinterpret_cast<void*>(this),
+                                        false,
+                                        OnMouseDown);
+
+      emscripten_set_mousemove_callback(canvasCssSelector_.c_str(),
+                                        reinterpret_cast<void*>(this),
+                                        false,
+                                        OnMouseMove);
+
+      emscripten_set_mouseup_callback(canvasCssSelector_.c_str(),
+                                      reinterpret_cast<void*>(this),
+                                      false,
+                                      OnMouseUp);
+    }
+  }
+
+  WebAssemblyViewport::~WebAssemblyViewport()
+  {
+    emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW,
+                                   reinterpret_cast<void*>(this),
+                                   false,
+                                   NULL);
+
+    if (enableEmscriptenMouseEvents_)
+    {
+
+      emscripten_set_mousedown_callback(canvasCssSelector_.c_str(),
+                                        reinterpret_cast<void*>(this),
+                                        false,
+                                        NULL);
+
+      emscripten_set_mousemove_callback(canvasCssSelector_.c_str(),
+                                        reinterpret_cast<void*>(this),
+                                        false,
+                                        NULL);
+
+      emscripten_set_mouseup_callback(canvasCssSelector_.c_str(),
+                                      reinterpret_cast<void*>(this),
+                                      false,
+                                      NULL);
+    }
+  }
+  
+  IViewport::ILock* WebAssemblyViewport::Lock()
+  {
+    return new WasmLock(*this);
+  }
+  
+  void WebAssemblyViewport::AcquireInteractor(IViewportInteractor* interactor)
+  {
+    if (interactor == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+    else
+    {
+      interactor_.reset(interactor);
+    }
+  }
+
+
+  void WebAssemblyViewport::RefreshCanvasSize()
+  {
+    double w, h;
+    emscripten_get_element_css_size(GetCanvasCssSelector().c_str(), &w, &h);
+
+    /**
+     * Emscripten has the function emscripten_get_element_css_size()
+     * to query the width and height of a named HTML element. I'm
+     * calling this first to get the initial size of the canvas DOM
+     * element, and then call emscripten_set_canvas_size() to
+     * initialize the framebuffer size of the canvas to the same
+     * size as its DOM element.
+     * https://floooh.github.io/2017/02/22/emsc-html.html
+     **/
+    if (w > 0 &&
+        h > 0)
+    {
+      canvasWidth_ = static_cast<unsigned int>(boost::math::iround(w));
+      canvasHeight_ = static_cast<unsigned int>(boost::math::iround(h));
+    }
+    else
+    {
+      canvasWidth_ = 0;
+      canvasHeight_ = 0;
+    }
+
+    emscripten_set_canvas_element_size(GetCanvasCssSelector().c_str(), canvasWidth_, canvasHeight_);
+
+    if (compositor_.get() != NULL)
+    {
+      compositor_->SetCanvasSize(canvasWidth_, canvasHeight_);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/WebAssemblyViewport.h	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,139 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../../OrthancStone/Sources/OrthancStone.h"
+
+#if !defined(ORTHANC_ENABLE_WASM)
+#  error Macro ORTHANC_ENABLE_WASM must be defined
+#endif
+
+#if ORTHANC_ENABLE_WASM != 1
+#  error This file can only be used if targeting WebAssembly
+#endif
+
+#include "../../../OrthancStone/Sources/Viewport/IViewport.h"
+#include "../../../OrthancStone/Sources/Viewport/IViewportInteractor.h"
+
+#include <Compatibility.h>
+
+#include <emscripten.h>
+#include <emscripten/html5.h>
+
+#include <memory>
+#include <string>
+#include <boost/enable_shared_from_this.hpp>
+
+namespace OrthancStone
+{
+  class WebAssemblyViewport : public IViewport,
+                              public boost::enable_shared_from_this<WebAssemblyViewport>
+
+  {
+  private:
+    class WasmLock;
+    
+    std::string                           canvasId_;
+    std::string                           canvasCssSelector_;
+    std::unique_ptr<ICompositor>          compositor_;
+    std::unique_ptr<ViewportController>   controller_;
+    std::unique_ptr<IViewportInteractor>  interactor_;
+    bool                                  enableEmscriptenMouseEvents_;
+    unsigned int                          canvasWidth_;
+    unsigned int                          canvasHeight_;
+
+    static EM_BOOL OnRequestAnimationFrame(double time, void *userData);
+    
+    static EM_BOOL OnResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData);
+
+    static EM_BOOL OnMouseDown(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData);
+    
+    static EM_BOOL OnMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData);
+    
+    static EM_BOOL OnMouseUp(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData);
+
+  protected:
+    void Invalidate();
+    
+    void ClearCompositor()
+    {
+      compositor_.reset();
+    }
+
+    bool HasCompositor() const
+    {
+      return compositor_.get() != NULL;
+    }
+
+    void AcquireCompositor(ICompositor* compositor /* takes ownership */);
+
+    virtual void Paint(ICompositor& compositor,
+                       ViewportController& controller) = 0;
+
+    /**
+    The second argument is temporary and should be deleted once the migration 
+    to interactors is finished. It should be set to "true" for new applications.
+    */
+    WebAssemblyViewport(const std::string& canvasId, 
+                        bool enableEmscriptenMouseEvents);
+
+    void PostConstructor();
+
+    void RefreshCanvasSize();
+
+    unsigned int GetCanvasWidth() const
+    {
+      return canvasWidth_;
+    }
+    
+    unsigned int GetCanvasHeight()
+    {
+      return canvasHeight_;
+    }
+
+  public:
+    virtual ILock* Lock() ORTHANC_OVERRIDE;
+
+    ~WebAssemblyViewport();
+
+    /**
+    This method takes ownership
+    */
+    void AcquireInteractor(IViewportInteractor* interactor);
+
+    const std::string& GetCanvasId() const
+    {
+      return canvasId_;
+    }
+
+    /**
+    emscripten functions requires the css selector for the canvas. This is 
+    different from the canvas id (the syntax is '#mycanvasid')
+    */
+    const std::string& GetCanvasCssSelector() const
+    {
+      return canvasCssSelector_;
+    }
+
+    void FitForPrint();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/WebGLViewport.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,86 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "WebGLViewport.h"
+
+#include "../../../OrthancStone/Sources/StoneException.h"
+#include "../../../OrthancStone/Sources/Scene2D/OpenGLCompositor.h"
+#include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h"
+
+namespace OrthancStone
+{
+  void WebGLViewport::Paint(ICompositor& compositor,
+                            ViewportController& controller)
+  {
+    try
+    {
+      compositor.Refresh(controller.GetScene());
+
+      /**
+       * No need to manually swap the buffer: "Rendered WebGL content
+       * is implicitly presented (displayed to the user) on the canvas
+       * when the event handler that renders with WebGL returns back
+       * to the browser event loop."
+       * https://emscripten.org/docs/api_reference/html5.h.html#webgl-context
+       *
+       * Could call "emscripten_webgl_commit_frame()" if
+       * "explicitSwapControl" option were set to "true".
+       **/
+    }
+    catch (const StoneException& e)
+    {
+      // Ignore problems about the loss of the WebGL context (edge case)
+      if (e.GetErrorCode() == ErrorCode_WebGLContextLost)
+      {
+        return;
+      }
+      else
+      {
+        throw;
+      }
+    }
+  }
+    
+
+  WebGLViewport::WebGLViewport(const std::string& canvasId, bool enableEmscriptenMouseEvents) :
+    WebAssemblyViewport(canvasId,enableEmscriptenMouseEvents),
+    context_(GetCanvasCssSelector())
+  {
+    AcquireCompositor(new OpenGLCompositor(context_));
+  }
+
+  boost::shared_ptr<WebGLViewport> WebGLViewport::Create(
+    const std::string& canvasId, bool enableEmscriptenMouseEvents)
+  {
+    boost::shared_ptr<WebGLViewport> that = boost::shared_ptr<WebGLViewport>(
+        new WebGLViewport(canvasId, enableEmscriptenMouseEvents));
+    
+    that->WebAssemblyViewport::PostConstructor();
+    return that;
+  }
+
+  WebGLViewport::~WebGLViewport()
+  {
+    // Make sure to delete the compositor before its parent "context_" gets
+    // deleted
+    ClearCompositor();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/WebGLViewport.h	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,52 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "WebAssemblyOpenGLContext.h"
+#include "WebAssemblyViewport.h"
+
+namespace OrthancStone
+{
+  class WebGLViewport : public WebAssemblyViewport
+  {
+  private:
+    OpenGL::WebAssemblyOpenGLContext  context_;
+    
+    WebGLViewport(const std::string& canvasId,
+                  bool enableEmscriptenMouseEvents);
+
+  protected:
+    virtual void Paint(ICompositor& compositor,
+                       ViewportController& controller) ORTHANC_OVERRIDE;
+    
+  public:
+    static boost::shared_ptr<WebGLViewport> Create(const std::string& canvasId,
+                                                   bool enableEmscriptenMouseEvents = true);
+
+    virtual ~WebGLViewport();
+
+    bool IsContextLost()
+    {
+      return context_.IsContextLost();
+    } 
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/WebGLViewportsRegistry.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,221 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "WebGLViewportsRegistry.h"
+
+#include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h"
+#include "../../../OrthancStone/Sources/Toolbox/GenericToolbox.h"
+
+#include <OrthancException.h>
+
+#include <boost/make_shared.hpp>
+
+
+static double viewportsTimeout_ = 1000;
+static std::unique_ptr<OrthancStone::WebGLViewportsRegistry>  globalRegistry_;
+
+
+namespace OrthancStone
+{
+  void WebGLViewportsRegistry::LaunchTimer()
+  {
+    timeOutID_ = emscripten_set_timeout(
+      OnTimeoutCallback, 
+      timeoutMS_, 
+      reinterpret_cast<void*>(this));
+  }
+  
+  void WebGLViewportsRegistry::OnTimeout()
+  {
+    for (Viewports::iterator it = viewports_.begin();
+         it != viewports_.end(); 
+         ++it)
+    {
+      if (it->second == NULL ||
+          it->second->IsContextLost())
+      {
+        LOG(INFO) << "WebGL context lost for canvas: " << it->first;
+
+        // Try and duplicate the HTML5 canvas in the DOM
+        EM_ASM({
+            var canvas = document.getElementById(UTF8ToString($0));
+            if (canvas) {
+              var parent = canvas.parentElement;
+              if (parent) {
+                var cloned = canvas.cloneNode(true /* deep copy */);
+                parent.insertBefore(cloned, canvas);
+                parent.removeChild(canvas);
+              }
+            }
+          },
+          it->first.c_str()  // $0 = ID of the canvas
+          );
+
+        // At this point, the old canvas is removed from the DOM and
+        // replaced by a fresh one with the same ID: Recreate the
+        // WebGL context on the new canvas
+        boost::shared_ptr<WebGLViewport> viewport;
+
+        // we need to steal the properties from the old viewport
+        // and set them to the new viewport
+        {
+          std::unique_ptr<IViewport::ILock> lock(it->second->Lock());
+
+          // TODO: remove ViewportController
+          Scene2D* scene = lock->GetController().ReleaseScene();
+          viewport = WebGLViewport::Create(it->first);
+
+          {
+            std::unique_ptr<IViewport::ILock> newLock(viewport->Lock());
+            newLock->GetController().AcquireScene(scene);
+          }
+        }
+
+        // Replace the old WebGL viewport by the new one
+        it->second = viewport;
+
+        // Tag the fresh canvas as needing a repaint
+        {
+          std::unique_ptr<IViewport::ILock> lock(it->second->Lock());
+          lock->Invalidate();
+        }
+      }
+    }
+      
+    LaunchTimer();
+  }
+
+  void WebGLViewportsRegistry::OnTimeoutCallback(void *userData)
+  {
+    // This object dies with the process or tab. 
+    WebGLViewportsRegistry* that = 
+      reinterpret_cast<WebGLViewportsRegistry*>(userData);
+    that->OnTimeout();
+  }
+
+  WebGLViewportsRegistry::WebGLViewportsRegistry(double timeoutMS) :
+    timeoutMS_(timeoutMS),
+    timeOutID_(0)
+  {
+    if (timeoutMS <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }          
+    
+    LaunchTimer();
+  }
+
+  WebGLViewportsRegistry::~WebGLViewportsRegistry()
+  {
+    emscripten_clear_timeout(timeOutID_);
+    Clear();
+  }
+
+  boost::shared_ptr<WebGLViewport> WebGLViewportsRegistry::Add(
+    const std::string& canvasId)
+  {
+    if (viewports_.find(canvasId) != viewports_.end())
+    {
+      LOG(ERROR) << "Canvas was already registered: " << canvasId;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      boost::shared_ptr<WebGLViewport> viewport = 
+        WebGLViewport::Create(canvasId);
+      viewports_[canvasId] = viewport;
+      return viewport;
+    }
+  }
+      
+  void WebGLViewportsRegistry::Remove(const std::string& canvasId)
+  {
+    Viewports::iterator found = viewports_.find(canvasId);
+
+    if (found == viewports_.end())
+    {
+      LOG(ERROR) << "Cannot remove unregistered canvas: " << canvasId;
+    }
+    else
+    {
+      viewports_.erase(found);
+    }
+  }
+  
+  void WebGLViewportsRegistry::Clear()
+  {
+    viewports_.clear();
+  }
+
+  WebGLViewportsRegistry::Accessor::Accessor(WebGLViewportsRegistry& that,
+                                             const std::string& canvasId) :
+    that_(that)
+  {
+    Viewports::iterator viewport = that.viewports_.find(canvasId);
+    if (viewport != that.viewports_.end() &&
+        viewport->second != NULL)
+    {
+      lock_.reset(viewport->second->Lock());
+    }
+  }
+
+  IViewport::ILock& WebGLViewportsRegistry::Accessor::GetViewport() const
+  {
+    if (IsValid())
+    {
+      return *lock_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  void WebGLViewportsRegistry::FinalizeGlobalRegistry()
+  {
+    globalRegistry_.reset();
+  }
+
+
+  void WebGLViewportsRegistry::SetGlobalRegistryTimeout(double timeout)
+  {
+    if (globalRegistry_.get())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      viewportsTimeout_ = timeout;
+    }
+  }
+
+
+  WebGLViewportsRegistry& WebGLViewportsRegistry::GetGlobalRegistry()
+  {
+    if (globalRegistry_.get() == NULL)
+    {
+      globalRegistry_.reset(new WebGLViewportsRegistry(viewportsTimeout_));
+    }
+
+    return *globalRegistry_;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Platforms/WebAssembly/WebGLViewportsRegistry.h	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,91 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "WebGLViewport.h"
+
+#include <boost/enable_shared_from_this.hpp>
+
+namespace OrthancStone
+{
+  /**
+   * This singleton class must be used if many WebGL viewports are
+   * created by the higher-level application, implying possible loss
+   * of WebGL contexts. The object will run an infinite update loop
+   * that checks whether all the WebGL context are still valid (not
+   * lost). If some WebGL context is lost, it is automatically
+   * reinitialized by created a fresh HTML5 canvas.
+   **/  
+  class WebGLViewportsRegistry : public boost::noncopyable,
+    public boost::enable_shared_from_this<WebGLViewportsRegistry>
+  {
+  private:
+    typedef std::map<std::string, boost::shared_ptr<WebGLViewport> >  Viewports;
+
+    double     timeoutMS_;
+    Viewports  viewports_;
+    long       timeOutID_;
+
+    void LaunchTimer();
+
+    void OnTimeout();
+
+    static void OnTimeoutCallback(void *userData);
+    
+  public:
+    explicit WebGLViewportsRegistry(double timeoutMS /* in milliseconds */);
+    
+    ~WebGLViewportsRegistry();
+
+    boost::shared_ptr<WebGLViewport> Add(const std::string& canvasId);
+
+    void Remove(const std::string& canvasId);
+
+    void Clear();
+
+    class Accessor : public boost::noncopyable
+    {
+    private:
+      WebGLViewportsRegistry&            that_;
+      std::unique_ptr<IViewport::ILock>  lock_;
+
+    public:
+      Accessor(WebGLViewportsRegistry& that,
+               const std::string& canvasId);
+
+      bool IsValid() const
+      {
+        return lock_.get() != NULL;
+      }
+
+      IViewport::ILock& GetViewport() const;
+    };
+
+
+
+    static void FinalizeGlobalRegistry();
+  
+    static void SetGlobalRegistryTimeout(double timeout);
+
+    static WebGLViewportsRegistry& GetGlobalRegistry();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Graveyard/LoaderCache.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,260 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "LoaderCache.h"
+
+#include "../StoneException.h"
+#include "OrthancSeriesVolumeProgressiveLoader.h"
+#include "OrthancMultiframeVolumeLoader.h"
+#include "DicomStructureSetLoader.h"
+#include "../Toolbox/GenericToolbox.h"
+
+#include "../Loaders/ILoadersContext.h"
+
+#if ORTHANC_ENABLE_WASM == 1
+# include <unistd.h>
+# include "../Oracle/WebAssemblyOracle.h"
+#else
+# include "../Oracle/ThreadedOracle.h"
+#endif
+
+#include "../Volumes/DicomVolumeImage.h"
+#include "../Volumes/DicomVolumeImageMPRSlicer.h"
+
+#include <OrthancException.h>
+#include <Toolbox.h>
+
+namespace OrthancStone
+{
+  LoaderCache::LoaderCache(OrthancStone::ILoadersContext& loadersContext, bool useCtProgressiveQuality)
+    : loadersContext_(loadersContext)
+    , useCtProgressiveQuality_(useCtProgressiveQuality)
+
+  {
+
+  }
+
+  boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> 
+    LoaderCache::GetSeriesVolumeProgressiveLoader(std::string seriesUuid)
+  {
+    try
+    {
+      // normalize keys a little
+      GenericToolbox::NormalizeUuid(seriesUuid);
+
+      // find in cache
+      if (seriesVolumeProgressiveLoaders_.find(seriesUuid) == seriesVolumeProgressiveLoaders_.end())
+      {
+        std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock());
+
+        boost::shared_ptr<OrthancStone::DicomVolumeImage> volumeImage(new OrthancStone::DicomVolumeImage);
+        boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> loader;
+      
+        // true means "use progressive quality"
+        // false means "load high quality slices only"
+        loader = OrthancSeriesVolumeProgressiveLoader::Create(loadersContext_, volumeImage, useCtProgressiveQuality_);
+        loader->LoadSeries(seriesUuid);
+        seriesVolumeProgressiveLoaders_[seriesUuid] = loader;
+      }
+      else
+      {
+//        LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : returning cached loader for seriesUUid = " << seriesUuid;
+      }
+      return seriesVolumeProgressiveLoaders_[seriesUuid];
+    }
+    catch (const Orthanc::OrthancException& e)
+    {
+      if (e.HasDetails())
+      {
+        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails();
+      }
+      else
+      {
+        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What();
+      }
+      throw;
+    }
+    catch (const std::exception& e)
+    {
+      LOG(ERROR) << "std::exception in LoaderCache: " << e.what();
+      throw;
+    }
+    catch (...)
+    {
+      LOG(ERROR) << "Unknown exception in LoaderCache";
+      throw;
+    }
+  }
+
+  boost::shared_ptr<OrthancMultiframeVolumeLoader> LoaderCache::GetMultiframeVolumeLoader(std::string instanceUuid)
+  {
+    // normalize keys a little
+    GenericToolbox::NormalizeUuid(instanceUuid);
+
+    // if the loader is not available, let's trigger its creation
+    if(multiframeVolumeLoaders_.find(instanceUuid) == multiframeVolumeLoaders_.end())
+    {
+      GetMultiframeDicomVolumeImageMPRSlicer(instanceUuid);
+    }
+    ORTHANC_ASSERT(multiframeVolumeLoaders_.find(instanceUuid) != multiframeVolumeLoaders_.end());
+
+    return multiframeVolumeLoaders_[instanceUuid];
+  }
+
+  boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> LoaderCache::GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid)
+  {
+    try
+    {
+      // normalize keys a little
+      GenericToolbox::NormalizeUuid(instanceUuid);
+
+      // find in cache
+      if (dicomVolumeImageMPRSlicers_.find(instanceUuid) == dicomVolumeImageMPRSlicers_.end())
+      {
+        std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock());
+        boost::shared_ptr<OrthancStone::DicomVolumeImage> volumeImage(new OrthancStone::DicomVolumeImage);
+        boost::shared_ptr<OrthancMultiframeVolumeLoader> loader;
+        {
+          loader = OrthancMultiframeVolumeLoader::Create(loadersContext_, volumeImage);
+          loader->LoadInstance(instanceUuid);
+        }
+        multiframeVolumeLoaders_[instanceUuid] = loader;
+        boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> mprSlicer(new OrthancStone::DicomVolumeImageMPRSlicer(volumeImage));
+        dicomVolumeImageMPRSlicers_[instanceUuid] = mprSlicer;
+      }
+      return dicomVolumeImageMPRSlicers_[instanceUuid];
+    }
+    catch (const Orthanc::OrthancException& e)
+    {
+      if (e.HasDetails())
+      {
+        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails();
+      }
+      else
+      {
+        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What();
+      }
+      throw;
+    }
+    catch (const std::exception& e)
+    {
+      LOG(ERROR) << "std::exception in LoaderCache: " << e.what();
+      throw;
+    }
+    catch (...)
+    {
+      LOG(ERROR) << "Unknown exception in LoaderCache";
+      throw;
+    }
+  }
+  
+  std::string LoaderCache::BuildDicomStructureSetLoaderKey(
+    const std::string& instanceUuid,
+    const std::string& uniqueKey)
+  {
+    return instanceUuid + "_" + uniqueKey;
+  }
+
+  boost::shared_ptr<DicomStructureSetLoader> LoaderCache::GetDicomStructureSetLoader(
+      std::string inInstanceUuid, 
+      const std::vector<std::string>& initiallyVisibleStructures,
+      const std::string& uniqueKey)
+  {
+    try
+    {
+      // normalize keys a little
+      GenericToolbox::NormalizeUuid(inInstanceUuid);
+
+      std::string entryKey = BuildDicomStructureSetLoaderKey(inInstanceUuid, uniqueKey);
+
+      // find in cache
+      if (dicomStructureSetLoaders_.find(entryKey) == dicomStructureSetLoaders_.end())
+      {
+        std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock());
+
+        boost::shared_ptr<DicomStructureSetLoader> loader;
+        {
+          loader = DicomStructureSetLoader::Create(loadersContext_);
+          loader->LoadInstance(inInstanceUuid, initiallyVisibleStructures);
+        }
+        dicomStructureSetLoaders_[entryKey] = loader;
+      }
+      return dicomStructureSetLoaders_[entryKey];
+    }
+    catch (const Orthanc::OrthancException& e)
+    {
+      if (e.HasDetails())
+      {
+        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails();
+      }
+      else
+      {
+        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What();
+      }
+      throw;
+    }
+    catch (const std::exception& e)
+    {
+      LOG(ERROR) << "std::exception in LoaderCache: " << e.what();
+      throw;
+    }
+    catch (...)
+    {
+      LOG(ERROR) << "Unknown exception in LoaderCache";
+      throw;
+    }
+  }
+
+  void LoaderCache::ClearCache()
+  {
+    std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock());
+    
+#ifndef NDEBUG
+    // ISO way of checking for debug builds
+    DebugDisplayObjRefCounts();
+#endif
+    seriesVolumeProgressiveLoaders_.clear();
+    multiframeVolumeLoaders_.clear();
+    dicomVolumeImageMPRSlicers_.clear();
+    dicomStructureSetLoaders_.clear();
+
+  }
+
+  template<typename T> void DebugDisplayObjRefCountsInMap(
+    const std::string& name, const std::map<std::string, boost::shared_ptr<T> >& myMap)
+  {
+    LOG(TRACE) << "Map \"" << name << "\" ref counts:";
+    size_t i = 0;
+    for (typename std::map<std::string, boost::shared_ptr<T> >::const_iterator 
+           it = myMap.begin(); it != myMap.end(); ++it)
+    {
+      LOG(TRACE) << "  element #" << i << ": ref count = " << it->second.use_count();
+      i++;
+    }
+  }
+
+  void LoaderCache::DebugDisplayObjRefCounts()
+  {
+    DebugDisplayObjRefCountsInMap("seriesVolumeProgressiveLoaders_", seriesVolumeProgressiveLoaders_);
+    DebugDisplayObjRefCountsInMap("multiframeVolumeLoaders_", multiframeVolumeLoaders_);
+    DebugDisplayObjRefCountsInMap("dicomVolumeImageMPRSlicers_", dicomVolumeImageMPRSlicers_);
+    DebugDisplayObjRefCountsInMap("dicomStructureSetLoaders_", dicomStructureSetLoaders_);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Graveyard/LoaderCache.h	Fri Oct 23 13:15:03 2020 +0200
@@ -0,0 +1,102 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "../Volumes/DicomVolumeImageMPRSlicer.h"
+#include "OrthancSeriesVolumeProgressiveLoader.h"
+#include "OrthancMultiframeVolumeLoader.h"
+#include "DicomStructureSetLoader.h"
+
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace OrthancStone
+{
+  class ILoadersContext;
+}
+
+namespace OrthancStone
+{
+  class LoaderCache
+  {
+  public:
+
+    virtual ~LoaderCache() {}
+
+    /**
+    By default, the CT loader in loader cache will only download the highest quality slices.
+    If you pass true for useCtProgressiveQuality, jpeg (50/100 quality), then jpeg (90/100 quality) 
+    then eventually uncompressed 16-bit images will be loaded. 
+    */
+    LoaderCache(OrthancStone::ILoadersContext& loadersContext,
+                bool useCtProgressiveQuality);
+
+    boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader>
+      GetSeriesVolumeProgressiveLoader      (std::string seriesUuid);
+    
+    boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer>
+      GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid);
+
+    boost::shared_ptr<OrthancMultiframeVolumeLoader>
+      GetMultiframeVolumeLoader(std::string instanceUuid);
+
+    /**
+    The DicomStructureSetLoader instances are stored in a map and indexed
+    by a key built from instanceUuid and uniqueKey.
+
+    If instanceUuid and uniqueKey correspond to an already existing loader, it is returned.
+
+    Please note that initiallyVisibleStructures is only used if the call results in the creation
+    of a new loader. In that case, the value is passed to the constructor.
+    */
+    boost::shared_ptr<DicomStructureSetLoader>
+      GetDicomStructureSetLoader(
+        std::string instanceUuid,
+        const std::vector<std::string>& initiallyVisibleStructures,
+        const std::string& uniqueKey = "");
+
+    std::string BuildDicomStructureSetLoaderKey(
+        const std::string& instanceUuid,
+        const std::string& uniqueKey = "");
+
+    void ClearCache();
+
+  protected:
+    
+    void DebugDisplayObjRefCounts();
+
+    OrthancStone::ILoadersContext& loadersContext_;
+    bool                           useCtProgressiveQuality_;
+
+    std::map<std::string, boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> >
+      seriesVolumeProgressiveLoaders_;
+    std::map<std::string, boost::shared_ptr<OrthancMultiframeVolumeLoader> >
+      multiframeVolumeLoaders_;
+    std::map<std::string, boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> >
+      dicomVolumeImageMPRSlicers_;
+    std::map<std::string, boost::shared_ptr<DicomStructureSetLoader> >
+      dicomStructureSetLoaders_;
+  };
+}
+
--- a/Applications/Samples/Sdl/CMakeLists.txt	Thu Oct 22 18:39:03 2020 +0200
+++ b/Applications/Samples/Sdl/CMakeLists.txt	Fri Oct 23 13:15:03 2020 +0200
@@ -21,7 +21,7 @@
 
 project(OrthancStone)
 
-include(${CMAKE_SOURCE_DIR}/../../../OrthancStone/Resources/CMake/OrthancStoneParameters.cmake)
+include(${CMAKE_SOURCE_DIR}/../../Platforms/Sdl/OrthancStoneSdlParameters.cmake)
 
 if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
   set(ORTHANC_BOOST_COMPONENTS program_options)
@@ -43,7 +43,7 @@
 set(ENABLE_SDL ON)
 SET(ENABLE_PUGIXML ON)   # To test compilation of OsiriX annotations
 
-include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/../../Platforms/Sdl/OrthancStoneSdlConfiguration.cmake)
 include(${CMAKE_SOURCE_DIR}/Utilities.cmake)
 
 if (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
--- a/Applications/Samples/Sdl/RtViewer/RtViewerSdl.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ b/Applications/Samples/Sdl/RtViewer/RtViewerSdl.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -27,9 +27,9 @@
 // Stone of Orthanc includes
 #include "../../../../OrthancStone/Sources/Loaders/GenericLoadersContext.h"
 #include "../../../../OrthancStone/Sources/OpenGL/OpenGLIncludes.h"
-#include "../../../../OrthancStone/Sources/OpenGL/SdlOpenGLContext.h"
 #include "../../../../OrthancStone/Sources/StoneException.h"
 #include "../../../../OrthancStone/Sources/StoneInitialization.h"
+#include "../../../Platforms/Sdl/SdlOpenGLContext.h"
 
 // Orthanc (a.o. for screenshot capture)
 #include <Compatibility.h>  // For std::unique_ptr<>
@@ -451,6 +451,7 @@
   using namespace OrthancStone;
 
   StoneInitialize();
+  OrthancStone::SdlWindow::GlobalInitialize();
 
   try
   {
@@ -462,7 +463,8 @@
   {
     LOG(ERROR) << "EXCEPTION: " << e.What();
   }
-
+  
+  OrthancStone::SdlWindow::GlobalFinalize();
   StoneFinalize();
 
   return 0;
--- a/Applications/Samples/Sdl/SdlHelpers.h	Thu Oct 22 18:39:03 2020 +0200
+++ b/Applications/Samples/Sdl/SdlHelpers.h	Fri Oct 23 13:15:03 2020 +0200
@@ -25,7 +25,7 @@
 # error This file cannot be used if ORTHANC_ENABLE_SDL != 1
 #endif
 
-#include "../../../OrthancStone/Sources/Viewport/SdlViewport.h"
+#include "../../Platforms/Sdl/SdlViewport.h"
 
 #include <boost/shared_ptr.hpp>
 
--- a/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ b/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -28,7 +28,7 @@
 #include "../../../../OrthancStone/Sources/StoneException.h"
 #include "../../../../OrthancStone/Sources/StoneInitialization.h"
 #include "../../../../OrthancStone/Sources/Viewport/DefaultViewportInteractor.h"
-#include "../../../../OrthancStone/Sources/Viewport/SdlViewport.h"
+#include "../../../Platforms/Sdl/SdlViewport.h"
 
 #include <Compatibility.h>  // For std::unique_ptr<>
 #include <OrthancException.h>
@@ -110,7 +110,8 @@
   try
   {
     OrthancStone::StoneInitialize();
-
+    OrthancStone::SdlWindow::GlobalInitialize();
+    
     ProcessOptions(argc, argv);
 
     //Orthanc::Logging::EnableInfoLevel(true);
@@ -252,6 +253,7 @@
       }
     }
 
+    OrthancStone::SdlWindow::GlobalFinalize();
     OrthancStone::StoneFinalize();
     return 0;
   }
--- a/Applications/Samples/WebAssembly/CMakeLists.txt	Thu Oct 22 18:39:03 2020 +0200
+++ b/Applications/Samples/WebAssembly/CMakeLists.txt	Fri Oct 23 13:15:03 2020 +0200
@@ -49,17 +49,16 @@
 # ---------------------------------------------------------------
 set(ALLOW_DOWNLOADS ON)
 
-include(${CMAKE_SOURCE_DIR}/../../../OrthancStone/Resources/CMake/OrthancStoneParameters.cmake)
+include(${CMAKE_SOURCE_DIR}/../../Platforms/WebAssembly/OrthancStoneWebAssemblyParameters.cmake)
 
 SET(ENABLE_DCMTK OFF)  # Not necessary
 SET(ENABLE_GOOGLE_TEST OFF)
 SET(ENABLE_LOCALE ON)  # Necessary for text rendering
-SET(ENABLE_WASM ON)
 SET(ORTHANC_SANDBOXED ON)
 
 # this will set up the build system for Stone of Orthanc and will
 # populate the ORTHANC_STONE_SOURCES CMake variable
-include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/../../Platforms/WebAssembly/OrthancStoneWebAssemblyConfiguration.cmake)
 
 
 # We embed a font to be used for on-screen overlays
--- a/Applications/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ b/Applications/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -23,11 +23,10 @@
 #include "../../Common/SampleHelpers.h"
 
 // Stone of Orthanc includes
-#include "../../../../OrthancStone/Sources/Loaders/WebAssemblyLoadersContext.h"
 #include "../../../../OrthancStone/Sources/StoneException.h"
 #include "../../../../OrthancStone/Sources/StoneInitialization.h"
-#include "../../../../OrthancStone/Sources/Viewport/WebGLViewport.h"
-//#include "../../../../OrthancStone/Sources/OpenGL/WebAssemblyOpenGLContext.h"
+#include "../../../Platforms/WebAssembly/WebAssemblyLoadersContext.h"
+#include "../../../Platforms/WebAssembly/WebGLViewport.h"
 
 #include <Toolbox.h>
 
--- a/Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ b/Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -21,9 +21,11 @@
 
 #include "SingleFrameViewerApplication.h"
 
-#include "../../../../OrthancStone/Sources/Loaders/WebAssemblyLoadersContext.h"
 #include "../../../../OrthancStone/Sources/StoneException.h"
 #include "../../../../OrthancStone/Sources/StoneInitialization.h"
+#include "../../../Platforms/WebAssembly/WebGLViewport.h"
+#include "../../../Platforms/WebAssembly/WebAssemblyLoadersContext.h"
+#include "../../../Platforms/WebAssembly/WebGLViewportsRegistry.h"
 
 #include <Compatibility.h>  // For std::unique_ptr<>
 #include <Toolbox.h>
@@ -114,12 +116,13 @@
                                         "Only one single viewport is available for this application");
       }
 
-      boost::shared_ptr<OrthancStone::WebGLViewport> viewport(OrthancStone::GetWebGLViewportsRegistry().Add(canvasId));
+      boost::shared_ptr<OrthancStone::WebGLViewport> viewport(
+        OrthancStone::WebGLViewportsRegistry::GetGlobalRegistry().Add(canvasId));
       application_ = OrthancStone::Application::Create(*context_, viewport);
 
       {
         OrthancStone::WebGLViewportsRegistry::Accessor accessor(
-          OrthancStone::GetWebGLViewportsRegistry(), canvasId);
+          OrthancStone::WebGLViewportsRegistry::GetGlobalRegistry(), canvasId);
 
         if (accessor.IsValid())
         {
--- a/Applications/StoneWebViewer/WebAssembly/CMakeLists.txt	Thu Oct 22 18:39:03 2020 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/CMakeLists.txt	Fri Oct 23 13:15:03 2020 +0200
@@ -49,23 +49,23 @@
 # ---------------------------------------------------------------
 set(ALLOW_DOWNLOADS ON)
 
-include(${CMAKE_SOURCE_DIR}/../../../OrthancStone/Resources/CMake/OrthancStoneParameters.cmake)
+include(${CMAKE_SOURCE_DIR}/../../Platforms/WebAssembly/OrthancStoneWebAssemblyParameters.cmake)
 
 SET(ENABLE_DCMTK ON)
 SET(ENABLE_DCMTK_NETWORKING OFF)
 SET(ENABLE_DCMTK_TRANSCODING OFF)
 SET(ENABLE_GOOGLE_TEST OFF)
 SET(ENABLE_LOCALE ON)  # Necessary for text rendering
-SET(ENABLE_WASM ON)
 SET(ENABLE_PUGIXML ON) # Necessary for OsiriX annotations
 SET(ORTHANC_SANDBOXED ON)
 
 # this will set up the build system for Stone of Orthanc and will
 # populate the ORTHANC_STONE_SOURCES CMake variable
-include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/../../Platforms/WebAssembly/OrthancStoneWebAssemblyConfiguration.cmake)
 
 include_directories(
   ${CMAKE_SOURCE_DIR}/../../../OrthancStone/Sources/
+  ${CMAKE_SOURCE_DIR}/../../Platforms/WebAssembly/
   )
 
 if (CMAKE_BUILD_TYPE MATCHES Debug)
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -66,7 +66,6 @@
 #include <Loaders/DicomResourcesLoader.h>
 #include <Loaders/SeriesMetadataLoader.h>
 #include <Loaders/SeriesThumbnailsLoader.h>
-#include <Loaders/WebAssemblyLoadersContext.h>
 #include <Messages/ObserverBase.h>
 #include <Oracle/ParseDicomFromWadoCommand.h>
 #include <Oracle/ParseDicomSuccessMessage.h>
@@ -79,8 +78,12 @@
 #include <Toolbox/GeometryToolbox.h>
 #include <Toolbox/SortedFrames.h>
 #include <Viewport/DefaultViewportInteractor.h>
-#include <Viewport/WebAssemblyCairoViewport.h>
-#include <Viewport/WebGLViewport.h>
+
+// WebAssembly includes
+#include <WebAssemblyCairoViewport.h>
+#include <WebAssemblyLoadersContext.h>
+#include <WebGLViewport.h>
+
 
 #include <boost/make_shared.hpp>
 #include <stdio.h>
--- a/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake	Thu Oct 22 18:39:03 2020 +0200
+++ b/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Oct 23 13:15:03 2020 +0200
@@ -56,45 +56,11 @@
     message(FATAL_ERROR "Cannot enable curl in sandboxed environments")
   endif()
 
-  if (ENABLE_SDL)
-    message(FATAL_ERROR "Cannot enable SDL in sandboxed environments")
-  endif()
-
   if (ENABLE_SSL)
     message(FATAL_ERROR "Cannot enable SSL in sandboxed environments")
   endif()
 endif()
 
-if (ENABLE_OPENGL)
-  if (NOT ENABLE_SDL AND NOT ENABLE_WASM)
-    message(FATAL_ERROR "Cannot enable OpenGL if WebAssembly and SDL are both disabled")
-  endif()
-endif()
-
-if (ENABLE_WASM)
-  if (NOT ORTHANC_SANDBOXED)
-    message(FATAL_ERROR "WebAssembly target must me configured as sandboxed")
-  endif()
-
-  if (NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
-    message(FATAL_ERROR "WebAssembly target requires the emscripten compiler")    
-  endif()
-
-  set(ENABLE_THREADS OFF)
-  set(ENABLE_WEB_CLIENT OFF)
-  add_definitions(-DORTHANC_ENABLE_WASM=1)
-else()
-  if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten" OR
-      CMAKE_SYSTEM_NAME STREQUAL "PNaCl" OR
-      CMAKE_SYSTEM_NAME STREQUAL "NaCl32" OR
-      CMAKE_SYSTEM_NAME STREQUAL "NaCl64")
-    message(FATAL_ERROR "Trying to use a Web compiler for a native build")
-  endif()
-
-  set(ENABLE_THREADS ON)
-  add_definitions(-DORTHANC_ENABLE_WASM=0)
-endif()
-  
 
 #####################################################################
 ## Configure mandatory third-party components
@@ -118,21 +84,6 @@
 endif()
 
 
-if(ENABLE_SDL)
-  message("SDL is enabled")
-  include(${CMAKE_CURRENT_LIST_DIR}/SdlConfiguration.cmake)
-  add_definitions(
-    -DORTHANC_ENABLE_SDL=1
-    )
-else()
-  message("SDL is disabled")
-  unset(USE_SYSTEM_SDL CACHE)
-  add_definitions(
-    -DORTHANC_ENABLE_SDL=0
-    )
-endif()
-
-
 if (ENABLE_THREADS)
   add_definitions(-DORTHANC_ENABLE_THREADS=1)
 else()
@@ -200,13 +151,6 @@
 ## System-specific patches
 #####################################################################
 
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows" AND
-    NOT MSVC AND
-    ENABLE_SDL)
-  # This is necessary when compiling EXE for Windows using MinGW
-  link_libraries(mingw32)
-endif()
-
 if (ORTHANC_SANDBOXED)
   # Remove functions not suitable for a sandboxed environment
   list(REMOVE_ITEM ORTHANC_CORE_SOURCES
@@ -222,23 +166,6 @@
 ## All the source files required to build Stone of Orthanc
 #####################################################################
 
-if (ENABLE_SDL)
-  list(APPEND ORTHANC_STONE_SOURCES
-    ${ORTHANC_STONE_ROOT}/Sources/Viewport/SdlWindow.cpp
-    ${ORTHANC_STONE_ROOT}/Sources/Viewport/SdlWindow.h
-    )
-
-  if (ENABLE_OPENGL)
-    list(APPEND ORTHANC_STONE_SOURCES
-      ${ORTHANC_STONE_ROOT}/Sources/OpenGL/SdlOpenGLContext.cpp
-      ${ORTHANC_STONE_ROOT}/Sources/OpenGL/SdlOpenGLContext.h
-      ${ORTHANC_STONE_ROOT}/Sources/Viewport/SdlViewport.cpp
-      ${ORTHANC_STONE_ROOT}/Sources/Viewport/SdlViewport.h
-      )
-  endif()
-endif()
-
-
 if (ENABLE_DCMTK)
   list(APPEND ORTHANC_STONE_SOURCES
     ${ORTHANC_STONE_ROOT}/Sources/Oracle/ParseDicomSuccessMessage.cpp
@@ -259,17 +186,6 @@
 endif()
 
 
-if (ENABLE_WASM)
-  list(APPEND ORTHANC_STONE_SOURCES
-    ${ORTHANC_STONE_ROOT}/Sources/Loaders/WebAssemblyLoadersContext.cpp
-    ${ORTHANC_STONE_ROOT}/Sources/Oracle/WebAssemblyOracle.cpp
-    ${ORTHANC_STONE_ROOT}/Sources/Viewport/WebAssemblyCairoViewport.cpp
-    ${ORTHANC_STONE_ROOT}/Sources/Viewport/WebAssemblyViewport.cpp
-    ${ORTHANC_STONE_ROOT}/Sources/Viewport/WebAssemblyViewport.h
-    )
-endif()
-
-
 if (ENABLE_PUGIXML)
   list(APPEND ORTHANC_STONE_SOURCES
     ${ORTHANC_STONE_ROOT}/Sources/Toolbox/OsiriX/AngleAnnotation.cpp
@@ -310,8 +226,6 @@
   ${ORTHANC_STONE_ROOT}/Sources/Loaders/IFetchingItemsSorter.h
   ${ORTHANC_STONE_ROOT}/Sources/Loaders/IFetchingStrategy.h
   ${ORTHANC_STONE_ROOT}/Sources/Loaders/LoadedDicomResources.cpp
-  ${ORTHANC_STONE_ROOT}/Sources/Loaders/LoaderCache.cpp
-  ${ORTHANC_STONE_ROOT}/Sources/Loaders/LoaderCache.h
   ${ORTHANC_STONE_ROOT}/Sources/Loaders/LoaderStateMachine.cpp
   ${ORTHANC_STONE_ROOT}/Sources/Loaders/LoaderStateMachine.h
   ${ORTHANC_STONE_ROOT}/Sources/Loaders/OrthancMultiframeVolumeLoader.cpp
@@ -533,8 +447,6 @@
   ${PIXMAN_SOURCES}
 
   # Optional components
-  ${SDL_SOURCES}
-  ${QT_SOURCES}
   ${GLEW_SOURCES}
   )
 
@@ -578,16 +490,8 @@
     ${ORTHANC_STONE_ROOT}/Sources/Scene2D/Internals/OpenGLTextureProgram.cpp
     ${ORTHANC_STONE_ROOT}/Sources/Scene2D/Internals/OpenGLTextureProgram.h
     )
+endif()
 
-  if (ENABLE_WASM)
-    list(APPEND ORTHANC_STONE_SOURCES
-      ${ORTHANC_STONE_ROOT}/Sources/OpenGL/WebAssemblyOpenGLContext.cpp
-      ${ORTHANC_STONE_ROOT}/Sources/OpenGL/WebAssemblyOpenGLContext.h
-      ${ORTHANC_STONE_ROOT}/Sources/Viewport/WebGLViewport.cpp
-      ${ORTHANC_STONE_ROOT}/Sources/Viewport/WebGLViewportsRegistry.cpp
-      )
-  endif()
-endif()
 
 ##
 ## TEST - Automatically add all ".h" headers to the list of sources
--- a/OrthancStone/Resources/CMake/OrthancStoneParameters.cmake	Thu Oct 22 18:39:03 2020 +0200
+++ b/OrthancStone/Resources/CMake/OrthancStoneParameters.cmake	Fri Oct 23 13:15:03 2020 +0200
@@ -74,12 +74,13 @@
 ## CMake parameters tunable by the user
 #####################################################################
 
+set(ENABLE_OPENGL ON CACHE BOOL "Enable support of OpenGL")
+
 # Advanced parameters to fine-tune linking against system libraries
 set(USE_SYSTEM_CAIRO ON CACHE BOOL "Use the system version of Cairo")
 set(USE_SYSTEM_FREETYPE ON CACHE BOOL "Use the system version of Freetype")
 set(USE_SYSTEM_GLEW ON CACHE BOOL "Use the system version of glew (for Windows only)")
 set(USE_SYSTEM_PIXMAN ON CACHE BOOL "Use the system version of Pixman")
-set(USE_SYSTEM_SDL ON CACHE BOOL "Use the system version of SDL2")
 
 
 
@@ -88,5 +89,4 @@
 ## the Stone of Orthanc
 #####################################################################
 
-set(ENABLE_OPENGL ON CACHE BOOL "Enable support of OpenGL")
-set(ENABLE_WASM OFF CACHE INTERNAL "Enable support of WebAssembly")
+set(ENABLE_THREADS ON CACHE INTERNAL "Enable threading support (must be false for WebAssembly)")
--- a/OrthancStone/Resources/CMake/SdlConfiguration.cmake	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,224 +0,0 @@
-# Stone of Orthanc
-# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
-# Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2020 Osimis S.A., Belgium
-#
-# This program is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Affero General Public License
-# as published by the Free Software Foundation, either version 3 of
-# the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Affero General Public License for more details.
-# 
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-
-if (STATIC_BUILD OR NOT USE_SYSTEM_SDL)
-  SET(SDL_SOURCES_DIR ${CMAKE_BINARY_DIR}/SDL2-2.0.4)
-  SET(SDL_URL "http://orthanc.osimis.io/ThirdPartyDownloads/SDL2-2.0.4.tar.gz")
-  SET(SDL_MD5 "44fc4a023349933e7f5d7a582f7b886e")
-  DownloadPackage(${SDL_MD5} ${SDL_URL} "${SDL_SOURCES_DIR}")
-
-  include_directories(${SDL_SOURCES_DIR}/include)
-
-  set(TMP "${SDL_SOURCES_DIR}/include/SDL_config_premake.h")
-  if (NOT EXISTS "${TMP}")
-    file(WRITE "${TMP}" "
-#include \"SDL_platform.h\"
-#define HAVE_STDARG_H 1
-#define HAVE_STDDEF_H 1
-#define HAVE_STDINT_H 1
-")
-  endif()
-
-  # General source files
-  file(GLOB SDL_SOURCES
-    ${SDL_SOURCES_DIR}/src/*.c
-    ${SDL_SOURCES_DIR}/src/atomic/*.c
-    ${SDL_SOURCES_DIR}/src/audio/*.c
-    ${SDL_SOURCES_DIR}/src/cpuinfo/*.c
-    ${SDL_SOURCES_DIR}/src/dynapi/*.c
-    ${SDL_SOURCES_DIR}/src/events/*.c
-    ${SDL_SOURCES_DIR}/src/file/*.c
-    ${SDL_SOURCES_DIR}/src/haptic/*.c
-    ${SDL_SOURCES_DIR}/src/joystick/*.c
-    ${SDL_SOURCES_DIR}/src/libm/*.c
-    ${SDL_SOURCES_DIR}/src/power/*.c
-    ${SDL_SOURCES_DIR}/src/render/*.c
-    ${SDL_SOURCES_DIR}/src/stdlib/*.c
-    ${SDL_SOURCES_DIR}/src/thread/*.c
-    ${SDL_SOURCES_DIR}/src/timer/*.c
-    ${SDL_SOURCES_DIR}/src/video/*.c
-
-    ${SDL_SOURCES_DIR}/src/loadso/dummy/*.c
-    #${SDL_SOURCES_DIR}/src/timer/dummy/*.c
-    ${SDL_SOURCES_DIR}/src/audio/dummy/*.c
-    ${SDL_SOURCES_DIR}/src/filesystem/dummy/*.c
-    ${SDL_SOURCES_DIR}/src/haptic/dummy/*.c
-    ${SDL_SOURCES_DIR}/src/joystick/dummy/*.c
-    #${SDL_SOURCES_DIR}/src/main/dummy/*.c
-    ${SDL_SOURCES_DIR}/src/video/dummy/*.c
-    )
-
-  add_definitions(
-    -DUSING_PREMAKE_CONFIG_H=1
-
-    -DSDL_AUDIO_DISABLED=1
-    -DSDL_AUDIO_DRIVER_DUMMY=1
-    -DSDL_FILESYSTEM_DISABLED=1
-    -DSDL_FILESYSTEM_DUMMY=1
-    -DSDL_FILE_DISABLED=1
-    -DSDL_HAPTIC_DISABLED=1
-    -DSDL_JOYSTICK_DISABLED=1
-
-    #-DSDL_THREADS_DISABLED=1
-    )
-
-  if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
-    file(GLOB TMP
-      ${SDL_SOURCES_DIR}/src/core/linux/*.c
-      ${SDL_SOURCES_DIR}/src/loadso/dlopen/*.c
-      ${SDL_SOURCES_DIR}/src/render/software/*.c
-      ${SDL_SOURCES_DIR}/src/thread/pthread/*.c
-      ${SDL_SOURCES_DIR}/src/timer/unix/*.c
-      ${SDL_SOURCES_DIR}/src/video/x11/*.c
-      )
-
-    list(APPEND SDL_SOURCES ${TMP})
-
-    add_definitions(
-      -DSDL_LOADSO_DLOPEN=1
-      -DSDL_THREAD_PTHREAD=1
-      -DSDL_TIMER_UNIX=1
-      -DSDL_POWER_DISABLED=1
-
-      -DSDL_VIDEO_DRIVER_X11=1
-
-      -DSDL_ASSEMBLY_ROUTINES=1
-      -DSDL_THREAD_PTHREAD_RECURSIVE_MUTEX=1
-      -DSDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS=1
-      -DHAVE_GCC_SYNC_LOCK_TEST_AND_SET=1
-      )
-
-    link_libraries(X11 Xext)
-
-    if (NOT CMAKE_SYSTEM_VERSION STREQUAL "Raspberry")
-      # Raspberry Pi has no support for OpenGL
-      file(GLOB TMP
-        ${SDL_SOURCES_DIR}/src/render/opengl/*.c
-        ${SDL_SOURCES_DIR}/src/render/opengles2/*.c
-        )
-
-      list(APPEND SDL_SOURCES ${TMP})
-
-      add_definitions(
-        -DSDL_VIDEO_OPENGL=1
-        -DSDL_VIDEO_OPENGL_ES2=1
-        -DSDL_VIDEO_RENDER_OGL=1
-        -DSDL_VIDEO_RENDER_OGL_ES2=1
-        -DSDL_VIDEO_OPENGL_GLX=1
-        -DSDL_VIDEO_OPENGL_EGL=1
-        )
-    endif()
-
-  elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows")
-    file(GLOB TMP
-      ${SDL_SOURCES_DIR}/src/audio/directsound/*.c
-      ${SDL_SOURCES_DIR}/src/audio/disk/*.c
-      ${SDL_SOURCES_DIR}/src/audio/winmm/*.c
-      ${SDL_SOURCES_DIR}/src/joystick/windows/*.c
-      ${SDL_SOURCES_DIR}/src/haptic/windows/*.c
-      ${SDL_SOURCES_DIR}/src/power/windows/*.c
-
-      ${SDL_SOURCES_DIR}/src/main/windows/*.c
-      ${SDL_SOURCES_DIR}/src/core/windows/*.c
-      ${SDL_SOURCES_DIR}/src/loadso/windows/*.c
-      ${SDL_SOURCES_DIR}/src/render/direct3d/*.c
-      ${SDL_SOURCES_DIR}/src/render/direct3d11/*.c
-      ${SDL_SOURCES_DIR}/src/render/opengl/*.c
-      ${SDL_SOURCES_DIR}/src/render/psp/*.c
-      ${SDL_SOURCES_DIR}/src/render/opengles/*.c
-      ${SDL_SOURCES_DIR}/src/render/opengles2/*.c
-      ${SDL_SOURCES_DIR}/src/render/software/*.c
-      ${SDL_SOURCES_DIR}/src/thread/generic/SDL_syscond.c   # Don't include more files from "thread/generic/*.c"!
-      ${SDL_SOURCES_DIR}/src/thread/windows/*.c
-      ${SDL_SOURCES_DIR}/src/timer/windows/*.c
-      ${SDL_SOURCES_DIR}/src/video/windows/*.c
-      ${SDL_SOURCES_DIR}/src/windows/dlopen/*.c
-      )
-
-    list(APPEND SDL_SOURCES ${TMP})
-
-    # NB: OpenGL ES headers are not available in MinGW-W64
-    add_definitions(
-      -DSDL_LOADSO_WINDOWS=1
-      -DSDL_THREAD_WINDOWS=1
-      -DSDL_TIMER_WINDOWS=1
-      -DSDL_POWER_WINDOWS=1
-
-      -DSDL_VIDEO_OPENGL=1
-      -DSDL_VIDEO_OPENGL_WGL=1
-      -DSDL_VIDEO_RENDER_D3D=1
-      -DSDL_VIDEO_RENDER_OGL=1
-      -DSDL_VIDEO_DRIVER_WINDOWS=1
-      )
-
-    if (MSVC)
-      add_definitions(
-        -D__FLTUSED__
-        -DHAVE_LIBC=1
-      )
-    else()
-      add_definitions(
-        -DHAVE_GCC_ATOMICS=1
-        -DSDL_ASSEMBLY_ROUTINES=1
-        )
-    endif()
-    
-    link_libraries(imm32 winmm version)
-
-  elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
-    file(GLOB TMP
-      ${SDL_SOURCES_DIR}/src/loadso/dlopen/*.c
-      ${SDL_SOURCES_DIR}/src/render/opengl/*.c
-      ${SDL_SOURCES_DIR}/src/render/opengles2/*.c
-      ${SDL_SOURCES_DIR}/src/render/software/*.c
-      ${SDL_SOURCES_DIR}/src/thread/pthread/*.c
-      ${SDL_SOURCES_DIR}/src/timer/unix/*.c
-      ${SDL_SOURCES_DIR}/src/video/cocoa/*.m
-      )
-
-    list(APPEND SDL_SOURCES ${TMP})
-
-    add_definitions(
-      -DSDL_LOADSO_DLOPEN=1
-      -DSDL_THREAD_PTHREAD=1
-      -DSDL_TIMER_UNIX=1
-      -DSDL_POWER_DISABLED=1
-
-      -DSDL_VIDEO_DRIVER_COCOA=1
-      -DSDL_VIDEO_OPENGL=1
-      -DSDL_VIDEO_OPENGL_CGL=1
-      -DSDL_VIDEO_RENDER_OGL=1
-      
-      -DSDL_ASSEMBLY_ROUTINES=1
-      -DSDL_THREAD_PTHREAD_RECURSIVE_MUTEX=1
-      )
-
-    find_library(CARBON_LIBRARY Carbon)
-    find_library(COCOA_LIBRARY Cocoa)
-    find_library(IOKIT_LIBRARY IOKit)
-    find_library(QUARTZ_LIBRARY QuartzCore)
-    link_libraries(${CARBON_LIBRARY} ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${QUARTZ_LIBRARY})
-
-  endif()
-
-else()
-  pkg_search_module(SDL2 REQUIRED sdl2)
-  include_directories(${SDL2_INCLUDE_DIRS})
-  link_libraries(${SDL2_LIBRARIES})
-endif()
--- a/OrthancStone/SharedLibrary/WebAssembly/CMakeLists.txt	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,212 +0,0 @@
-# Stone of Orthanc
-# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
-# Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2020 Osimis S.A., Belgium
-#
-# This program is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Affero General Public License
-# as published by the Free Software Foundation, either version 3 of
-# the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-
-cmake_minimum_required(VERSION 2.8.3)
-
-project(OrthancStoneModule)
-
-
-# Warning message related to WebAssembly modules: We know that 1.38.41
-# DOES NOT work, but that 1.39.17 works.
-message("")
-message("=== IMPORTANT: Make sure to use a recent version of Emscripten (preferably >= 2.0.0) ===")
-message("")
-
-
-set(ORTHANC_STONE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/../../../wasm-binaries/OrthancStoneModule" CACHE PATH "Where to put the WebAssembly binaries")
-
-
-# Ask for the generation of a side module
-set(WASM_FLAGS "-s SIDE_MODULE=1 -s EXPORT_ALL=1")  # Must be before "Compiler.cmake"
-
-
-# Configuration of the Emscripten compiler for WebAssembly target
-# ---------------------------------------------------------------
-
-set(USE_WASM ON CACHE BOOL "")
-
-set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "")
-
-set(WASM_FLAGS "${WASM_FLAGS} -s WASM=1 -s FETCH=1")
-if (CMAKE_BUILD_TYPE STREQUAL "Debug")
-  set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1")
-endif()
-
-set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0")
-set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1")
-add_definitions(
-  -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1
-)
-
-
-# Stone of Orthanc configuration
-# ---------------------------------------------------------------
-
-include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake)
-
-SET(ENABLE_DCMTK ON)
-SET(ENABLE_DCMTK_NETWORKING OFF)
-SET(ENABLE_DCMTK_TRANSCODING OFF)
-SET(ENABLE_GOOGLE_TEST OFF)
-SET(ENABLE_LOCALE ON)  # Necessary for text rendering
-SET(ENABLE_WASM ON)
-SET(ORTHANC_SANDBOXED ON)
-
-include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake)
-
-
-
-
-################################################################################
-
-# The source files that register a callback cannot be part of a side
-# module, and must be compiled in the main module. The following
-# command can be used to identify such files:
-#  $ grep -lrE 'emscripten_' ../../Sources/
-
-set(SOURCES_WITH_EMSCRIPTEN_CALLBACKS
-  ${ORTHANC_STONE_ROOT}/Sources/Oracle/WebAssemblyOracle.cpp
-  ${ORTHANC_STONE_ROOT}/Sources/Viewport/WebAssemblyViewport.cpp
-  ${ORTHANC_STONE_ROOT}/Sources/Viewport/WebAssemblyCairoViewport.cpp
-  )
-
-list(REMOVE_ITEM ORTHANC_STONE_SOURCES
-  ${SOURCES_WITH_EMSCRIPTEN_CALLBACKS}
-  )
-
-configure_file(
-  ${CMAKE_SOURCE_DIR}/../OrthancStone.h.in
-  ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-stone/OrthancStone.h
-  )
-
-configure_file(
-  ${ORTHANC_FRAMEWORK_ROOT}/../SharedLibrary/OrthancFramework.h.in
-  ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework/OrthancFramework.h
-  )
-
-file(
-  COPY ${CMAKE_SOURCE_DIR}/../../Sources/
-  DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-stone
-  NO_SOURCE_PERMISSIONS
-  FILES_MATCHING
-  PATTERN "*.h"
-  PATTERN OrthancStone.h EXCLUDE
-  PATTERN "Deprecated*" EXCLUDE
-  )
-
-file(
-  COPY ${ORTHANC_FRAMEWORK_ROOT}/
-  DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework
-  NO_SOURCE_PERMISSIONS
-  FILES_MATCHING
-  PATTERN "*.h"
-  PATTERN OrthancFramework.h EXCLUDE
-  )
-
-add_executable(OrthancStoneModule
-  ${ORTHANC_STONE_SOURCES}
-  ${AUTOGENERATED_SOURCES}  
-  ${CAIRO_SOURCES}
-  ${PIXMAN_SOURCES}
-  ${FREETYPE_SOURCES}
-  )
-
-set_target_properties(OrthancStoneModule
-  PROPERTIES
-  COMPILE_FLAGS "${WASM_FLAGS}"
-  LINK_FLAGS "${WASM_LINKER_FLAGS}"
-  )
-
-# CMake does not natively handle SIDE_MODULE, and believes that
-# Emscripten produces a ".js" file (whereas it creates only the
-# ".wasm"). Create a dummy ".js" for target to work.
-add_custom_command(
-  TARGET OrthancStoneModule POST_BUILD
-  COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/OrthancStoneModule.js
-  )
-
-file(
-  COPY ${BOOST_SOURCES_DIR}/boost/
-  DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/boost/
-  NO_SOURCE_PERMISSIONS
-  FILES_MATCHING
-  PATTERN "*.h"
-  PATTERN "*.hpp"
-  PATTERN "*.ipp"
-  )
-
-file(
-  COPY ${JSONCPP_SOURCES_DIR}/include/json/
-  DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/json/
-  NO_SOURCE_PERMISSIONS
-  FILES_MATCHING
-  PATTERN "*.h"
-  )
-
-file(
-  COPY ${CAIRO_SOURCES_DIR}/src/
-  DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/cairo/
-  NO_SOURCE_PERMISSIONS
-  FILES_MATCHING
-  PATTERN "*.h"
-  )
-
-set(DCMTK_MODULES
-  dcmdata
-  config
-  ofstd
-  oflog
-  )
-
-foreach (module IN LISTS DCMTK_MODULES)
-  file(
-    COPY ${DCMTK_SOURCES_DIR}/ofstd/include/dcmtk/${module}/
-    DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/dcmtk/${module}/
-    NO_SOURCE_PERMISSIONS
-    FILES_MATCHING
-    PATTERN "*.h"
-    )
-endforeach()
-
-    
-install(
-  TARGETS OrthancStoneModule
-  DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/lib
-  )
-
-install(FILES
-  ${CMAKE_CURRENT_BINARY_DIR}/OrthancStoneModule.wasm
-  DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/lib
-  )
-
-install(
-  DIRECTORY
-  ${CMAKE_CURRENT_BINARY_DIR}/Include/boost
-  ${CMAKE_CURRENT_BINARY_DIR}/Include/cairo
-  ${CMAKE_CURRENT_BINARY_DIR}/Include/dcmtk
-  ${CMAKE_CURRENT_BINARY_DIR}/Include/json
-  ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework
-  ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-stone
-  DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/include/
-  )
-
-install(FILES
-  ${SOURCES_WITH_EMSCRIPTEN_CALLBACKS}
-  DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/src/orthanc-stone
-  )
--- a/OrthancStone/SharedLibrary/WebAssembly/NOTES.txt	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-
-Install Emscripten:
-https://emscripten.org/docs/getting_started/downloads.html
-
-# cd ~/Downloads
-# git clone https://github.com/emscripten-core/emsdk.git
-# cd emsdk
-# ./emsdk install 2.0.0
-# ./emsdk activate 2.0.0
-
-
-Then, if the installation path was "~/Downloads/emsdk/":
-
-# source ~/Downloads/emsdk/emsdk_env.sh
-# cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DALLOW_DOWNLOADS=ON -G Ninja
-# ninja install
-
-=> The binaries will be put in "../../../../wasm-binaries/"
--- a/OrthancStone/Sources/Loaders/LoaderCache.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,270 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-#include "LoaderCache.h"
-
-#include "../StoneException.h"
-#include "OrthancSeriesVolumeProgressiveLoader.h"
-#include "OrthancMultiframeVolumeLoader.h"
-#include "DicomStructureSetLoader.h"
-
-#include "../Loaders/ILoadersContext.h"
-
-#if ORTHANC_ENABLE_WASM == 1
-# include <unistd.h>
-# include "../Oracle/WebAssemblyOracle.h"
-#else
-# include "../Oracle/ThreadedOracle.h"
-#endif
-
-#include "../Volumes/DicomVolumeImage.h"
-#include "../Volumes/DicomVolumeImageMPRSlicer.h"
-
-#include <OrthancException.h>
-#include <Toolbox.h>
-
-namespace OrthancStone
-{
-  LoaderCache::LoaderCache(OrthancStone::ILoadersContext& loadersContext, bool useCtProgressiveQuality)
-    : loadersContext_(loadersContext)
-    , useCtProgressiveQuality_(useCtProgressiveQuality)
-
-  {
-
-  }
-
-  boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> 
-    LoaderCache::GetSeriesVolumeProgressiveLoader(std::string seriesUuid)
-  {
-    try
-    {
-      // normalize keys a little
-      NormalizeUuid(seriesUuid);
-
-      // find in cache
-      if (seriesVolumeProgressiveLoaders_.find(seriesUuid) == seriesVolumeProgressiveLoaders_.end())
-      {
-        std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock());
-
-        boost::shared_ptr<OrthancStone::DicomVolumeImage> volumeImage(new OrthancStone::DicomVolumeImage);
-        boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> loader;
-      
-        // true means "use progressive quality"
-        // false means "load high quality slices only"
-        loader = OrthancSeriesVolumeProgressiveLoader::Create(loadersContext_, volumeImage, useCtProgressiveQuality_);
-        loader->LoadSeries(seriesUuid);
-        seriesVolumeProgressiveLoaders_[seriesUuid] = loader;
-      }
-      else
-      {
-//        LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : returning cached loader for seriesUUid = " << seriesUuid;
-      }
-      return seriesVolumeProgressiveLoaders_[seriesUuid];
-    }
-    catch (const Orthanc::OrthancException& e)
-    {
-      if (e.HasDetails())
-      {
-        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails();
-      }
-      else
-      {
-        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What();
-      }
-      throw;
-    }
-    catch (const std::exception& e)
-    {
-      LOG(ERROR) << "std::exception in LoaderCache: " << e.what();
-      throw;
-    }
-    catch (...)
-    {
-      LOG(ERROR) << "Unknown exception in LoaderCache";
-      throw;
-    }
-  }
-
-  boost::shared_ptr<OrthancMultiframeVolumeLoader> LoaderCache::GetMultiframeVolumeLoader(std::string instanceUuid)
-  {
-    // normalize keys a little
-    NormalizeUuid(instanceUuid);
-
-    // if the loader is not available, let's trigger its creation
-    if(multiframeVolumeLoaders_.find(instanceUuid) == multiframeVolumeLoaders_.end())
-    {
-      GetMultiframeDicomVolumeImageMPRSlicer(instanceUuid);
-    }
-    ORTHANC_ASSERT(multiframeVolumeLoaders_.find(instanceUuid) != multiframeVolumeLoaders_.end());
-
-    return multiframeVolumeLoaders_[instanceUuid];
-  }
-
-  boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> LoaderCache::GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid)
-  {
-    try
-    {
-      // normalize keys a little
-      NormalizeUuid(instanceUuid);
-
-      // find in cache
-      if (dicomVolumeImageMPRSlicers_.find(instanceUuid) == dicomVolumeImageMPRSlicers_.end())
-      {
-        std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock());
-        boost::shared_ptr<OrthancStone::DicomVolumeImage> volumeImage(new OrthancStone::DicomVolumeImage);
-        boost::shared_ptr<OrthancMultiframeVolumeLoader> loader;
-        {
-          loader = OrthancMultiframeVolumeLoader::Create(loadersContext_, volumeImage);
-          loader->LoadInstance(instanceUuid);
-        }
-        multiframeVolumeLoaders_[instanceUuid] = loader;
-        boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> mprSlicer(new OrthancStone::DicomVolumeImageMPRSlicer(volumeImage));
-        dicomVolumeImageMPRSlicers_[instanceUuid] = mprSlicer;
-      }
-      return dicomVolumeImageMPRSlicers_[instanceUuid];
-    }
-    catch (const Orthanc::OrthancException& e)
-    {
-      if (e.HasDetails())
-      {
-        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails();
-      }
-      else
-      {
-        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What();
-      }
-      throw;
-    }
-    catch (const std::exception& e)
-    {
-      LOG(ERROR) << "std::exception in LoaderCache: " << e.what();
-      throw;
-    }
-    catch (...)
-    {
-      LOG(ERROR) << "Unknown exception in LoaderCache";
-      throw;
-    }
-  }
-  
-  std::string LoaderCache::BuildDicomStructureSetLoaderKey(
-    const std::string& instanceUuid,
-    const std::string& uniqueKey)
-  {
-    return instanceUuid + "_" + uniqueKey;
-  }
-
-  boost::shared_ptr<DicomStructureSetLoader> LoaderCache::GetDicomStructureSetLoader(
-      std::string inInstanceUuid, 
-      const std::vector<std::string>& initiallyVisibleStructures,
-      const std::string& uniqueKey)
-  {
-    try
-    {
-      // normalize keys a little
-      NormalizeUuid(inInstanceUuid);
-
-      std::string entryKey = BuildDicomStructureSetLoaderKey(inInstanceUuid, uniqueKey);
-
-      // find in cache
-      if (dicomStructureSetLoaders_.find(entryKey) == dicomStructureSetLoaders_.end())
-      {
-        std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock());
-
-        boost::shared_ptr<DicomStructureSetLoader> loader;
-        {
-          loader = DicomStructureSetLoader::Create(loadersContext_);
-          loader->LoadInstance(inInstanceUuid, initiallyVisibleStructures);
-        }
-        dicomStructureSetLoaders_[entryKey] = loader;
-      }
-      return dicomStructureSetLoaders_[entryKey];
-    }
-    catch (const Orthanc::OrthancException& e)
-    {
-      if (e.HasDetails())
-      {
-        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails();
-      }
-      else
-      {
-        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What();
-      }
-      throw;
-    }
-    catch (const std::exception& e)
-    {
-      LOG(ERROR) << "std::exception in LoaderCache: " << e.what();
-      throw;
-    }
-    catch (...)
-    {
-      LOG(ERROR) << "Unknown exception in LoaderCache";
-      throw;
-    }
-  }
-
-  void LoaderCache::ClearCache()
-  {
-    std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock());
-    
-#ifndef NDEBUG
-    // ISO way of checking for debug builds
-    DebugDisplayObjRefCounts();
-#endif
-    seriesVolumeProgressiveLoaders_.clear();
-    multiframeVolumeLoaders_.clear();
-    dicomVolumeImageMPRSlicers_.clear();
-    dicomStructureSetLoaders_.clear();
-
-  }
-
-  template<typename T> void DebugDisplayObjRefCountsInMap(
-    const std::string& name, const std::map<std::string, boost::shared_ptr<T> >& myMap)
-  {
-    LOG(TRACE) << "Map \"" << name << "\" ref counts:";
-    size_t i = 0;
-    for (typename std::map<std::string, boost::shared_ptr<T> >::const_iterator 
-           it = myMap.begin(); it != myMap.end(); ++it)
-    {
-      LOG(TRACE) << "  element #" << i << ": ref count = " << it->second.use_count();
-      i++;
-    }
-  }
-
-  void LoaderCache::DebugDisplayObjRefCounts()
-  {
-    DebugDisplayObjRefCountsInMap("seriesVolumeProgressiveLoaders_", seriesVolumeProgressiveLoaders_);
-    DebugDisplayObjRefCountsInMap("multiframeVolumeLoaders_", multiframeVolumeLoaders_);
-    DebugDisplayObjRefCountsInMap("dicomVolumeImageMPRSlicers_", dicomVolumeImageMPRSlicers_);
-    DebugDisplayObjRefCountsInMap("dicomStructureSetLoaders_", dicomStructureSetLoaders_);
-  }
-
-  /**
-  This method could have been called StripSpacesAndChangeToLower but we might want to 
-  add some UUID validation to the argument
-  */
-  void LoaderCache::NormalizeUuid(std::string& uuid)
-  {
-    std::string temp = Orthanc::Toolbox::StripSpaces(uuid);
-    Orthanc::Toolbox::ToLowerCase(temp);
-    uuid.swap(temp);
-  }
-}
--- a/OrthancStone/Sources/Loaders/LoaderCache.h	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,107 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-#pragma once
-
-#include "../Volumes/DicomVolumeImageMPRSlicer.h"
-#include "OrthancSeriesVolumeProgressiveLoader.h"
-#include "OrthancMultiframeVolumeLoader.h"
-#include "DicomStructureSetLoader.h"
-
-#include <boost/shared_ptr.hpp>
-
-#include <map>
-#include <string>
-#include <vector>
-
-namespace OrthancStone
-{
-  class ILoadersContext;
-}
-
-namespace OrthancStone
-{
-  class LoaderCache
-  {
-  public:
-
-    virtual ~LoaderCache() {}
-
-    /**
-    By default, the CT loader in loader cache will only download the highest quality slices.
-    If you pass true for useCtProgressiveQuality, jpeg (50/100 quality), then jpeg (90/100 quality) 
-    then eventually uncompressed 16-bit images will be loaded. 
-    */
-    LoaderCache(OrthancStone::ILoadersContext& loadersContext,
-                bool useCtProgressiveQuality);
-
-    boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader>
-      GetSeriesVolumeProgressiveLoader      (std::string seriesUuid);
-    
-    boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer>
-      GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid);
-
-    boost::shared_ptr<OrthancMultiframeVolumeLoader>
-      GetMultiframeVolumeLoader(std::string instanceUuid);
-
-    /**
-    The DicomStructureSetLoader instances are stored in a map and indexed
-    by a key built from instanceUuid and uniqueKey.
-
-    If instanceUuid and uniqueKey correspond to an already existing loader, it is returned.
-
-    Please note that initiallyVisibleStructures is only used if the call results in the creation
-    of a new loader. In that case, the value is passed to the constructor.
-    */
-    boost::shared_ptr<DicomStructureSetLoader>
-      GetDicomStructureSetLoader(
-        std::string instanceUuid,
-        const std::vector<std::string>& initiallyVisibleStructures,
-        const std::string& uniqueKey = "");
-
-    std::string BuildDicomStructureSetLoaderKey(
-        const std::string& instanceUuid,
-        const std::string& uniqueKey = "");
-
-    void ClearCache();
-
-    /**
-    Service method static and exposed for unit tests.
-    */
-    static void NormalizeUuid(std::string& uuid);
-
-  protected:
-    
-    void DebugDisplayObjRefCounts();
-
-    OrthancStone::ILoadersContext& loadersContext_;
-    bool                           useCtProgressiveQuality_;
-
-    std::map<std::string, boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> >
-      seriesVolumeProgressiveLoaders_;
-    std::map<std::string, boost::shared_ptr<OrthancMultiframeVolumeLoader> >
-      multiframeVolumeLoaders_;
-    std::map<std::string, boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> >
-      dicomVolumeImageMPRSlicers_;
-    std::map<std::string, boost::shared_ptr<DicomStructureSetLoader> >
-      dicomStructureSetLoaders_;
-  };
-}
-
--- a/OrthancStone/Sources/Loaders/WebAssemblyLoadersContext.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "WebAssemblyLoadersContext.h"
-
-namespace OrthancStone
-{
-  class WebAssemblyLoadersContext::Locker : public ILoadersContext::ILock
-  {
-  private:
-    WebAssemblyLoadersContext&  that_;
-
-  public:
-    explicit Locker(WebAssemblyLoadersContext& that) :
-      that_(that)
-    {
-    }      
-      
-    virtual ILoadersContext& GetContext() const ORTHANC_OVERRIDE
-    {
-      return that_;
-    }
-
-    virtual IObservable& GetOracleObservable() const ORTHANC_OVERRIDE
-    {
-      return that_.oracle_.GetOracleObservable();
-    }
-
-    virtual void Schedule(boost::shared_ptr<IObserver> receiver,
-                          int priority,
-                          IOracleCommand* command /* Takes ownership */) ORTHANC_OVERRIDE
-    {
-      that_.scheduler_->Schedule(receiver, priority, command);
-    }
-
-    virtual void CancelRequests(boost::shared_ptr<IObserver> receiver) ORTHANC_OVERRIDE
-    {
-      that_.scheduler_->CancelRequests(receiver);
-    }
-
-    virtual void CancelAllRequests() ORTHANC_OVERRIDE
-    {
-      that_.scheduler_->CancelAllRequests();
-    }
-
-    virtual void AddLoader(boost::shared_ptr<IObserver> loader) ORTHANC_OVERRIDE
-    {
-      that_.loaders_.push_back(loader);
-    }
-
-    virtual void GetStatistics(uint64_t& scheduledCommands,
-                               uint64_t& processedCommands) ORTHANC_OVERRIDE
-    {
-      scheduledCommands = that_.scheduler_->GetTotalScheduled();
-      processedCommands = that_.scheduler_->GetTotalProcessed();
-    }
-  };
-    
-
-  WebAssemblyLoadersContext::WebAssemblyLoadersContext(unsigned int maxHighPriority,
-                                                       unsigned int maxStandardPriority,
-                                                       unsigned int maxLowPriority)
-  {
-    oracle_.GetOracleObservable();
-    scheduler_ = OracleScheduler::Create(oracle_, oracle_.GetOracleObservable(), oracle_,
-                                         maxHighPriority, maxStandardPriority, maxLowPriority);
-
-    if (!scheduler_)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  }
-
-
-  ILoadersContext::ILock* WebAssemblyLoadersContext::Lock()
-  {
-    return new Locker(*this);
-  }
-}
--- a/OrthancStone/Sources/Loaders/WebAssemblyLoadersContext.h	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ILoadersContext.h"
-#include "../Oracle/WebAssemblyOracle.h"
-#include "OracleScheduler.h"
-
-#include <list>
-
-namespace OrthancStone
-{
-  class WebAssemblyLoadersContext : public ILoadersContext
-  {
-  private:
-    class Locker;
-    
-    WebAssemblyOracle                          oracle_;
-    boost::shared_ptr<OracleScheduler>         scheduler_;
-    std::list< boost::shared_ptr<IObserver> >  loaders_;
-    
-  public:
-    WebAssemblyLoadersContext(unsigned int maxHighPriority,
-                              unsigned int maxStandardPriority,
-                              unsigned int maxLowPriority);
-
-    void SetLocalOrthanc(const std::string& root)
-    {
-      oracle_.SetLocalOrthanc(root);
-    }
-
-    void SetRemoteOrthanc(const Orthanc::WebServiceParameters& orthanc)
-    {
-      oracle_.SetRemoteOrthanc(orthanc);
-    }
-
-    void SetDicomCacheSize(size_t size)
-    {
-      oracle_.SetDicomCacheSize(size);
-    }
-
-    virtual ILock* Lock() ORTHANC_OVERRIDE;
-  };
-}
--- a/OrthancStone/Sources/OpenGL/OpenGLIncludes.h	Thu Oct 22 18:39:03 2020 +0200
+++ b/OrthancStone/Sources/OpenGL/OpenGLIncludes.h	Fri Oct 23 13:15:03 2020 +0200
@@ -23,6 +23,14 @@
 
 #include "../OrthancStone.h"
 
+#if !defined(ORTHANC_ENABLE_SDL)
+#  error The macro ORTHANC_ENABLE_SDL must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_WASM)
+#  error The macro ORTHANC_ENABLE_WASM must be defined
+#endif
+
 #if !defined(ORTHANC_ENABLE_OPENGL)
 #  error The macro ORTHANC_ENABLE_OPENGL must be defined
 #endif
@@ -44,73 +52,94 @@
 #  include <GL/glext.h>
 #endif
 
-#if ORTHANC_ENABLE_SDL == 1
-# include <SDL_video.h>
 
-# ifdef NDEBUG
 
-// glGetError is very expensive!
-
-#   define ORTHANC_OPENGL_CHECK(name)
-#   define ORTHANC_OPENGL_TRACE_CURRENT_CONTEXT(msg)
-
-# else 
+#if ORTHANC_ENABLE_SDL == 1
+#  include <SDL_video.h>
 
-#   define ORTHANC_OPENGL_CHECK(name) \
-if(true)                                                                                                                                \
-{                                                                                                                                       \
-  GLenum error = glGetError();                                                                                                          \
-  if (error != GL_NO_ERROR) {                                                                                                           \
-    SDL_GLContext ctx = SDL_GL_GetCurrentContext();                                                                                     \
-    LOG(ERROR) << "Error when calling " << name << " | current context is: 0x" << std::hex << ctx <<  " | error code is " << error;     \
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,"OpenGL error in " name " | See log.");                            \
-  }                                                                                                                                     \
-} else (void)0
+#  if !defined(NDEBUG)  // Is build type "debug"?
+//   glGetError is very expensive!
+#    include <OrthancException.h>
+#    define ORTHANC_OPENGL_CHECK(name)                                  \
+  if(true)                                                              \
+  {                                                                     \
+    GLenum error = glGetError();                                        \
+    if (error != GL_NO_ERROR) {                                         \
+      SDL_GLContext ctx = SDL_GL_GetCurrentContext();                   \
+      LOG(ERROR) << "Error when calling " << name << " | current context is: 0x" \
+                 << std::hex << ctx <<  " | error code is " << error;   \
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,"OpenGL error in " name " | See log."); \
+    }                                                                   \
+  } else (void)0
 
-#   define ORTHANC_OPENGL_TRACE_CURRENT_CONTEXT(msg)                          \
-if(true)                                                                   \
-{                                                                          \
-  SDL_GLContext ctx = SDL_GL_GetCurrentContext();                          \
-  LOG(TRACE) << msg << " | Current OpenGL context is " << std::hex << ctx; \
-} else (void)0
+#   define ORTHANC_OPENGL_TRACE_CURRENT_CONTEXT(msg)                    \
+  if(true)                                                              \
+  {                                                                     \
+    SDL_GLContext ctx = SDL_GL_GetCurrentContext();                     \
+    LOG(TRACE) << msg << " | Current OpenGL context is " << std::hex << ctx; \
+  } else (void)0
 
-# endif
+#  endif /* NDEBUG */
+#endif  /* SDL */
 
-#endif
+
 
 #if ORTHANC_ENABLE_WASM == 1
-#include <emscripten/html5.h>
+#  include <emscripten/html5.h>
 
-#define ORTHANC_OPENGL_CHECK(name) \
-if(true)                                                                                                                                                                                    \
-{                                                                                                                                                                                           \
-  GLenum error = glGetError();                                                                                                                                                              \
-  if (error != GL_NO_ERROR) {                                                                                                                                                               \
-    EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_get_current_context();                                                                                                           \
-    EM_BOOL lost = emscripten_is_webgl_context_lost(ctx);                                                                                                                                   \
-    LOG(ERROR) << "Error when calling " << name << " | current context is: 0x" << std::hex << ctx <<  " | error code is " << error << " | emscripten_is_webgl_context_lost = " << lost;     \
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,"OpenGL error in " name " | See log.");                                                                                \
-  }                                                                                                                                                                                         \
-} else (void)0
+#  if !defined(NDEBUG)  // Is build type "debug"?
+#    include <OrthancException.h>
+#    define ORTHANC_OPENGL_CHECK(name)                                  \
+  if(true)                                                              \
+  {                                                                     \
+    GLenum error = glGetError();                                        \
+    if (error != GL_NO_ERROR) {                                         \
+      EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_get_current_context(); \
+      EM_BOOL lost = emscripten_is_webgl_context_lost(ctx);             \
+      LOG(ERROR) << "Error when calling " << name << " | current context is: 0x" \
+                 << std::hex << ctx <<  " | error code is " << error << " | emscripten_is_webgl_context_lost = " << lost; \
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,"OpenGL error in " name " | See log."); \
+    }                                                                   \
+  } else (void)0
+
+#    define ORTHANC_OPENGL_TRACE_CURRENT_CONTEXT(msg)                   \
+  if(true)                                                              \
+  {                                                                     \
+    EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_get_current_context(); \
+    LOG(TRACE) << msg << " | Current OpenGL context is " << std::hex << ctx; \
+  } else (void)0
 
-#define ORTHANC_OPENGL_TRACE_CURRENT_CONTEXT(msg)                                 \
-if(true)                                                                          \
-{                                                                                 \
-  EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_get_current_context();   \
-  LOG(TRACE) << msg << " | Current OpenGL context is " << std::hex << ctx;        \
-} else (void)0
+#    define ORTHANC_CHECK_CURRENT_CONTEXT(context)                      \
+  if(true)                                                              \
+  {                                                                     \
+    EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_get_current_context(); \
+    void* actualCtx = reinterpret_cast<void*>(ctx);                     \
+    void* expectedCtx = context.DebugGetInternalContext();              \
+    if(expectedCtx != actualCtx)                                        \
+    {                                                                   \
+      LOG(ERROR) << "Expected context was " << std::hex << expectedCtx  \
+                 << " while actual context is " << std::hex << actualCtx; \
+    }                                                                   \
+  } else (void)0
 
-#define ORTHANC_CHECK_CURRENT_CONTEXT(context)                                                                                                         \
-if(true)                                                                                                                                               \
-{                                                                                                                                                      \
-  EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_get_current_context();                                                                        \
-  void* actualCtx = reinterpret_cast<void*>(ctx);                                                                                                      \
-  void* expectedCtx = context.DebugGetInternalContext();                                                                                               \
-  if(expectedCtx != actualCtx)                                                                                                                         \
-  {                                                                                                                                                    \
-    LOG(ERROR) << "Expected context was " << std::hex << expectedCtx << " while actual context is " << std::hex << actualCtx;                          \
-  }                                                                                                                                                    \
-} else (void)0
+#  endif /* NDEBUG */
+#endif /* WASM */
+
+
+
 
+
+// Define void implementation of debug macros if they were not defined above
+
+#if !defined(ORTHANC_OPENGL_CHECK)
+#  define ORTHANC_OPENGL_CHECK(name)
 #endif
 
+#if !defined(ORTHANC_OPENGL_TRACE_CURRENT_CONTEXT)
+#  define ORTHANC_OPENGL_TRACE_CURRENT_CONTEXT(msg)
+#endif
+
+#if !defined(ORTHANC_CHECK_CURRENT_CONTEXT)
+#  define ORTHANC_CHECK_CURRENT_CONTEXT(context)
+#endif
+
--- a/OrthancStone/Sources/OpenGL/SdlOpenGLContext.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,127 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "SdlOpenGLContext.h"
-#include "../StoneException.h"
-
-#if ORTHANC_ENABLE_SDL == 1
-
-#if !defined(ORTHANC_ENABLE_GLEW)
-#  error Macro ORTHANC_ENABLE_GLEW must be defined
-#endif
-
-#if ORTHANC_ENABLE_GLEW == 1
-#  include <GL/glew.h>
-#endif
-
-#include <OrthancException.h>
-
-namespace OrthancStone
-{
-  SdlOpenGLContext::SdlOpenGLContext(const char* title,
-                                     unsigned int width,
-                                     unsigned int height,
-                                     bool allowDpiScaling) 
-    : window_(title, width, height, true /* enable OpenGL */, allowDpiScaling)
-    , context_(NULL)
-  {
-    context_ = SDL_GL_CreateContext(window_.GetObject());
-    
-    if (context_ == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
-                                      "Cannot initialize OpenGL");
-    }
-
-#if ORTHANC_ENABLE_GLEW == 1
-    // The initialization function of glew (i.e. "glewInit()") can
-    // only be called once an OpenGL is setup.
-    // https://stackoverflow.com/a/45033669/881731
-    {
-      static boost::mutex  mutex_;
-      static bool          isGlewInitialized_ = false;
-  
-      boost::mutex::scoped_lock lock(mutex_);
-
-      if (!isGlewInitialized_)
-      {
-        LOG(INFO) << "Initializing glew";
-        
-        GLenum err = glewInit();
-        if (GLEW_OK != err)
-        {
-          LOG(ERROR) << glewGetErrorString(err);
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
-                                          "Cannot initialize glew");
-        }
-
-        isGlewInitialized_ = true;
-      }
-    }    
-#endif
-  }
-
-  
-  SdlOpenGLContext::~SdlOpenGLContext()
-  {
-    SDL_GL_DeleteContext(context_);
-  }
-
-  void SdlOpenGLContext::MakeCurrent()
-  {
-    if (SDL_GL_MakeCurrent(window_.GetObject(), context_) != 0)
-    {
-      const char* errText = SDL_GetError();
-      std::stringstream ss;
-      ss << "Cannot set current OpenGL context. SDL error text: " << errText;
-      std::string errStr = ss.str();
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, errStr.c_str());
-    }
-
-    // This makes our buffer swap synchronized with the monitor's vertical refresh
-    SDL_GL_SetSwapInterval(1);
-  }
-
-  
-  void SdlOpenGLContext::SwapBuffer()
-  {
-    // Swap our buffer to display the current contents of buffer on screen
-    SDL_GL_SwapWindow(window_.GetObject());
-  }
-
-  
-  unsigned int SdlOpenGLContext::GetCanvasWidth() const
-  {
-    int w = 0;
-    SDL_GL_GetDrawableSize(window_.GetObject(), &w, NULL);
-    return static_cast<unsigned int>(w);
-  }
-
-  
-  unsigned int SdlOpenGLContext::GetCanvasHeight() const
-  {
-    int h = 0;
-    SDL_GL_GetDrawableSize(window_.GetObject(), NULL, &h);
-    return static_cast<unsigned int>(h);
-  }
-}
-
-#endif
--- a/OrthancStone/Sources/OpenGL/SdlOpenGLContext.h	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#if ORTHANC_ENABLE_SDL == 1
-
-#include "IOpenGLContext.h"
-#include "../Viewport/SdlWindow.h"
-
-#include <Compatibility.h>  // For ORTHANC_OVERRIDE
-
-#include <SDL_render.h>
-
-#include <Enumerations.h>
-
-namespace OrthancStone
-{
-  class SdlOpenGLContext : public OpenGL::IOpenGLContext
-  {
-  private:
-    SdlWindow      window_;
-    SDL_GLContext  context_;
-
-  public:
-    SdlOpenGLContext(const char* title,
-                     unsigned int width,
-                     unsigned int height,
-                     bool allowDpiScaling = true);
-
-    ~SdlOpenGLContext();
-
-    SdlWindow& GetWindow()
-    {
-      return window_;
-    }
-
-    virtual bool IsContextLost() ORTHANC_OVERRIDE
-    {
-      // On desktop applications, an OpenGL context should never be lost
-      return false;
-    }
-
-    virtual void MakeCurrent() ORTHANC_OVERRIDE;
-
-    virtual void SwapBuffer() ORTHANC_OVERRIDE;
-
-    unsigned int GetCanvasWidth() const;
-
-    unsigned int GetCanvasHeight() const;
-
-    void ToggleMaximize()
-    {
-      window_.ToggleMaximize();
-    }
-  };
-}
-
-#endif
--- a/OrthancStone/Sources/OpenGL/WebAssemblyOpenGLContext.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,199 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "WebAssemblyOpenGLContext.h"
-
-#include "../StoneException.h"
-
-#include <OrthancException.h>
-
-#include <emscripten/html5.h>
-#include <emscripten/em_asm.h>
-
-#include <boost/math/special_functions/round.hpp>
-
-namespace OrthancStone
-{
-  namespace OpenGL
-  {
-    class WebAssemblyOpenGLContext::PImpl
-    {
-    private:
-      std::string                     canvasSelector_;
-      EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context_;
-      bool                            isContextLost_;
-
-    public:
-      explicit PImpl(const std::string& canvasSelector) :
-        canvasSelector_(canvasSelector),
-        isContextLost_(false)
-      {
-        // Context configuration
-        EmscriptenWebGLContextAttributes attr; 
-        emscripten_webgl_init_context_attributes(&attr);
-
-        // The next line might be necessary to print using
-        // WebGL. Sometimes, if set to "false" (the default value),
-        // the canvas was rendered as a fully white or black
-        // area. UNCONFIRMED.
-        attr.preserveDrawingBuffer = true;
-
-        context_ = emscripten_webgl_create_context(canvasSelector.c_str(), &attr);
-        if (context_ == 0)
-        {
-          std::string message("Cannot create an OpenGL context for the element with the following CSS selector: \"");
-          message += canvasSelector;
-          message += "\"  Please make sure the -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 flag has been passed to Emscripten when building.";
-          LOG(ERROR) << message;
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, message);
-        }
-      }
-
-      void* DebugGetInternalContext() const
-      {
-        return reinterpret_cast<void*>(context_);
-      }
-
-      bool IsContextLost()
-      {
-        //LOG(TRACE) << "IsContextLost() for context " << std::hex << context_ << std::dec;
-        bool apiFlag = (emscripten_is_webgl_context_lost(context_) != 0);
-        isContextLost_ = apiFlag;
-        return isContextLost_;
-      }
-
-      void SetLostContext()
-      {
-        isContextLost_ = true;
-      }
-
-      ~PImpl()
-      {
-        try
-        {
-          EMSCRIPTEN_RESULT result = emscripten_webgl_destroy_context(context_);
-          if (result != EMSCRIPTEN_RESULT_SUCCESS)
-          {
-            LOG(ERROR) << "emscripten_webgl_destroy_context returned code " << result;
-          }
-        }
-        catch (const Orthanc::OrthancException& e)
-        {
-          if (e.HasDetails())
-          {
-            LOG(ERROR) << "OrthancException in WebAssemblyOpenGLContext::~PImpl: " << e.What() << " Details: " << e.GetDetails();
-          }
-          else
-          {
-            LOG(ERROR) << "OrthancException in WebAssemblyOpenGLContext::~PImpl: " << e.What();
-          }
-        }
-        catch (const std::exception& e)
-        {
-          LOG(ERROR) << "std::exception in WebAssemblyOpenGLContext::~PImpl: " << e.what();
-        }
-        catch (...)
-        {
-          LOG(ERROR) << "Unknown exception in WebAssemblyOpenGLContext::~PImpl";
-        }
-      }
-
-      const std::string& GetCanvasSelector() const
-      {
-        return canvasSelector_;
-      }
-
-      void MakeCurrent()
-      {
-        if (IsContextLost())
-        {
-          LOG(ERROR) << "MakeCurrent() called on lost context " << context_;
-          throw StoneException(ErrorCode_WebGLContextLost);
-        }
-
-        if (emscripten_is_webgl_context_lost(context_))
-        {
-          LOG(ERROR) << "OpenGL context has been lost for canvas selector: " << canvasSelector_;
-          SetLostContext();
-          throw StoneException(ErrorCode_WebGLContextLost);
-        }
-
-        if (emscripten_webgl_make_context_current(context_) != EMSCRIPTEN_RESULT_SUCCESS)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
-                                          "Cannot set the OpenGL context");
-        }
-      }
-
-      void SwapBuffer() 
-      {
-        /**
-         * "Rendered WebGL content is implicitly presented (displayed to
-         * the user) on the canvas when the event handler that renders with
-         * WebGL returns back to the browser event loop."
-         * https://emscripten.org/docs/api_reference/html5.h.html#webgl-context
-         *
-         * Could call "emscripten_webgl_commit_frame()" if
-         * "explicitSwapControl" option were set to "true".
-         **/
-      }
-    };
-
-
-    WebAssemblyOpenGLContext::WebAssemblyOpenGLContext(const std::string& canvasSelector) :
-      pimpl_(new PImpl(canvasSelector))
-    {
-    }
-
-    bool WebAssemblyOpenGLContext::IsContextLost()
-    {
-      return pimpl_->IsContextLost();
-    }
-
-    void WebAssemblyOpenGLContext::SetLostContext()
-    {
-      pimpl_->SetLostContext();
-    }
-
-    void* WebAssemblyOpenGLContext::DebugGetInternalContext() const
-    {
-      return pimpl_->DebugGetInternalContext();
-    }
-    
-    void WebAssemblyOpenGLContext::MakeCurrent()
-    {
-      assert(pimpl_.get() != NULL);
-      pimpl_->MakeCurrent();
-    }
-
-    void WebAssemblyOpenGLContext::SwapBuffer() 
-    {
-      assert(pimpl_.get() != NULL);
-      pimpl_->SwapBuffer();
-    }
-
-    const std::string& WebAssemblyOpenGLContext::GetCanvasSelector() const
-    {
-      assert(pimpl_.get() != NULL);
-      return pimpl_->GetCanvasSelector();
-    }
-  }
-}
--- a/OrthancStone/Sources/OpenGL/WebAssemblyOpenGLContext.h	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#if !defined(ORTHANC_ENABLE_WASM)
-#  error Macro ORTHANC_ENABLE_WASM must be defined
-#endif
-
-#if ORTHANC_ENABLE_WASM != 1
-#  error This file can only be used if targeting WebAssembly
-#endif
-
-#if !defined(ORTHANC_ENABLE_OPENGL)
-#  error The macro ORTHANC_ENABLE_OPENGL must be defined
-#endif
-
-#if ORTHANC_ENABLE_OPENGL != 1
-#  error Support for OpenGL is disabled
-#endif
-
-#include "IOpenGLContext.h"
-
-#include <Compatibility.h>  // For ORTHANC_OVERRIDE
-
-#include <boost/shared_ptr.hpp>
-
-namespace OrthancStone
-{
-  namespace OpenGL
-  {
-    class WebAssemblyOpenGLContext : public OpenGL::IOpenGLContext
-    {
-    private:
-      class PImpl;
-      boost::shared_ptr<PImpl>  pimpl_;
-
-    public:
-      explicit WebAssemblyOpenGLContext(const std::string& canvasSelector);
-
-      virtual bool IsContextLost() ORTHANC_OVERRIDE;
-
-      virtual void MakeCurrent() ORTHANC_OVERRIDE;
-
-      virtual void SwapBuffer() ORTHANC_OVERRIDE;
-
-      /**
-      Returns true if the underlying context has been successfully recreated
-      */
-      //bool TryRecreate();
-
-      const std::string& GetCanvasSelector() const;
-
-
-      /**
-       * This is for manual context loss (debug purposes)
-       **/
-      void* DebugGetInternalContext() const;
-      void SetLostContext();
-    };
-  }
-}
--- a/OrthancStone/Sources/Oracle/WebAssemblyOracle.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,818 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1
-#  include "WebAssemblyOracle_Includes.h"
-#else
-// This is the case when using the WebAssembly side module, and this
-// source file must be compiled within the WebAssembly main module
-#  include <Oracle/WebAssemblyOracle_Includes.h>
-#endif
-
-#include <OrthancException.h>
-#include <Toolbox.h>
-
-#include <emscripten.h>
-#include <emscripten/html5.h>
-#include <emscripten/fetch.h>
-
-
-#if ORTHANC_ENABLE_DCMTK == 1
-static unsigned int BUCKET_SOP = 1;
-#endif
-
-
-namespace OrthancStone
-{
-  class WebAssemblyOracle::TimeoutContext
-  {
-  private:
-    WebAssemblyOracle&                 oracle_;
-    boost::weak_ptr<IObserver>         receiver_;
-    std::unique_ptr<SleepOracleCommand>  command_;
-
-  public:
-    TimeoutContext(WebAssemblyOracle& oracle,
-                   boost::weak_ptr<IObserver> receiver,
-                   IOracleCommand* command) :
-      oracle_(oracle),
-      receiver_(receiver)
-    {
-      if (command == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-      else
-      {
-        command_.reset(dynamic_cast<SleepOracleCommand*>(command));
-      }
-    }
-
-    void EmitMessage()
-    {      
-      assert(command_.get() != NULL);
-
-      SleepOracleCommand::TimeoutMessage message(*command_);
-      oracle_.EmitMessage(receiver_, message);
-    }
-
-    static void Callback(void *userData)
-    {
-      std::unique_ptr<TimeoutContext> context(reinterpret_cast<TimeoutContext*>(userData));
-      context->EmitMessage();
-    }
-  };
-    
-
-  /**
-  This object is created on the heap for every http request.
-  It is deleted in the success (or error) callbacks.
-
-  This object references the receiver of the request. Since this is a raw
-  reference, we need additional checks to make sure we send the response to
-  the same object, for the object can be deleted and a new one recreated at the
-  same address (it often happens in the [single-threaded] browser context).
-  */
-  class WebAssemblyOracle::FetchContext : public boost::noncopyable
-  {
-  private:
-    WebAssemblyOracle&             oracle_;
-    boost::weak_ptr<IObserver>     receiver_;
-    std::unique_ptr<IOracleCommand>  command_;
-    std::string                    expectedContentType_;
-
-  public:
-    FetchContext(WebAssemblyOracle& oracle,
-                 boost::weak_ptr<IObserver> receiver,
-                 IOracleCommand* command,
-                 const std::string& expectedContentType) :
-      oracle_(oracle),
-      receiver_(receiver),
-      command_(command),
-      expectedContentType_(expectedContentType)
-    {
-      if (Orthanc::Logging::IsTraceLevelEnabled())
-      {
-        // Calling "receiver.lock()" is expensive, hence the quick check if TRACE is enabled
-        LOG(TRACE) << "WebAssemblyOracle::FetchContext::FetchContext() | "
-                   << "receiver address = " << std::hex << receiver.lock().get();
-      }
-
-      if (command == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-    }
-
-    const std::string& GetExpectedContentType() const
-    {
-      return expectedContentType_;
-    }
-
-    IMessageEmitter& GetEmitter() const
-    {
-      return oracle_;
-    }
-
-    boost::weak_ptr<IObserver> GetReceiver() const
-    {
-      return receiver_;
-    }
-
-    void EmitMessage(const IMessage& message)
-    {
-      if (Orthanc::Logging::IsTraceLevelEnabled())
-      {
-        // Calling "receiver_.lock()" is expensive, hence the quick check if TRACE is enabled
-        LOG(TRACE) << "WebAssemblyOracle::FetchContext::EmitMessage receiver_ = "
-                   << std::hex << receiver_.lock().get() << std::dec;
-      }
-      
-      oracle_.EmitMessage(receiver_, message);
-    }
-
-    IOracleCommand& GetCommand() const
-    {
-      return *command_;
-    }
-
-    template <typename T>
-    const T& GetTypedCommand() const
-    {
-      return dynamic_cast<T&>(*command_);
-    }
-
-#if ORTHANC_ENABLE_DCMTK == 1
-    void StoreInCache(const std::string& sopInstanceUid,
-                      std::unique_ptr<Orthanc::ParsedDicomFile>& dicom,
-                      size_t fileSize)
-    {
-      if (oracle_.dicomCache_.get())
-      {
-        // Store it into the cache for future use
-        oracle_.dicomCache_->Acquire(BUCKET_SOP, sopInstanceUid,
-                                     dicom.release(), fileSize, true);
-      }              
-    }
-#endif
-
-    static void SuccessCallback(emscripten_fetch_t *fetch)
-    {
-      /**
-       * Firstly, make a local copy of the fetched information, and
-       * free data associated with the fetch.
-       **/
-      
-      if (fetch->userData == NULL)
-      {
-        LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback fetch->userData is NULL!!!!!!!";
-        return;
-      }
-
-      std::unique_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData));
-
-      std::string answer;
-      if (fetch->numBytes > 0)
-      {
-        answer.assign(fetch->data, fetch->numBytes);
-      }
-
-
-      /**
-       * Retrieving the headers of the HTTP answer.
-       **/
-      HttpHeaders headers;
-
-#if (__EMSCRIPTEN_major__ < 1 ||                                        \
-     (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ < 38) ||        \
-     (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ == 38 && __EMSCRIPTEN_tiny__ < 37))
-#  warning Consider upgrading Emscripten to a version above 1.38.37, incomplete support of Fetch API
-
-      /**
-       * HACK - If emscripten < 1.38.37, the fetch API does not
-       * contain a way to retrieve the HTTP headers of the answer. We
-       * make the assumption that the "Content-Type" header of the
-       * response is the same as the "Accept" header of the
-       * query. This is fixed thanks to the
-       * "emscripten_fetch_get_response_headers()" function that was
-       * added to "fetch.h" at emscripten-1.38.37 on 2019-06-26.
-       *
-       * https://github.com/emscripten-core/emscripten/blob/1.38.37/system/include/emscripten/fetch.h
-       * https://github.com/emscripten-core/emscripten/pull/8486
-       **/
-      if (fetch->userData != NULL)
-      {
-        if (!context->GetExpectedContentType().empty())
-        {
-          headers["Content-Type"] = context->GetExpectedContentType();
-        }
-      }
-#else
-      {
-        size_t size = emscripten_fetch_get_response_headers_length(fetch);
-
-        std::string plainHeaders(size + 1, '\0');
-        emscripten_fetch_get_response_headers(fetch, &plainHeaders[0], size + 1);
-
-        std::vector<std::string> tokens;
-        Orthanc::Toolbox::TokenizeString(tokens, plainHeaders, '\n');
-
-        for (size_t i = 0; i < tokens.size(); i++)
-        {
-          size_t p = tokens[i].find(':');
-          if (p != std::string::npos)
-          {
-            std::string key = Orthanc::Toolbox::StripSpaces(tokens[i].substr(0, p));
-            std::string value = Orthanc::Toolbox::StripSpaces(tokens[i].substr(p + 1));
-            headers[key] = value;
-          }
-        }
-      }
-#endif
-      
-      LOG(TRACE) << "About to call emscripten_fetch_close";
-      emscripten_fetch_close(fetch);
-      LOG(TRACE) << "Successfully called emscripten_fetch_close";
-
-      /**
-       * Secondly, use the retrieved data.
-       * IMPORTANT NOTE: the receiver might be dead. This is prevented 
-       * by the object responsible for zombie check, later on.
-       **/
-      try
-      {
-        if (context.get() == NULL)
-        {
-          LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback: (context.get() == NULL)";
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-        }
-        else
-        {
-          switch (context->GetCommand().GetType())
-          {
-            case IOracleCommand::Type_Http:
-            {
-              HttpCommand::SuccessMessage message(context->GetTypedCommand<HttpCommand>(), headers, answer);
-              context->EmitMessage(message);
-              break;
-            }
-
-            case IOracleCommand::Type_OrthancRestApi:
-            {
-              LOG(TRACE) << "WebAssemblyOracle::FetchContext::SuccessCallback. About to call context->EmitMessage(message);";
-              OrthancRestApiCommand::SuccessMessage message
-                (context->GetTypedCommand<OrthancRestApiCommand>(), headers, answer);
-              context->EmitMessage(message);
-              break;
-            }
-
-            case IOracleCommand::Type_GetOrthancImage:
-            {
-              context->GetTypedCommand<GetOrthancImageCommand>().ProcessHttpAnswer
-                (context->GetReceiver(), context->GetEmitter(), answer, headers);
-              break;
-            }
-
-            case IOracleCommand::Type_GetOrthancWebViewerJpeg:
-            {
-              context->GetTypedCommand<GetOrthancWebViewerJpegCommand>().ProcessHttpAnswer
-                (context->GetReceiver(), context->GetEmitter(), answer);
-              break;
-            }
-
-            case IOracleCommand::Type_ParseDicomFromWado:
-            {
-#if ORTHANC_ENABLE_DCMTK == 1
-              const ParseDicomFromWadoCommand& command =
-                context->GetTypedCommand<ParseDicomFromWadoCommand>();
-              
-              size_t fileSize;
-              std::unique_ptr<Orthanc::ParsedDicomFile> dicom
-                (ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, headers));
-
-              {
-                ParseDicomSuccessMessage message(command, command.GetSource(), *dicom, fileSize, true);
-                context->EmitMessage(message);
-              }
-
-              context->StoreInCache(command.GetSopInstanceUid(), dicom, fileSize);
-#else
-              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-#endif
-              break;
-            }
-
-            default:
-              LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in SuccessCallback): "
-                         << context->GetCommand().GetType();
-              throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-          }
-        }
-      }
-      catch (Orthanc::OrthancException& e)
-      {
-        LOG(INFO) << "Error while processing a fetch answer in the oracle: " << e.What();
-
-        {
-          OracleCommandExceptionMessage message(context->GetCommand(), e);
-          context->EmitMessage(message);
-        }
-      }
-    }
-
-    static void FailureCallback(emscripten_fetch_t *fetch)
-    {
-      std::unique_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData));
-
-#if 0
-      {
-        const size_t kEmscriptenStatusTextSize = sizeof(emscripten_fetch_t::statusText);
-        char message[kEmscriptenStatusTextSize + 1];
-        memcpy(message, fetch->statusText, kEmscriptenStatusTextSize);
-        message[kEmscriptenStatusTextSize] = 0;
-
-        LOG(ERROR) << "Fetching " << fetch->url
-                   << " failed, HTTP failure status code: " << fetch->status
-                   << " | statusText = " << message
-                   << " | numBytes = " << fetch->numBytes
-                   << " | totalBytes = " << fetch->totalBytes
-                   << " | readyState = " << fetch->readyState;
-      }
-#endif
-
-      {
-        OracleCommandExceptionMessage message
-          (context->GetCommand(), Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol));
-        context->EmitMessage(message);
-      }
-      
-      /**
-       * TODO - The following code leads to an infinite recursion, at
-       * least with Firefox running on incognito mode => WHY?
-       **/      
-      emscripten_fetch_close(fetch); // Also free data on failure.
-    }
-  };
-    
-
-
-  class WebAssemblyOracle::FetchCommand : public boost::noncopyable
-  {
-  private:
-    WebAssemblyOracle&             oracle_;
-    boost::weak_ptr<IObserver>     receiver_;
-    std::unique_ptr<IOracleCommand>  command_;
-    Orthanc::HttpMethod            method_;
-    std::string                    url_;
-    std::string                    body_;
-    HttpHeaders                    headers_;
-    unsigned int                   timeout_;
-    std::string                    expectedContentType_;
-    bool                           hasCredentials_;
-    std::string                    username_;
-    std::string                    password_;
-
-  public:
-    FetchCommand(WebAssemblyOracle& oracle,
-                 boost::weak_ptr<IObserver> receiver,
-                 IOracleCommand* command) :
-      oracle_(oracle),
-      receiver_(receiver),
-      command_(command),
-      method_(Orthanc::HttpMethod_Get),
-      timeout_(0),
-      hasCredentials_(false)
-    {
-      if (command == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-    }
-
-    void SetMethod(Orthanc::HttpMethod method)
-    {
-      method_ = method;
-    }
-
-    void SetUrl(const std::string& url)
-    {
-      url_ = url;
-    }
-
-    void SetBody(std::string& body /* will be swapped */)
-    {
-      body_.swap(body);
-    }
-
-    void AddHttpHeaders(const HttpHeaders& headers)
-    {
-      for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it)
-      {
-        headers_[it->first] = it->second;
-      }
-    }
-
-    void SetTimeout(unsigned int timeout)
-    {
-      timeout_ = timeout;
-    }
-
-    void SetCredentials(const std::string& username,
-                        const std::string& password)
-    {
-      hasCredentials_ = true;
-      username_ = username;
-      password_ = password;
-    }
-
-    void Execute()
-    {
-      if (command_.get() == NULL)
-      {
-        // Cannot call Execute() twice
-        LOG(ERROR) << "WebAssemblyOracle::Execute(): (command_.get() == NULL)";
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-
-      emscripten_fetch_attr_t attr;
-      emscripten_fetch_attr_init(&attr);
-
-      const char* method;
-      
-      switch (method_)
-      {
-        case Orthanc::HttpMethod_Get:
-          method = "GET";
-          break;
-
-        case Orthanc::HttpMethod_Post:
-          method = "POST";
-          break;
-
-        case Orthanc::HttpMethod_Delete:
-          method = "DELETE";
-          break;
-
-        case Orthanc::HttpMethod_Put:
-          method = "PUT";
-          break;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-      }
-
-      strcpy(attr.requestMethod, method);
-
-      attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_REPLACE;
-      attr.onsuccess = FetchContext::SuccessCallback;
-      attr.onerror = FetchContext::FailureCallback;
-      attr.timeoutMSecs = timeout_ * 1000;
-
-      if (hasCredentials_)
-      {
-        attr.withCredentials = EM_TRUE;
-        attr.userName = username_.c_str();
-        attr.password = password_.c_str();
-      }
-      
-      std::vector<const char*> headers;
-      headers.reserve(2 * headers_.size() + 1);
-
-      std::string expectedContentType;
-        
-      for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it)
-      {
-        std::string key;
-        Orthanc::Toolbox::ToLowerCase(key, it->first);
-          
-        if (key == "accept")
-        {
-          expectedContentType = it->second;
-        }
-
-        if (key != "accept-encoding")  // Web browsers forbid the modification of this HTTP header
-        {
-          headers.push_back(it->first.c_str());
-          headers.push_back(it->second.c_str());
-        }
-      }
-        
-      headers.push_back(NULL);  // Termination of the array of HTTP headers
-
-      attr.requestHeaders = &headers[0];
-
-      char* requestData = NULL;
-      if (!body_.empty())
-        requestData = reinterpret_cast<char*>(malloc(body_.size()));
-        
-      try 
-      {
-        if (!body_.empty())
-        {
-          memcpy(requestData, &(body_[0]), body_.size());
-          attr.requestDataSize = body_.size();
-          attr.requestData = requestData;
-        }
-        attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType);
-
-        // Must be the last call to prevent memory leak on error
-        emscripten_fetch(&attr, url_.c_str());
-      }        
-      catch(...)
-      {
-        if(requestData != NULL)
-          free(requestData);
-        throw;
-      }
-    }
-  };
-
-
-  void WebAssemblyOracle::SetOrthancUrl(FetchCommand& command,
-                                        const std::string& uri) const
-  {
-    if (isLocalOrthanc_)
-    {
-      command.SetUrl(localOrthancRoot_ + uri);
-    }
-    else
-    {
-      command.SetUrl(remoteOrthanc_.GetUrl() + uri);
-      command.AddHttpHeaders(remoteOrthanc_.GetHttpHeaders());
-      
-      if (!remoteOrthanc_.GetUsername().empty())
-      {
-        command.SetCredentials(remoteOrthanc_.GetUsername(), remoteOrthanc_.GetPassword());
-      }
-    }
-  }
-    
-
-  void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
-                                  HttpCommand* command)
-  {
-    FetchCommand fetch(*this, receiver, command);
-    
-    fetch.SetMethod(command->GetMethod());
-    fetch.SetUrl(command->GetUrl());
-    fetch.AddHttpHeaders(command->GetHttpHeaders());
-    fetch.SetTimeout(command->GetTimeout());
-    
-    if (command->GetMethod() == Orthanc::HttpMethod_Post ||
-        command->GetMethod() == Orthanc::HttpMethod_Put)
-    {
-      std::string body;
-      command->SwapBody(body);
-      fetch.SetBody(body);
-    }
-    
-    fetch.Execute();
-  }
-  
-
-  void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
-                                  OrthancRestApiCommand* command)
-  {
-    try
-    {
-      //LOG(TRACE) << "*********** WebAssemblyOracle::Execute.";
-      //LOG(TRACE) << "WebAssemblyOracle::Execute | command = " << command;
-      FetchCommand fetch(*this, receiver, command);
-
-      fetch.SetMethod(command->GetMethod());
-      SetOrthancUrl(fetch, command->GetUri());
-      fetch.AddHttpHeaders(command->GetHttpHeaders());
-      fetch.SetTimeout(command->GetTimeout());
-
-      if (command->GetMethod() == Orthanc::HttpMethod_Post ||
-        command->GetMethod() == Orthanc::HttpMethod_Put)
-      {
-        std::string body;
-        command->SwapBody(body);
-        fetch.SetBody(body);
-      }
-
-      fetch.Execute();
-      //LOG(TRACE) << "*********** successful end of WebAssemblyOracle::Execute.";
-    }
-    catch (const Orthanc::OrthancException& e)
-    {
-      if (e.HasDetails())
-      {
-        LOG(ERROR) << "OrthancException in WebAssemblyOracle::Execute: " << e.What() << " Details: " << e.GetDetails();
-      }
-      else
-      {
-        LOG(ERROR) << "OrthancException in WebAssemblyOracle::Execute: " << e.What();
-      }
-      //LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute.";
-      throw;
-    }
-    catch (const std::exception& e)
-    {
-      LOG(ERROR) << "std::exception in WebAssemblyOracle::Execute: " << e.what();
-//       LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute.";
-      throw;
-    }
-    catch (...)
-    {
-      LOG(ERROR) << "Unknown exception in WebAssemblyOracle::Execute";
-//       LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute.";
-      throw;
-    }
-  }
-    
-    
-  void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
-                                  GetOrthancImageCommand* command)
-  {
-    FetchCommand fetch(*this, receiver, command);
-
-    SetOrthancUrl(fetch, command->GetUri());
-    fetch.AddHttpHeaders(command->GetHttpHeaders());
-    fetch.SetTimeout(command->GetTimeout());
-      
-    fetch.Execute();
-  }
-    
-    
-  void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
-                                  GetOrthancWebViewerJpegCommand* command)
-  {
-    FetchCommand fetch(*this, receiver, command);
-
-    SetOrthancUrl(fetch, command->GetUri());
-    fetch.AddHttpHeaders(command->GetHttpHeaders());
-    fetch.SetTimeout(command->GetTimeout());
-      
-    fetch.Execute();
-  }
-
-
-  void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
-                                  ParseDicomFromWadoCommand* command)
-  {
-    std::unique_ptr<ParseDicomFromWadoCommand> protection(command);
-    
-#if ORTHANC_ENABLE_DCMTK == 1
-    if (dicomCache_.get())
-    {
-      ParsedDicomCache::Reader reader(*dicomCache_, BUCKET_SOP, protection->GetSopInstanceUid());
-      if (reader.IsValid() &&
-          reader.HasPixelData())
-      {
-        // Reuse the DICOM file from the cache
-        ParseDicomSuccessMessage message(*protection, protection->GetSource(), reader.GetDicom(),
-                                         reader.GetFileSize(), reader.HasPixelData());
-        EmitMessage(receiver, message);
-        return;
-      }
-    }
-#endif
-
-    switch (command->GetRestCommand().GetType())
-    {
-      case IOracleCommand::Type_Http:
-      {
-        const HttpCommand& rest =
-          dynamic_cast<const HttpCommand&>(protection->GetRestCommand());
-        
-        FetchCommand fetch(*this, receiver, protection.release());
-    
-        fetch.SetMethod(rest.GetMethod());
-        fetch.SetUrl(rest.GetUrl());
-        fetch.AddHttpHeaders(rest.GetHttpHeaders());
-        fetch.SetTimeout(rest.GetTimeout());
-    
-        if (rest.GetMethod() == Orthanc::HttpMethod_Post ||
-            rest.GetMethod() == Orthanc::HttpMethod_Put)
-        {
-          std::string body = rest.GetBody();
-          fetch.SetBody(body);
-        }
-    
-        fetch.Execute();
-        break;
-      }
-
-      case IOracleCommand::Type_OrthancRestApi:
-      {
-        const OrthancRestApiCommand& rest =
-          dynamic_cast<const OrthancRestApiCommand&>(protection->GetRestCommand());
-        
-        FetchCommand fetch(*this, receiver, protection.release());
-
-        fetch.SetMethod(rest.GetMethod());
-        SetOrthancUrl(fetch, rest.GetUri());
-        fetch.AddHttpHeaders(rest.GetHttpHeaders());
-        fetch.SetTimeout(rest.GetTimeout());
-
-        if (rest.GetMethod() == Orthanc::HttpMethod_Post ||
-            rest.GetMethod() == Orthanc::HttpMethod_Put)
-        {
-          std::string body = rest.GetBody();
-          fetch.SetBody(body);
-        }
-
-        fetch.Execute();
-        break;
-      }
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-  }
-
-
-  bool WebAssemblyOracle::Schedule(boost::shared_ptr<IObserver> receiver,
-                                   IOracleCommand* command)
-  {
-    LOG(TRACE) << "WebAssemblyOracle::Schedule : receiver = "
-               << std::hex << receiver.get();
-    
-    std::unique_ptr<IOracleCommand> protection(command);
-
-    if (command == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-
-    switch (command->GetType())
-    {
-      case IOracleCommand::Type_Http:
-        Execute(receiver, dynamic_cast<HttpCommand*>(protection.release()));
-        break;
-        
-      case IOracleCommand::Type_OrthancRestApi:
-        Execute(receiver, dynamic_cast<OrthancRestApiCommand*>(protection.release()));
-        break;
-        
-      case IOracleCommand::Type_GetOrthancImage:
-        Execute(receiver, dynamic_cast<GetOrthancImageCommand*>(protection.release()));
-        break;
-
-      case IOracleCommand::Type_GetOrthancWebViewerJpeg:
-        Execute(receiver, dynamic_cast<GetOrthancWebViewerJpegCommand*>(protection.release()));
-        break;          
-            
-      case IOracleCommand::Type_Sleep:
-      {
-        unsigned int timeoutMS = dynamic_cast<SleepOracleCommand*>(command)->GetDelay();
-        emscripten_set_timeout(TimeoutContext::Callback, timeoutMS,
-                               new TimeoutContext(*this, receiver, protection.release()));
-        break;
-      }
-            
-      case IOracleCommand::Type_ParseDicomFromWado:
-#if ORTHANC_ENABLE_DCMTK == 1
-        Execute(receiver, dynamic_cast<ParseDicomFromWadoCommand*>(protection.release()));
-#else
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
-                                          "DCMTK must be enabled to parse DICOM files");
-#endif
-        break;
-            
-      default:
-        LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in Schedule): "
-                   << command->GetType();
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-
-    return true;
-  }
-
-
-  void WebAssemblyOracle::SetDicomCacheSize(size_t size)
-  {
-#if ORTHANC_ENABLE_DCMTK == 1
-    if (size == 0)
-    {
-      dicomCache_.reset();
-    }
-    else
-    {
-      dicomCache_.reset(new ParsedDicomCache(size));
-    }
-#else
-    LOG(INFO) << "DCMTK support is disabled, the DICOM cache is disabled";
-#endif
-  }
-}
--- a/OrthancStone/Sources/Oracle/WebAssemblyOracle.h	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,127 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../OrthancStone.h"
-
-#if !defined(ORTHANC_ENABLE_WASM)
-#  error The macro ORTHANC_ENABLE_WASM must be defined
-#endif
-
-#if ORTHANC_ENABLE_WASM != 1
-#  error This file can only compiled for WebAssembly
-#endif
-
-#include "../Messages/IObservable.h"
-#include "../Messages/IMessageEmitter.h"
-#include "IOracle.h"
-
-#if ORTHANC_ENABLE_DCMTK == 1
-#  include "../Toolbox/ParsedDicomCache.h"
-#endif
-
-#include <Compatibility.h>  // For ORTHANC_OVERRIDE
-#include <WebServiceParameters.h>
-
-#include <Enumerations.h>
-
-namespace OrthancStone
-{
-  class GetOrthancImageCommand;
-  class GetOrthancWebViewerJpegCommand;
-  class HttpCommand;
-  class OrthancRestApiCommand;
-  class ParseDicomFromWadoCommand;
-  
-  class WebAssemblyOracle :
-    public IOracle,
-    public IMessageEmitter
-  {
-  private:
-    typedef std::map<std::string, std::string>  HttpHeaders;
-    
-    class TimeoutContext;
-    class FetchContext;
-    class FetchCommand;
-
-    void SetOrthancUrl(FetchCommand& command,
-                       const std::string& uri) const;
-    
-    void Execute(boost::weak_ptr<IObserver> receiver,
-                 HttpCommand* command);    
-    
-    void Execute(boost::weak_ptr<IObserver> receiver,
-                 OrthancRestApiCommand* command);    
-    
-    void Execute(boost::weak_ptr<IObserver> receiver,
-                 GetOrthancImageCommand* command);    
-    
-    void Execute(boost::weak_ptr<IObserver> receiver,
-                 GetOrthancWebViewerJpegCommand* command);
-    
-    void Execute(boost::weak_ptr<IObserver> receiver,
-                 ParseDicomFromWadoCommand* command);
-
-    IObservable                    oracleObservable_;
-    bool                           isLocalOrthanc_;
-    std::string                    localOrthancRoot_;
-    Orthanc::WebServiceParameters  remoteOrthanc_;
-
-#if ORTHANC_ENABLE_DCMTK == 1
-    std::unique_ptr<ParsedDicomCache>  dicomCache_;
-#endif
-
-  public:
-    WebAssemblyOracle() :
-      isLocalOrthanc_(false)
-    {
-    }
-    
-    virtual void EmitMessage(boost::weak_ptr<IObserver> observer,
-                             const IMessage& message) ORTHANC_OVERRIDE
-    {
-      oracleObservable_.EmitMessage(observer, message);
-    }
-    
-    virtual bool Schedule(boost::shared_ptr<IObserver> receiver,
-                          IOracleCommand* command) ORTHANC_OVERRIDE;
-
-    IObservable& GetOracleObservable()
-    {
-      return oracleObservable_;
-    }
-
-    void SetLocalOrthanc(const std::string& root)
-    {
-      isLocalOrthanc_ = true;
-      localOrthancRoot_ = root;
-    }
-
-    void SetRemoteOrthanc(const Orthanc::WebServiceParameters& orthanc)
-    {
-      isLocalOrthanc_ = false;
-      remoteOrthanc_ = orthanc;
-    }
-
-    void SetDicomCacheSize(size_t size);
-  };
-}
--- a/OrthancStone/Sources/Oracle/WebAssemblyOracle_Includes.h	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-/**
- * This file serves as an indirection to avoid large "#if
- * ORTHANC_BUILDING_STONE_LIBRARY == 1" in "WebAssemblyOracle.cpp"
- **/
-
-#include "WebAssemblyOracle.h"
-
-#include "OracleCommandExceptionMessage.h"
-#include "SleepOracleCommand.h"
-
-#if ORTHANC_ENABLE_DCMTK == 1
-#  include "ParseDicomSuccessMessage.h"
-#endif
-
-#include "GetOrthancImageCommand.h"
-#include "GetOrthancWebViewerJpegCommand.h"
-#include "HttpCommand.h"
-#include "OrthancRestApiCommand.h"
-#include "ParseDicomFromWadoCommand.h"
--- a/OrthancStone/Sources/StoneInitialization.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ b/OrthancStone/Sources/StoneInitialization.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -21,10 +21,6 @@
 
 #include "StoneInitialization.h"
 
-#if !defined(ORTHANC_ENABLE_SDL)
-#  error Macro ORTHANC_ENABLE_SDL must be defined
-#endif
-
 #if !defined(ORTHANC_ENABLE_SSL)
 #  error Macro ORTHANC_ENABLE_SSL must be defined
 #endif
@@ -40,10 +36,6 @@
 #  endif
 #endif
 
-#if ORTHANC_ENABLE_SDL == 1
-#  include "Viewport/SdlWindow.h"
-#endif
-
 #if ORTHANC_ENABLE_CURL == 1
 #  include <HttpClient.h>
 #endif
@@ -52,11 +44,6 @@
 #  include <DicomParsing/FromDcmtkBridge.h>
 #endif
 
-#if ORTHANC_ENABLE_WASM == 1
-static double viewportsTimeout_ = 1000;
-static std::unique_ptr<OrthancStone::WebGLViewportsRegistry>  viewportsRegistry_;
-#endif
-
 #include "Toolbox/LinearAlgebra.h"
 
 #include <Logging.h>
@@ -150,23 +137,11 @@
         }
       }
     }
-
-#if ORTHANC_ENABLE_SDL == 1
-    OrthancStone::SdlWindow::GlobalInitialize();
-#endif
   }
   
 
   void StoneFinalize()
   {
-#if ORTHANC_ENABLE_WASM == 1
-    viewportsRegistry_.reset();
-#endif
-    
-#if ORTHANC_ENABLE_SDL == 1
-    OrthancStone::SdlWindow::GlobalFinalize();
-#endif
-    
 #if ORTHANC_ENABLE_DCMTK == 1
     Orthanc::FromDcmtkBridge::FinalizeCodecs();
 #endif
@@ -181,32 +156,4 @@
 
     Orthanc::Logging::Finalize();
   }
-
-
-#if ORTHANC_ENABLE_WASM == 1
-  void SetWebGLViewportsRegistryTimeout(double timeout)
-  {
-    if (viewportsRegistry_.get())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      viewportsTimeout_ = timeout;
-    }
-  }
-#endif
-
-
-#if ORTHANC_ENABLE_WASM == 1
-  WebGLViewportsRegistry& GetWebGLViewportsRegistry()
-  {
-    if (viewportsRegistry_.get() == NULL)
-    {
-      viewportsRegistry_.reset(new WebGLViewportsRegistry(viewportsTimeout_));
-    }
-
-    return *viewportsRegistry_;
-  }
-#endif
 }
--- a/OrthancStone/Sources/StoneInitialization.h	Thu Oct 22 18:39:03 2020 +0200
+++ b/OrthancStone/Sources/StoneInitialization.h	Fri Oct 23 13:15:03 2020 +0200
@@ -21,14 +21,6 @@
 
 #pragma once
 
-#if !defined(ORTHANC_ENABLE_WASM)
-#  error Macro ORTHANC_ENABLE_WASM must be defined
-#endif
-
-#if ORTHANC_ENABLE_WASM == 1
-#  include "Viewport/WebGLViewportsRegistry.h"
-#endif
-
 #include <Logging.h>
 
 namespace OrthancStone
@@ -41,12 +33,4 @@
   }
 
   void StoneFinalize();
-
-#if ORTHANC_ENABLE_WASM == 1
-  void SetWebGLViewportsRegistryTimeout(double timeout);
-#endif
-
-#if ORTHANC_ENABLE_WASM == 1
-  WebGLViewportsRegistry& GetWebGLViewportsRegistry();
-#endif
 }
--- a/OrthancStone/Sources/Toolbox/GenericToolbox.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ b/OrthancStone/Sources/Toolbox/GenericToolbox.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -21,6 +21,8 @@
 
 #include "GenericToolbox.h"
 
+#include <Toolbox.h>
+
 #include <boost/regex.hpp>
 
 namespace OrthancStone
@@ -100,5 +102,12 @@
       }
     }
 
+
+    void NormalizeUuid(std::string& uuid)
+    {
+      std::string temp = Orthanc::Toolbox::StripSpaces(uuid);
+      Orthanc::Toolbox::ToLowerCase(temp);
+      uuid.swap(temp);
+    }
   }
 }
--- a/OrthancStone/Sources/Toolbox/GenericToolbox.h	Thu Oct 22 18:39:03 2020 +0200
+++ b/OrthancStone/Sources/Toolbox/GenericToolbox.h	Fri Oct 23 13:15:03 2020 +0200
@@ -304,5 +304,12 @@
     {
       return GetRgbaValuesFromString(red, green, blue, alpha, text.c_str());
     }
+
+    
+    /**
+    This method could have been called StripSpacesAndChangeToLower but we might want to 
+    add some UUID validation to the argument
+    */
+    void NormalizeUuid(std::string& uuid);
   }
 }
--- a/OrthancStone/Sources/Viewport/SdlViewport.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,236 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-#include "SdlViewport.h"
-
-#include "../Scene2DViewport/ViewportController.h"
-
-#include <OrthancException.h>
-
-#include <boost/make_shared.hpp>
-
-namespace OrthancStone
-{
-  ICompositor& SdlViewport::SdlLock::GetCompositor()
-  {
-    if (that_.compositor_.get() == NULL)
-    {
-      // The derived class should have called "AcquireCompositor()"
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-    else
-    {
-      return *that_.compositor_;
-    }
-  }
-
-
-  void SdlViewport::AcquireCompositor(ICompositor* compositor /* takes ownership */)
-  {
-    if (compositor == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-
-    compositor_.reset(compositor);
-  }
-
-
-  SdlViewport::SdlViewport()
-  {
-    refreshEvent_ = SDL_RegisterEvents(1);
-
-    if (refreshEvent_ == static_cast<uint32_t>(-1))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  }
-  
-  void SdlViewport::PostConstructor()
-  {
-    controller_ = boost::make_shared<ViewportController>(shared_from_this());
-  }
-    
-  void SdlViewport::SendRefreshEvent()
-  {
-    SDL_Event event;
-    SDL_memset(&event, 0, sizeof(event));
-    event.type = refreshEvent_;
-    SDL_PushEvent(&event);  // This function is thread-safe, and can be called from other threads safely.
-  }
-
-  
-  void SdlViewport::UpdateSize(unsigned int width, unsigned int height)
-  {
-    SdlLock lock(*this);
-    lock.GetCompositor().SetCanvasSize(width, height);
-    lock.Invalidate();
-  }
-
-
-  SdlOpenGLViewport::SdlOpenGLViewport(const std::string& title,
-                                       unsigned int width,
-                                       unsigned int height,
-                                       bool allowDpiScaling) :
-    context_(title.c_str(), width, height, allowDpiScaling)
-  {
-    AcquireCompositor(new OpenGLCompositor(context_));  // (*)
-  }
-
-
-  void SdlOpenGLViewport::RefreshCanvasSize()
-  {
-    UpdateSize(context_.GetCanvasWidth(), context_.GetCanvasHeight());
-  }
-
-
-  boost::shared_ptr<SdlOpenGLViewport> SdlOpenGLViewport::Create(
-    const std::string& title,
-    unsigned int width,
-    unsigned int height,
-    bool allowDpiScaling)
-  {
-    boost::shared_ptr<SdlOpenGLViewport> that =
-      boost::shared_ptr<SdlOpenGLViewport>(new SdlOpenGLViewport(title, width, height, allowDpiScaling));
-    that->SdlViewport::PostConstructor();
-    return that;
-  }
-
-  uint32_t SdlOpenGLViewport::GetSdlWindowId()
-  {
-    const SdlWindow& sdlWindowWrapper = context_.GetWindow();
-    SDL_Window* sdlWindow = sdlWindowWrapper.GetObject();
-    Uint32 sdlWindowId = SDL_GetWindowID(sdlWindow);
-    return sdlWindowId;
-  }
-
-  SdlOpenGLViewport::~SdlOpenGLViewport()
-  {
-    // Make sure that the "OpenGLCompositor" is destroyed BEFORE the
-    // "OpenGLContext" it references (*)
-    ClearCompositor();
-  }
-
-
-  void SdlOpenGLViewport::Paint()
-  {
-    SdlLock lock(*this);
-    lock.GetCompositor().Refresh(lock.GetController().GetScene());
-  }
-
-
-  void SdlOpenGLViewport::ToggleMaximize()
-  {
-    // No need to call "Invalidate()" here, as "UpdateSize()" will
-    // be invoked after event "SDL_WINDOWEVENT_SIZE_CHANGED"
-    SdlLock lock(*this);
-    context_.ToggleMaximize();
-  }
-
-
-
-  void SdlCairoViewport::RefreshCanvasSize()
-  {
-    UpdateSize(window_.GetWidth(), window_.GetHeight());
-  }
-
-  SdlCairoViewport::SdlCairoViewport(const char* title,
-                                     unsigned int width,
-                                     unsigned int height,
-                                     bool allowDpiScaling) :
-    window_(title, width, height, false /* enable OpenGL */, allowDpiScaling),
-    sdlSurface_(NULL)
-  {
-    AcquireCompositor(new CairoCompositor(width, height));
-  }
-
-  SdlCairoViewport::~SdlCairoViewport()
-  {
-    if (sdlSurface_)
-    {
-      SDL_FreeSurface(sdlSurface_);
-    }
-  }
-  
-  uint32_t SdlCairoViewport::GetSdlWindowId()
-  {
-    SDL_Window* sdlWindow = window_.GetObject();
-    Uint32 sdlWindowId = SDL_GetWindowID(sdlWindow);
-    return sdlWindowId;
-  }
-
-  void SdlCairoViewport::Paint()
-  {
-    SdlLock lock(*this);
-
-    lock.GetCompositor().Refresh(lock.GetController().GetScene());
-    CreateSdlSurfaceFromCompositor(dynamic_cast<CairoCompositor&>(lock.GetCompositor()));
-    
-    if (sdlSurface_ != NULL)
-    {
-      window_.Render(sdlSurface_);
-    }
-  }
-
-
-  void SdlCairoViewport::ToggleMaximize()
-  {
-    // No need to call "Invalidate()" here, as "UpdateSize()" will
-    // be invoked after event "SDL_WINDOWEVENT_SIZE_CHANGED"
-    SdlLock lock(*this);
-    window_.ToggleMaximize();
-  }
-
-  
-  // Assumes that the mutex is locked
-  void SdlCairoViewport::CreateSdlSurfaceFromCompositor(const CairoCompositor& compositor)
-  {
-    static const uint32_t rmask = 0x00ff0000;
-    static const uint32_t gmask = 0x0000ff00;
-    static const uint32_t bmask = 0x000000ff;
-
-    const unsigned int width = compositor.GetCanvas().GetWidth();
-    const unsigned int height = compositor.GetCanvas().GetHeight();
-
-    if (sdlSurface_ != NULL)
-    {
-      if (sdlSurface_->pixels == compositor.GetCanvas().GetBuffer() &&
-          sdlSurface_->w == static_cast<int>(width) &&
-          sdlSurface_->h == static_cast<int>(height) &&
-          sdlSurface_->pitch == static_cast<int>(compositor.GetCanvas().GetPitch()))
-      {
-        // The image from the compositor has not changed, no need to update the surface
-        return;
-      }
-      else
-      {
-        SDL_FreeSurface(sdlSurface_);
-      }
-    }
-
-    sdlSurface_ = SDL_CreateRGBSurfaceFrom((void*)(compositor.GetCanvas().GetBuffer()), width, height, 32,
-                                           compositor.GetCanvas().GetPitch(), rmask, gmask, bmask, 0);
-    if (!sdlSurface_)
-    {
-      LOG(ERROR) << "Cannot create a SDL surface from a Cairo surface";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  }
-}
--- a/OrthancStone/Sources/Viewport/SdlViewport.h	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,203 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-#pragma once
-
-#if !defined(ORTHANC_ENABLE_SDL)
-#  error Macro ORTHANC_ENABLE_SDL must be defined
-#endif
-
-#if ORTHANC_ENABLE_SDL != 1
-#  error SDL must be enabled to use this file
-#endif
-
-#if !defined(ORTHANC_ENABLE_OPENGL)
-#  error The macro ORTHANC_ENABLE_OPENGL must be defined
-#endif
-
-#if ORTHANC_ENABLE_OPENGL != 1
-#  error Support for OpenGL is disabled
-#endif
-
-#include "../OpenGL/SdlOpenGLContext.h"
-#include "../Scene2D/OpenGLCompositor.h"
-#include "../Scene2D/CairoCompositor.h"
-#include "IViewport.h"
-
-#include <SDL_events.h>
-
-// TODO: required for UndoStack injection
-// I don't like it either :)
-#include <boost/weak_ptr.hpp>
-
-#include <boost/thread/recursive_mutex.hpp>
-#include <boost/enable_shared_from_this.hpp>
-
-namespace OrthancStone
-{
-  class UndoStack;
-
-  class SdlViewport : public IViewport,
-                      public boost::enable_shared_from_this<SdlViewport>
-  {
-  private:
-    boost::recursive_mutex                 mutex_;
-    uint32_t                               refreshEvent_;
-    boost::shared_ptr<ViewportController>  controller_;
-    std::unique_ptr<ICompositor>           compositor_;
-
-    void SendRefreshEvent();
-
-  protected:
-    class SdlLock : public ILock
-    {
-    private:
-      SdlViewport&                        that_;
-      boost::recursive_mutex::scoped_lock lock_;
-
-    public:
-      explicit SdlLock(SdlViewport& that) :
-        that_(that),
-        lock_(that.mutex_)
-      {
-      }
-
-      virtual bool HasCompositor() const ORTHANC_OVERRIDE
-      {
-        return true;
-      }
-
-      virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE;
-      
-      virtual ViewportController& GetController() ORTHANC_OVERRIDE
-      {
-        return *that_.controller_;
-      }
-
-      virtual void Invalidate() ORTHANC_OVERRIDE
-      {
-        that_.SendRefreshEvent();
-      }
-      
-      virtual void RefreshCanvasSize() ORTHANC_OVERRIDE
-      {
-        that_.RefreshCanvasSize();
-      }
-    };
-
-    void ClearCompositor()
-    {
-      compositor_.reset();
-    }
-
-    void AcquireCompositor(ICompositor* compositor /* takes ownership */);
-
-    virtual void RefreshCanvasSize() = 0;
-    
-  protected:
-    SdlViewport();
-
-    void PostConstructor();
-
-  public:
-    bool IsRefreshEvent(const SDL_Event& event) const
-    {
-      return (event.type == refreshEvent_);
-    }
-
-    virtual ILock* Lock() ORTHANC_OVERRIDE
-    {
-      return new SdlLock(*this);
-    }
-
-    virtual uint32_t GetSdlWindowId() = 0;
-
-    void UpdateSize(unsigned int width,
-                    unsigned int height);
-
-    virtual void ToggleMaximize() = 0;
-
-    // Must be invoked from the main SDL thread
-    virtual void Paint() = 0;
-  };
-
-
-  class SdlOpenGLViewport : public SdlViewport
-  {
-  private:
-    SdlOpenGLContext  context_;
-
-    SdlOpenGLViewport(const std::string& title,
-                      unsigned int       width,
-                      unsigned int       height,
-                      bool               allowDpiScaling = true);
-
-  protected:
-    virtual void RefreshCanvasSize() ORTHANC_OVERRIDE;
-    
-  public:
-    static boost::shared_ptr<SdlOpenGLViewport> Create(const std::string&,
-                                                       unsigned int width,
-                                                       unsigned int height,
-                                                       bool allowDpiScaling = true);
-
-
-    virtual ~SdlOpenGLViewport();
-
-    virtual uint32_t GetSdlWindowId() ORTHANC_OVERRIDE;
-
-    virtual void Paint() ORTHANC_OVERRIDE;
-
-    virtual void ToggleMaximize() ORTHANC_OVERRIDE;
-  };
-
-
-  class SdlCairoViewport : public SdlViewport
-  {
-  private:
-    SdlWindow     window_;
-    SDL_Surface*  sdlSurface_;
-
-    void CreateSdlSurfaceFromCompositor(const CairoCompositor& compositor);
-
-    SdlCairoViewport(const char* title,
-                     unsigned int width,
-                     unsigned int height,
-                     bool allowDpiScaling = true);
-
-  protected:
-    virtual void RefreshCanvasSize() ORTHANC_OVERRIDE;
-    
-  public:
-    static boost::shared_ptr<SdlCairoViewport> Create(const char* title,
-                     unsigned int width,
-                     unsigned int height,
-                     bool allowDpiScaling = true);
-
-
-    virtual ~SdlCairoViewport();
-
-    virtual uint32_t GetSdlWindowId() ORTHANC_OVERRIDE;
-
-    virtual void Paint() ORTHANC_OVERRIDE;
-
-    virtual void ToggleMaximize() ORTHANC_OVERRIDE;
-  };
-}
--- a/OrthancStone/Sources/Viewport/SdlWindow.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,210 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "SdlWindow.h"
-
-#if ORTHANC_ENABLE_SDL == 1
-
-#include <Logging.h>
-#include <OrthancException.h>
-
-#ifdef WIN32 
-#include <windows.h> // for SetProcessDpiAware
-#endif 
-// WIN32
-
-#include <SDL_render.h>
-#include <SDL_video.h>
-#include <SDL.h>
-
-namespace OrthancStone
-{
-  SdlWindow::SdlWindow(const char* title,
-                       unsigned int width,
-                       unsigned int height,
-                       bool enableOpenGl,
-                       bool allowDpiScaling) :
-    maximized_(false)
-  {
-    // TODO Understand why, with SDL_WINDOW_OPENGL + MinGW32 + Release
-    // build mode, the application crashes whenever the SDL window is
-    // resized or maximized
-
-    uint32_t windowFlags, rendererFlags;
-    if (enableOpenGl)
-    {
-      windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
-      rendererFlags = SDL_RENDERER_ACCELERATED;
-    }
-    else
-    {
-      windowFlags = SDL_WINDOW_RESIZABLE;
-      rendererFlags = SDL_RENDERER_SOFTWARE;
-    }
-
-// TODO: probably required on MacOS X, too
-#if defined(WIN32) && (_WIN32_WINNT >= 0x0600)
-    if (!allowDpiScaling)
-    {
-      // if we do NOT allow DPI scaling, it means an SDL pixel will be a real
-      // monitor pixel. This is needed for high-DPI applications
-
-      // Enable high-DPI support on Windows
-
-      // THE FOLLOWING HAS BEEN COMMENTED OUT BECAUSE IT WILL CRASH UNDER 
-      // OLD WINDOWS VERSIONS
-      // ADD THIS AT THE TOP TO ENABLE IT:
-      // 
-      //#pragma comment(lib, "Shcore.lib") THIS IS ONLY REQUIRED FOR SetProcessDpiAwareness
-      //#include <windows.h>
-      //#include <ShellScalingAPI.h> THIS IS ONLY REQUIRED FOR SetProcessDpiAwareness
-      //#include <comdef.h> THIS IS ONLY REQUIRED FOR SetProcessDpiAwareness
-      // SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
-      
-      // This is supported on Vista+
-      SetProcessDPIAware();
-
-      windowFlags |= SDL_WINDOW_ALLOW_HIGHDPI;
-    }
-#endif 
-// WIN32
-    
-    window_ = SDL_CreateWindow(title,
-                               SDL_WINDOWPOS_UNDEFINED,
-                               SDL_WINDOWPOS_UNDEFINED,
-                               width, height, windowFlags);
-
-    if (window_ == NULL) 
-    {
-      LOG(ERROR) << "Cannot create the SDL window: " << SDL_GetError();
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-
-    renderer_ = SDL_CreateRenderer(window_, -1, rendererFlags);
-    if (!renderer_)
-    {
-      LOG(ERROR) << "Cannot create the SDL renderer: " << SDL_GetError();
-      SDL_DestroyWindow(window_);
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  }
-
-
-  SdlWindow::~SdlWindow()
-  {
-    if (renderer_ != NULL)
-    { 
-      SDL_DestroyRenderer(renderer_);
-    }
-
-    if (window_ != NULL)
-    { 
-      SDL_DestroyWindow(window_);
-    }
-  }
-
-
-  unsigned int SdlWindow::GetWidth() const
-  {
-    int w = -1;
-    SDL_GetWindowSize(window_, &w, NULL);
-
-    if (w < 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-    else
-    {
-      return static_cast<unsigned int>(w);
-    }
-  }
-
-
-  unsigned int SdlWindow::GetHeight() const
-  {
-    int h = -1;
-    SDL_GetWindowSize(window_, NULL, &h);
-
-    if (h < 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-    else
-    {
-      return static_cast<unsigned int>(h);
-    }
-  }
-
-
-  void SdlWindow::Render(SDL_Surface* surface)
-  {
-    /**
-     * "You are strongly encouraged to call SDL_RenderClear() to
-     * initialize the backbuffer before starting each new frame's
-     * drawing, even if you plan to overwrite every pixel."
-     * https://wiki.libsdl.org/SDL_RenderPresent
-     **/
-    SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
-    SDL_RenderClear(renderer_);  // Clear the entire screen to our selected color
-
-    SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer_, surface);
-    if (texture != NULL)
-    {
-      SDL_RenderCopy(renderer_, texture, NULL, NULL);
-      SDL_DestroyTexture(texture);
-    }
-
-    SDL_RenderPresent(renderer_);
-  }
-
-
-  void SdlWindow::ToggleMaximize()
-  {
-    if (maximized_)
-    {
-      SDL_RestoreWindow(window_);
-      maximized_ = false;
-    }
-    else
-    {
-      SDL_MaximizeWindow(window_);
-      maximized_ = true;
-    }
-  }
-
-
-  void SdlWindow::GlobalInitialize()
-  {
-    if (SDL_Init(SDL_INIT_VIDEO) != 0)
-    {
-      LOG(ERROR) << "Cannot initialize SDL";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  }
-
-
-  void SdlWindow::GlobalFinalize()
-  {
-    SDL_Quit();
-  }
-}
-
-#endif
--- a/OrthancStone/Sources/Viewport/SdlWindow.h	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#if ORTHANC_ENABLE_SDL == 1
-
-#include <boost/noncopyable.hpp>
-
-// Forward declaration of SDL type to avoid clashes with DCMTK headers
-// on "typedef Sint8", in "StoneInitialization.cpp"
-struct SDL_Window;
-struct SDL_Renderer;
-struct SDL_Surface;
-
-namespace OrthancStone
-{
-  class SdlWindow : public boost::noncopyable
-  {
-  private:
-    struct SDL_Window   *window_;
-    struct SDL_Renderer *renderer_;
-    bool                 maximized_;
-
-  public:
-    SdlWindow(const char* title,
-              unsigned int width,
-              unsigned int height,
-              bool enableOpenGl,
-              bool allowDpiScaling = true);
-
-    ~SdlWindow();
-
-    SDL_Window *GetObject() const
-    {
-      return window_;
-    }
-
-    unsigned int GetWidth() const;
-
-    unsigned int GetHeight() const;
-
-    /**
-     * WARNING: "Refresh()" cannot only be called from the main SDL
-     * thread, in which the window was created. Otherwise, the
-     * renderer displays nothing!
-     **/
-    void Render(struct SDL_Surface* surface);
-
-    void ToggleMaximize();
-
-    static void GlobalInitialize();
-
-    static void GlobalFinalize();
-  };
-}
-
-#endif
--- a/OrthancStone/Sources/Viewport/WebAssemblyCairoViewport.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,113 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1
-#  include "WebAssemblyCairoViewport.h"
-#  include "../Scene2D/CairoCompositor.h"
-#  include "../Scene2DViewport/ViewportController.h"
-#else
-// This is the case when using the WebAssembly side module, and this
-// source file must be compiled within the WebAssembly main module
-#  include <Viewport/WebAssemblyCairoViewport.h>
-#  include <Scene2D/CairoCompositor.h>
-#  include <Scene2DViewport/ViewportController.h>
-#endif
-
-
-#include <Images/Image.h>
-
-namespace OrthancStone
-{
-  void WebAssemblyCairoViewport::Paint(ICompositor& compositor,
-                                       ViewportController& controller)
-  {
-    compositor.Refresh(controller.GetScene());
-
-    // Create a temporary memory buffer for the canvas in JavaScript
-    Orthanc::ImageAccessor cairo;
-    dynamic_cast<CairoCompositor&>(compositor).GetCanvas().GetReadOnlyAccessor(cairo);
-
-    const unsigned int width = cairo.GetWidth();
-    const unsigned int height = cairo.GetHeight();
-
-    if (javascript_.get() == NULL ||
-        javascript_->GetWidth() != width ||
-        javascript_->GetHeight() != height)
-    {
-      javascript_.reset(new Orthanc::Image(Orthanc::PixelFormat_RGBA32, width, height,
-                                           true /* force minimal pitch */));
-    }
-      
-    // Convert from BGRA32 memory layout (only color mode supported
-    // by Cairo, which corresponds to CAIRO_FORMAT_ARGB32) to RGBA32
-    // (as expected by HTML5 canvas). This simply amounts to
-    // swapping the B and R channels. Alpha channel is also set to
-    // full opacity (255).
-    uint8_t* q = reinterpret_cast<uint8_t*>(javascript_->GetBuffer());
-    for (unsigned int y = 0; y < height; y++)
-    {
-      const uint8_t* p = reinterpret_cast<const uint8_t*>(cairo.GetConstRow(y));
-      for (unsigned int x = 0; x < width; x++)
-      {
-        q[0] = p[2];  // R
-        q[1] = p[1];  // G
-        q[2] = p[0];  // B
-        q[3] = 255;   // A
-
-        p += 4;
-        q += 4;
-      }
-    }
-
-    // Execute JavaScript commands to blit the image buffer onto the
-    // 2D drawing context of the HTML5 canvas
-    EM_ASM({
-        const data = new Uint8ClampedArray(Module.HEAP8.buffer, $1, 4 * $2 * $3);
-        const img = new ImageData(data, $2, $3);
-        const ctx = document.getElementById(UTF8ToString($0)).getContext('2d');
-        ctx.putImageData(img, 0, 0);
-      },
-      GetCanvasId().c_str(), // $0
-      javascript_->GetBuffer(),   // $1
-      javascript_->GetWidth(),    // $2
-      javascript_->GetHeight());  // $3
-  }
-    
-
-  WebAssemblyCairoViewport::WebAssemblyCairoViewport(const std::string& canvasId,
-                                                     bool enableEmscriptenMouseEvents) :
-    WebAssemblyViewport(canvasId,enableEmscriptenMouseEvents)
-  {
-    RefreshCanvasSize();
-    AcquireCompositor(new CairoCompositor(GetCanvasWidth(), GetCanvasHeight()));
-  }
-  
-
-  boost::shared_ptr<WebAssemblyCairoViewport> WebAssemblyCairoViewport::Create(
-    const std::string& canvasId, bool enableEmscriptenMouseEvents)
-  {
-    boost::shared_ptr<WebAssemblyCairoViewport> that = boost::shared_ptr<WebAssemblyCairoViewport>(
-        new WebAssemblyCairoViewport(canvasId, enableEmscriptenMouseEvents));
-    
-    that->WebAssemblyViewport::PostConstructor();
-    return that;
-  }
-}
--- a/OrthancStone/Sources/Viewport/WebAssemblyCairoViewport.h	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "WebAssemblyViewport.h"
-
-namespace OrthancStone
-{
-  class WebAssemblyCairoViewport : public WebAssemblyViewport
-  {
-  private:
-    std::unique_ptr<Orthanc::ImageAccessor>  javascript_;
-        
-    WebAssemblyCairoViewport(const std::string& canvasId,
-                             bool enableEmscriptenMouseEvents);
-
-  protected:
-    virtual void Paint(ICompositor& compositor,
-                       ViewportController& controller) ORTHANC_OVERRIDE;
-    
-  public:
-    static boost::shared_ptr<WebAssemblyCairoViewport> Create(const std::string& canvasId,
-                                                              bool enableEmscriptenMouseEvents = true);
-
-    virtual ~WebAssemblyCairoViewport()
-    {
-      ClearCompositor();
-    }
-  };
-}
--- a/OrthancStone/Sources/Viewport/WebAssemblyViewport.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,399 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1
-#  include "WebAssemblyViewport.h"
-#  include "DefaultViewportInteractor.h"
-#  include "../Toolbox/GenericToolbox.h"
-#  include "../Scene2DViewport/ViewportController.h"
-#else
-// This is the case when using the WebAssembly side module, and this
-// source file must be compiled within the WebAssembly main module
-#  include <Viewport/WebAssemblyViewport.h>
-#  include <Toolbox/GenericToolbox.h>
-#  include <Scene2DViewport/ViewportController.h>
-#  include <Viewport/DefaultViewportInteractor.h>
-#endif
-
-
-#include <OrthancException.h>
-
-#include <boost/make_shared.hpp>
-#include <boost/enable_shared_from_this.hpp>
-#include <boost/math/special_functions/round.hpp>
-
-namespace OrthancStone
-{
-  static void ConvertMouseEvent(PointerEvent& target,
-                                const EmscriptenMouseEvent& source,
-                                const ICompositor& compositor)
-  {
-    int x = static_cast<int>(source.targetX);
-    int y = static_cast<int>(source.targetY);
-
-    switch (source.button)
-    {
-      case 0:
-        target.SetMouseButton(MouseButton_Left);
-        break;
-
-      case 1:
-        target.SetMouseButton(MouseButton_Middle);
-        break;
-
-      case 2:
-        target.SetMouseButton(MouseButton_Right);
-        break;
-
-      default:
-        target.SetMouseButton(MouseButton_None);
-        break;
-    }
-      
-    target.AddPosition(compositor.GetPixelCenterCoordinates(x, y));
-    target.SetAltModifier(source.altKey);
-    target.SetControlModifier(source.ctrlKey);
-    target.SetShiftModifier(source.shiftKey);
-  }
-
-
-  class WebAssemblyViewport::WasmLock : public ILock
-  {
-  private:
-    WebAssemblyViewport& that_;
-
-  public:
-    WasmLock(WebAssemblyViewport& that) :
-      that_(that)
-    {
-    }
-
-    virtual bool HasCompositor() const ORTHANC_OVERRIDE
-    {
-      return that_.compositor_.get() != NULL;
-    }
-
-    virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE
-    {
-      if (that_.compositor_.get() == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        return *that_.compositor_;
-      }
-    }
-
-    virtual ViewportController& GetController() ORTHANC_OVERRIDE
-    {
-      assert(that_.controller_);
-      return *that_.controller_;
-    }
-
-    virtual void Invalidate() ORTHANC_OVERRIDE
-    {
-      that_.Invalidate();
-    }
-
-    virtual void RefreshCanvasSize() ORTHANC_OVERRIDE
-    {
-      that_.RefreshCanvasSize();
-    }
-  };
-
-
-  EM_BOOL WebAssemblyViewport::OnRequestAnimationFrame(double time, void *userData)
-  {
-    LOG(TRACE) << __func__;
-    WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData);
-
-    if (that->compositor_.get() != NULL &&
-        that->controller_ /* should always be true */)
-    {
-      that->Paint(*that->compositor_, *that->controller_);
-    }
-      
-    LOG(TRACE) << "Exiting: " << __func__;
-    return true;
-  }
-
-  EM_BOOL WebAssemblyViewport::OnResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData)
-  {
-    LOG(TRACE) << __func__;
-    WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData);
-
-    if (that->compositor_.get() != NULL)
-    {
-      that->RefreshCanvasSize();
-      that->Invalidate();
-    }
-      
-    LOG(TRACE) << "Exiting: " << __func__;
-    return true;
-  }
-
-
-  EM_BOOL WebAssemblyViewport::OnMouseDown(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
-  {
-    WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData);
-
-    LOG(TRACE) << "mouse down: " << that->GetCanvasCssSelector();      
-
-    if (that->compositor_.get() != NULL &&
-        that->interactor_.get() != NULL)
-    {
-      PointerEvent pointer;
-      ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_);
-
-      that->controller_->HandleMousePress(*that->interactor_, pointer,
-                                         that->compositor_->GetCanvasWidth(),
-                                         that->compositor_->GetCanvasHeight());        
-      that->Invalidate();
-    }
-
-    LOG(TRACE) << "Exiting: " << __func__;
-    return true;
-  }
-
-    
-  EM_BOOL WebAssemblyViewport::OnMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
-  {
-    WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData);
-
-    if (that->compositor_.get() != NULL &&
-        that->controller_->HasActiveTracker())
-    {
-      PointerEvent pointer;
-      ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_);
-      if (that->controller_->HandleMouseMove(pointer))
-      {
-        that->Invalidate();
-      }
-    }
-
-    LOG(TRACE) << "Exiting: " << __func__;
-    return true;
-  }
-    
-  EM_BOOL WebAssemblyViewport::OnMouseUp(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
-  {
-    LOG(TRACE) << __func__;
-    WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData);
-
-    if (that->compositor_.get() != NULL)
-    {
-      PointerEvent pointer;
-      ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_);
-      that->controller_->HandleMouseRelease(pointer);
-      that->Invalidate();
-    }
-
-    LOG(TRACE) << "Exiting: " << __func__;
-    return true;
-  }
-
-  void WebAssemblyViewport::Invalidate()
-  {
-    emscripten_request_animation_frame(OnRequestAnimationFrame, reinterpret_cast<void*>(this));
-  }
-
-  void WebAssemblyViewport::FitForPrint()
-  {
-    if (compositor_.get() != NULL &&
-        controller_ /* should always be true */)
-    {
-      RefreshCanvasSize();
-      compositor_->FitContent(controller_->GetScene());
-      OnRequestAnimationFrame(0, reinterpret_cast<void*>(this));  // Mandatory to work with Firefox
-    }
-  }
-
-  void WebAssemblyViewport::AcquireCompositor(ICompositor* compositor /* takes ownership */)
-  {
-    if (compositor == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-    else
-    {
-      compositor_.reset(compositor);
-    }
-  }
-
-#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR == 1
-// everything OK..... we're using the new setting
-#else
-#pragma message("WARNING: DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR is not defined or equal to 0. Stone will use the OLD Emscripten rules for DOM element selection.")
-#endif
-
-  WebAssemblyViewport::WebAssemblyViewport(
-    const std::string& canvasId, bool enableEmscriptenMouseEvents) :
-    canvasId_(canvasId),
-#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR == 1
-    canvasCssSelector_("#" + canvasId),
-#else
-    canvasCssSelector_(canvasId),
-#endif
-    interactor_(new DefaultViewportInteractor),
-    enableEmscriptenMouseEvents_(enableEmscriptenMouseEvents),
-    canvasWidth_(0),
-    canvasHeight_(0)
-  {
-  }
-
-  void WebAssemblyViewport::PostConstructor()
-  {
-    boost::shared_ptr<IViewport> viewport = shared_from_this();
-    controller_.reset(new ViewportController(viewport));
-
-    LOG(INFO) << "Initializing Stone viewport on HTML canvas: " 
-              << canvasId_;
-
-    if (canvasId_.empty() ||
-        canvasId_[0] == '#')
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
-        "The canvas identifier must not start with '#'");
-    }
-
-    // Disable right-click on the canvas (i.e. context menu)
-    EM_ASM({
-        document.getElementById(UTF8ToString($0)).oncontextmenu = 
-        function(event)
-        {
-          event.preventDefault();
-        }
-      },
-      canvasId_.c_str()   // $0
-      );
-
-    // It is not possible to monitor the resizing of individual
-    // canvas, so we track the full window of the browser
-    emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW,
-                                   reinterpret_cast<void*>(this),
-                                   false,
-                                   OnResize);
-
-    if (enableEmscriptenMouseEvents_)
-    {
-
-      // if any of this function causes an error in the console, please
-      // make sure you are using the new (as of 1.39.x) version of 
-      // emscripten element lookup rules( pass 
-      // "-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1" to the linker.
-
-      emscripten_set_mousedown_callback(canvasCssSelector_.c_str(),
-                                        reinterpret_cast<void*>(this),
-                                        false,
-                                        OnMouseDown);
-
-      emscripten_set_mousemove_callback(canvasCssSelector_.c_str(),
-                                        reinterpret_cast<void*>(this),
-                                        false,
-                                        OnMouseMove);
-
-      emscripten_set_mouseup_callback(canvasCssSelector_.c_str(),
-                                      reinterpret_cast<void*>(this),
-                                      false,
-                                      OnMouseUp);
-    }
-  }
-
-  WebAssemblyViewport::~WebAssemblyViewport()
-  {
-    emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW,
-                                   reinterpret_cast<void*>(this),
-                                   false,
-                                   NULL);
-
-    if (enableEmscriptenMouseEvents_)
-    {
-
-      emscripten_set_mousedown_callback(canvasCssSelector_.c_str(),
-                                        reinterpret_cast<void*>(this),
-                                        false,
-                                        NULL);
-
-      emscripten_set_mousemove_callback(canvasCssSelector_.c_str(),
-                                        reinterpret_cast<void*>(this),
-                                        false,
-                                        NULL);
-
-      emscripten_set_mouseup_callback(canvasCssSelector_.c_str(),
-                                      reinterpret_cast<void*>(this),
-                                      false,
-                                      NULL);
-    }
-  }
-  
-  IViewport::ILock* WebAssemblyViewport::Lock()
-  {
-    return new WasmLock(*this);
-  }
-  
-  void WebAssemblyViewport::AcquireInteractor(IViewportInteractor* interactor)
-  {
-    if (interactor == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-    else
-    {
-      interactor_.reset(interactor);
-    }
-  }
-
-
-  void WebAssemblyViewport::RefreshCanvasSize()
-  {
-    double w, h;
-    emscripten_get_element_css_size(GetCanvasCssSelector().c_str(), &w, &h);
-
-    /**
-     * Emscripten has the function emscripten_get_element_css_size()
-     * to query the width and height of a named HTML element. I'm
-     * calling this first to get the initial size of the canvas DOM
-     * element, and then call emscripten_set_canvas_size() to
-     * initialize the framebuffer size of the canvas to the same
-     * size as its DOM element.
-     * https://floooh.github.io/2017/02/22/emsc-html.html
-     **/
-    if (w > 0 &&
-        h > 0)
-    {
-      canvasWidth_ = static_cast<unsigned int>(boost::math::iround(w));
-      canvasHeight_ = static_cast<unsigned int>(boost::math::iround(h));
-    }
-    else
-    {
-      canvasWidth_ = 0;
-      canvasHeight_ = 0;
-    }
-
-    emscripten_set_canvas_element_size(GetCanvasCssSelector().c_str(), canvasWidth_, canvasHeight_);
-
-    if (compositor_.get() != NULL)
-    {
-      compositor_->SetCanvasSize(canvasWidth_, canvasHeight_);
-    }
-  }
-}
--- a/OrthancStone/Sources/Viewport/WebAssemblyViewport.h	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,139 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../OrthancStone.h"
-
-#if !defined(ORTHANC_ENABLE_WASM)
-#  error Macro ORTHANC_ENABLE_WASM must be defined
-#endif
-
-#if ORTHANC_ENABLE_WASM != 1
-#  error This file can only be used if targeting WebAssembly
-#endif
-
-#include "IViewport.h"
-#include "IViewportInteractor.h"
-
-#include <Compatibility.h>
-
-#include <emscripten.h>
-#include <emscripten/html5.h>
-
-#include <memory>
-#include <string>
-#include <boost/enable_shared_from_this.hpp>
-
-namespace OrthancStone
-{
-  class WebAssemblyViewport : public IViewport,
-                              public boost::enable_shared_from_this<WebAssemblyViewport>
-
-  {
-  private:
-    class WasmLock;
-    
-    std::string                           canvasId_;
-    std::string                           canvasCssSelector_;
-    std::unique_ptr<ICompositor>          compositor_;
-    std::unique_ptr<ViewportController>   controller_;
-    std::unique_ptr<IViewportInteractor>  interactor_;
-    bool                                  enableEmscriptenMouseEvents_;
-    unsigned int                          canvasWidth_;
-    unsigned int                          canvasHeight_;
-
-    static EM_BOOL OnRequestAnimationFrame(double time, void *userData);
-    
-    static EM_BOOL OnResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData);
-
-    static EM_BOOL OnMouseDown(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData);
-    
-    static EM_BOOL OnMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData);
-    
-    static EM_BOOL OnMouseUp(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData);
-
-  protected:
-    void Invalidate();
-    
-    void ClearCompositor()
-    {
-      compositor_.reset();
-    }
-
-    bool HasCompositor() const
-    {
-      return compositor_.get() != NULL;
-    }
-
-    void AcquireCompositor(ICompositor* compositor /* takes ownership */);
-
-    virtual void Paint(ICompositor& compositor,
-                       ViewportController& controller) = 0;
-
-    /**
-    The second argument is temporary and should be deleted once the migration 
-    to interactors is finished. It should be set to "true" for new applications.
-    */
-    WebAssemblyViewport(const std::string& canvasId, 
-                        bool enableEmscriptenMouseEvents);
-
-    void PostConstructor();
-
-    void RefreshCanvasSize();
-
-    unsigned int GetCanvasWidth() const
-    {
-      return canvasWidth_;
-    }
-    
-    unsigned int GetCanvasHeight()
-    {
-      return canvasHeight_;
-    }
-
-  public:
-    virtual ILock* Lock() ORTHANC_OVERRIDE;
-
-    ~WebAssemblyViewport();
-
-    /**
-    This method takes ownership
-    */
-    void AcquireInteractor(IViewportInteractor* interactor);
-
-    const std::string& GetCanvasId() const
-    {
-      return canvasId_;
-    }
-
-    /**
-    emscripten functions requires the css selector for the canvas. This is 
-    different from the canvas id (the syntax is '#mycanvasid')
-    */
-    const std::string& GetCanvasCssSelector() const
-    {
-      return canvasCssSelector_;
-    }
-
-    void FitForPrint();
-  };
-}
--- a/OrthancStone/Sources/Viewport/WebGLViewport.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "WebGLViewport.h"
-
-#include "../StoneException.h"
-#include "../Scene2D/OpenGLCompositor.h"
-#include "../Scene2DViewport/ViewportController.h"
-
-namespace OrthancStone
-{
-  void WebGLViewport::Paint(ICompositor& compositor,
-                            ViewportController& controller)
-  {
-    try
-    {
-      compositor.Refresh(controller.GetScene());
-
-      /**
-       * No need to manually swap the buffer: "Rendered WebGL content
-       * is implicitly presented (displayed to the user) on the canvas
-       * when the event handler that renders with WebGL returns back
-       * to the browser event loop."
-       * https://emscripten.org/docs/api_reference/html5.h.html#webgl-context
-       *
-       * Could call "emscripten_webgl_commit_frame()" if
-       * "explicitSwapControl" option were set to "true".
-       **/
-    }
-    catch (const StoneException& e)
-    {
-      // Ignore problems about the loss of the WebGL context (edge case)
-      if (e.GetErrorCode() == ErrorCode_WebGLContextLost)
-      {
-        return;
-      }
-      else
-      {
-        throw;
-      }
-    }
-  }
-    
-
-  WebGLViewport::WebGLViewport(const std::string& canvasId, bool enableEmscriptenMouseEvents) :
-    WebAssemblyViewport(canvasId,enableEmscriptenMouseEvents),
-    context_(GetCanvasCssSelector())
-  {
-    AcquireCompositor(new OpenGLCompositor(context_));
-  }
-
-  boost::shared_ptr<WebGLViewport> WebGLViewport::Create(
-    const std::string& canvasId, bool enableEmscriptenMouseEvents)
-  {
-    boost::shared_ptr<WebGLViewport> that = boost::shared_ptr<WebGLViewport>(
-        new WebGLViewport(canvasId, enableEmscriptenMouseEvents));
-    
-    that->WebAssemblyViewport::PostConstructor();
-    return that;
-  }
-
-  WebGLViewport::~WebGLViewport()
-  {
-    // Make sure to delete the compositor before its parent "context_" gets
-    // deleted
-    ClearCompositor();
-  }
-}
--- a/OrthancStone/Sources/Viewport/WebGLViewport.h	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "WebAssemblyViewport.h"
-#include "../OpenGL/WebAssemblyOpenGLContext.h"
-
-namespace OrthancStone
-{
-  class WebGLViewport : public WebAssemblyViewport
-  {
-  private:
-    OpenGL::WebAssemblyOpenGLContext  context_;
-    
-    WebGLViewport(const std::string& canvasId,
-                  bool enableEmscriptenMouseEvents);
-
-  protected:
-    virtual void Paint(ICompositor& compositor,
-                       ViewportController& controller) ORTHANC_OVERRIDE;
-    
-  public:
-    static boost::shared_ptr<WebGLViewport> Create(const std::string& canvasId,
-                                                   bool enableEmscriptenMouseEvents = true);
-
-    virtual ~WebGLViewport();
-
-    bool IsContextLost()
-    {
-      return context_.IsContextLost();
-    } 
-  };
-}
--- a/OrthancStone/Sources/Viewport/WebGLViewportsRegistry.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,186 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "WebGLViewportsRegistry.h"
-
-#include "../Toolbox/GenericToolbox.h"
-#include "../Scene2DViewport/ViewportController.h"
-
-#include <OrthancException.h>
-
-#include <boost/make_shared.hpp>
-
-namespace OrthancStone
-{
-  void WebGLViewportsRegistry::LaunchTimer()
-  {
-    timeOutID_ = emscripten_set_timeout(
-      OnTimeoutCallback, 
-      timeoutMS_, 
-      reinterpret_cast<void*>(this));
-  }
-  
-  void WebGLViewportsRegistry::OnTimeout()
-  {
-    for (Viewports::iterator it = viewports_.begin();
-         it != viewports_.end(); 
-         ++it)
-    {
-      if (it->second == NULL ||
-          it->second->IsContextLost())
-      {
-        LOG(INFO) << "WebGL context lost for canvas: " << it->first;
-
-        // Try and duplicate the HTML5 canvas in the DOM
-        EM_ASM({
-            var canvas = document.getElementById(UTF8ToString($0));
-            if (canvas) {
-              var parent = canvas.parentElement;
-              if (parent) {
-                var cloned = canvas.cloneNode(true /* deep copy */);
-                parent.insertBefore(cloned, canvas);
-                parent.removeChild(canvas);
-              }
-            }
-          },
-          it->first.c_str()  // $0 = ID of the canvas
-          );
-
-        // At this point, the old canvas is removed from the DOM and
-        // replaced by a fresh one with the same ID: Recreate the
-        // WebGL context on the new canvas
-        boost::shared_ptr<WebGLViewport> viewport;
-
-        // we need to steal the properties from the old viewport
-        // and set them to the new viewport
-        {
-          std::unique_ptr<IViewport::ILock> lock(it->second->Lock());
-
-          // TODO: remove ViewportController
-          Scene2D* scene = lock->GetController().ReleaseScene();
-          viewport = WebGLViewport::Create(it->first);
-
-          {
-            std::unique_ptr<IViewport::ILock> newLock(viewport->Lock());
-            newLock->GetController().AcquireScene(scene);
-          }
-        }
-
-        // Replace the old WebGL viewport by the new one
-        it->second = viewport;
-
-        // Tag the fresh canvas as needing a repaint
-        {
-          std::unique_ptr<IViewport::ILock> lock(it->second->Lock());
-          lock->Invalidate();
-        }
-      }
-    }
-      
-    LaunchTimer();
-  }
-
-  void WebGLViewportsRegistry::OnTimeoutCallback(void *userData)
-  {
-    // This object dies with the process or tab. 
-    WebGLViewportsRegistry* that = 
-      reinterpret_cast<WebGLViewportsRegistry*>(userData);
-    that->OnTimeout();
-  }
-
-  WebGLViewportsRegistry::WebGLViewportsRegistry(double timeoutMS) :
-    timeoutMS_(timeoutMS),
-    timeOutID_(0)
-  {
-    if (timeoutMS <= 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }          
-    
-    LaunchTimer();
-  }
-
-  WebGLViewportsRegistry::~WebGLViewportsRegistry()
-  {
-    emscripten_clear_timeout(timeOutID_);
-    Clear();
-  }
-
-  boost::shared_ptr<WebGLViewport> WebGLViewportsRegistry::Add(
-    const std::string& canvasId)
-  {
-    if (viewports_.find(canvasId) != viewports_.end())
-    {
-      LOG(ERROR) << "Canvas was already registered: " << canvasId;
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      boost::shared_ptr<WebGLViewport> viewport = 
-        WebGLViewport::Create(canvasId);
-      viewports_[canvasId] = viewport;
-      return viewport;
-    }
-  }
-      
-  void WebGLViewportsRegistry::Remove(const std::string& canvasId)
-  {
-    Viewports::iterator found = viewports_.find(canvasId);
-
-    if (found == viewports_.end())
-    {
-      LOG(ERROR) << "Cannot remove unregistered canvas: " << canvasId;
-    }
-    else
-    {
-      viewports_.erase(found);
-    }
-  }
-  
-  void WebGLViewportsRegistry::Clear()
-  {
-    viewports_.clear();
-  }
-
-  WebGLViewportsRegistry::Accessor::Accessor(WebGLViewportsRegistry& that,
-                                             const std::string& canvasId) :
-    that_(that)
-  {
-    Viewports::iterator viewport = that.viewports_.find(canvasId);
-    if (viewport != that.viewports_.end() &&
-        viewport->second != NULL)
-    {
-      lock_.reset(viewport->second->Lock());
-    }
-  }
-
-  IViewport::ILock& WebGLViewportsRegistry::Accessor::GetViewport() const
-  {
-    if (IsValid())
-    {
-      return *lock_;
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-  }
-}
--- a/OrthancStone/Sources/Viewport/WebGLViewportsRegistry.h	Thu Oct 22 18:39:03 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "WebGLViewport.h"
-
-#include <boost/enable_shared_from_this.hpp>
-
-namespace OrthancStone
-{
-  /**
-   * This singleton class must be used if many WebGL viewports are
-   * created by the higher-level application, implying possible loss
-   * of WebGL contexts. The object will run an infinite update loop
-   * that checks whether all the WebGL context are still valid (not
-   * lost). If some WebGL context is lost, it is automatically
-   * reinitialized by created a fresh HTML5 canvas.
-   **/  
-  class WebGLViewportsRegistry : public boost::noncopyable,
-    public boost::enable_shared_from_this<WebGLViewportsRegistry>
-  {
-  private:
-    typedef std::map<std::string, boost::shared_ptr<WebGLViewport> >  Viewports;
-
-    double     timeoutMS_;
-    Viewports  viewports_;
-    long       timeOutID_;
-
-    void LaunchTimer();
-
-    void OnTimeout();
-
-    static void OnTimeoutCallback(void *userData);
-    
-  public:
-    explicit WebGLViewportsRegistry(double timeoutMS /* in milliseconds */);
-    
-    ~WebGLViewportsRegistry();
-
-    boost::shared_ptr<WebGLViewport> Add(const std::string& canvasId);
-
-    void Remove(const std::string& canvasId);
-
-    void Clear();
-
-    class Accessor : public boost::noncopyable
-    {
-    private:
-      WebGLViewportsRegistry&            that_;
-      std::unique_ptr<IViewport::ILock>  lock_;
-
-    public:
-      Accessor(WebGLViewportsRegistry& that,
-               const std::string& canvasId);
-
-      bool IsValid() const
-      {
-        return lock_.get() != NULL;
-      }
-
-      IViewport::ILock& GetViewport() const;
-    };
-  };
-}
--- a/UnitTestsSources/UnitTestsMain.cpp	Thu Oct 22 18:39:03 2020 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Fri Oct 23 13:15:03 2020 +0200
@@ -23,9 +23,9 @@
 
 #include "../OrthancStone/Sources/StoneInitialization.h"
 #include "../OrthancStone/Sources/Toolbox/FiniteProjectiveCamera.h"
+#include "../OrthancStone/Sources/Toolbox/GenericToolbox.h"
 #include "../OrthancStone/Sources/Toolbox/GeometryToolbox.h"
 #include "../OrthancStone/Sources/Volumes/ImageBuffer3D.h"
-#include "../OrthancStone/Sources/Loaders/LoaderCache.h"
 
 #include <Images/ImageProcessing.h>
 #include <Logging.h>
@@ -811,69 +811,69 @@
   ASSERT_DOUBLE_EQ(1.3671875, v[1]); 
 }
 
-TEST(LoaderCache, NormalizeUuid)
+TEST(GenericToolbox, NormalizeUuid)
 {
   std::string ref("44ca5051-14ef-4d2f-8bd7-db20bfb61fbb");
 
   {
     std::string u("44ca5051-14ef-4d2f-8bd7-db20bfb61fbb");
-    OrthancStone::LoaderCache::NormalizeUuid(u);
+    OrthancStone::GenericToolbox::NormalizeUuid(u);
     ASSERT_EQ(ref, u);
   }
 
   // space left
   {
     std::string u("  44ca5051-14ef-4d2f-8bd7-db20bfb61fbb");
-    OrthancStone::LoaderCache::NormalizeUuid(u);
+    OrthancStone::GenericToolbox::NormalizeUuid(u);
     ASSERT_EQ(ref, u);
   }
 
   // space right
   {
     std::string u("44ca5051-14ef-4d2f-8bd7-db20bfb61fbb  ");
-    OrthancStone::LoaderCache::NormalizeUuid(u);
+    OrthancStone::GenericToolbox::NormalizeUuid(u);
     ASSERT_EQ(ref, u);
   }
 
   // space l & r 
   {
     std::string u("  44ca5051-14ef-4d2f-8bd7-db20bfb61fbb  ");
-    OrthancStone::LoaderCache::NormalizeUuid(u);
+    OrthancStone::GenericToolbox::NormalizeUuid(u);
     ASSERT_EQ(ref, u);
   }
 
   // space left + case 
   {
     std::string u("  44CA5051-14ef-4d2f-8bd7-dB20bfb61fbb");
-    OrthancStone::LoaderCache::NormalizeUuid(u);
+    OrthancStone::GenericToolbox::NormalizeUuid(u);
     ASSERT_EQ(ref, u);
   }
 
   // space right + case 
   {
     std::string u("44ca5051-14EF-4D2f-8bd7-db20bfb61fbB  ");
-    OrthancStone::LoaderCache::NormalizeUuid(u);
+    OrthancStone::GenericToolbox::NormalizeUuid(u);
     ASSERT_EQ(ref, u);
   }
 
   // space l & r + case
   {
     std::string u("  44cA5051-14Ef-4d2f-8bD7-db20bfb61fbb  ");
-    OrthancStone::LoaderCache::NormalizeUuid(u);
+    OrthancStone::GenericToolbox::NormalizeUuid(u);
     ASSERT_EQ(ref, u);
   }
 
   // no
   {
     std::string u("  44ca5051-14ef-4d2f-8bd7-  db20bfb61fbb");
-    OrthancStone::LoaderCache::NormalizeUuid(u);
+    OrthancStone::GenericToolbox::NormalizeUuid(u);
     ASSERT_NE(ref, u);
   }
 
   // no
   {
     std::string u("44ca5051-14ef-4d2f-8bd7-db20bfb61fb");
-    OrthancStone::LoaderCache::NormalizeUuid(u);
+    OrthancStone::GenericToolbox::NormalizeUuid(u);
     ASSERT_NE(ref, u);
   }
 }