changeset 576:529c9617654b

FontRenderer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 19 Apr 2019 15:11:16 +0200
parents 919226caca82
children b098a3aaf694
files .hgignore Framework/Fonts/FontRenderer.cpp Framework/Fonts/FontRenderer.h Framework/Fonts/Glyph.cpp Framework/Fonts/Glyph.h Framework/Fonts/GlyphAlphabet.cpp Framework/Fonts/GlyphAlphabet.h Framework/Toolbox/DynamicBitmap.cpp Framework/Toolbox/DynamicBitmap.h Resources/CMake/FreetypeConfiguration.cmake Resources/CMake/OrthancStoneConfiguration.cmake Resources/CMake/OrthancStoneParameters.cmake
diffstat 12 files changed, 892 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Fri Apr 19 09:08:15 2019 +0200
+++ b/.hgignore	Fri Apr 19 15:11:16 2019 +0200
@@ -1,28 +1,31 @@
-CMakeLists.txt.user
-Platforms/Generic/ThirdPartyDownloads/
-Applications/build-*
+syntax: glob
+*~
+*.cpp.orig
+*.h.orig
+.vs/
+.vscode/
 Applications/Qt/archive/
 Applications/Samples/ThirdPartyDownloads/
+Applications/Samples/build-wasm/
+Applications/Samples/build-web/
+Applications/Samples/node_modules/
+Applications/Samples/rt-viewer-demo/ThirdPartyDownloads/
 Applications/Samples/rt-viewer-demo/build-sdl-msvc15/
 Applications/Samples/rt-viewer-demo/build-tsc-output/
 Applications/Samples/rt-viewer-demo/build-wasm/
 Applications/Samples/rt-viewer-demo/build-web/
-Applications/Samples/rt-viewer-demo/ThirdPartyDownloads/
-Applications/Samples/build-wasm/
-Applications/Samples/build-web/
-Applications/Samples/node_modules/
-Resources/CommandTool/protoc-tests/node_modules/
-Resources/CommandTool/protoc-tests/generated_js/
-Resources/CommandTool/protoc-tests/generated_ts/
-Resources/CommandTool/flatc-tests/basic/build/
-.vscode/
+Applications/build-*
+CMakeLists.txt.user
+Platforms/Generic/ThirdPartyDownloads/
 Resources/CodeGeneration/__pycache__
 Resources/CodeGeneration/build/
 Resources/CodeGeneration/build_browser/
 Resources/CodeGeneration/testCppHandler/build/
 Resources/CodeGeneration/testCppHandler/build_msbuild/
-syntax: glob
-Resources/CodeGeneration/testWasmIntegrated/build-wasm/
+Resources/CodeGeneration/testWasmIntegrated/build-final/
 Resources/CodeGeneration/testWasmIntegrated/build-tsc/
-Resources/CodeGeneration/testWasmIntegrated/build-final/
-
+Resources/CodeGeneration/testWasmIntegrated/build-wasm/
+Resources/CommandTool/flatc-tests/basic/build/
+Resources/CommandTool/protoc-tests/generated_js/
+Resources/CommandTool/protoc-tests/generated_ts/
+Resources/CommandTool/protoc-tests/node_modules/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Fonts/FontRenderer.cpp	Fri Apr 19 15:11:16 2019 +0200
@@ -0,0 +1,185 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "FontRenderer.h"
+
+#include "../Toolbox/DynamicBitmap.h"
+
+#include <Core/OrthancException.h>
+
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+
+
+// https://stackoverflow.com/questions/31161284/how-can-i-get-the-corresponding-error-string-from-an-ft-error-code
+static std::string GetErrorMessage(FT_Error err)
+{
+#undef __FTERRORS_H__
+#define FT_ERRORDEF( e, v, s )  case e: return s;
+#define FT_ERROR_START_LIST     switch (err) {
+#define FT_ERROR_END_LIST       }
+#include FT_ERRORS_H
+  return "(Unknown error)";
+}
+
+
+static void CheckError(FT_Error err)
+{
+  if (err != 0)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                    "Error in FreeType: " + GetErrorMessage(err));
+  }
+}
+
+
+namespace OrthancStone
+{
+  class FontRenderer::PImpl : public boost::noncopyable
+  {
+  private:
+    std::string  fontContent_;
+    FT_Library   library_;
+    FT_Face      face_;
+
+    void Clear()
+    {
+      if (face_ != NULL)
+      {
+        FT_Done_Face(face_);        
+        face_ = NULL;
+      }
+
+      fontContent_.clear();
+    }
+
+  public:
+    PImpl() :
+      library_(NULL),
+      face_(NULL)
+    {
+      CheckError(FT_Init_FreeType(&library_));
+    }
+
+    
+    ~PImpl()
+    {
+      Clear();
+      FT_Done_FreeType(library_);
+    }
+
+    
+    void LoadFont(const std::string& fontContent,
+                  unsigned int fontSize)
+    {
+      Clear();
+
+      // It is necessary to make a private copy of the font, as
+      // Freetype makes the assumption that the buffer containing the
+      // font is never deleted
+      fontContent_.assign(fontContent);
+      
+      const FT_Byte* data = reinterpret_cast<const FT_Byte*>(fontContent_.c_str());
+
+      CheckError(FT_New_Memory_Face(library_, data, fontContent_.size(), 0, &face_));
+      CheckError(FT_Set_Char_Size(face_,         // handle to face object  
+                                  0,             // char_width in 1/64th of points  
+                                  fontSize * 64, // char_height in 1/64th of points 
+                                  72,            // horizontal device resolution 
+                                  72));          // vertical device resolution
+
+      CheckError(FT_Select_Charmap(face_, FT_ENCODING_UNICODE));
+    }
+    
+
+    Glyph* Render(uint32_t unicode)
+    {
+      if (face_ == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
+                                        "First call LoadFont()");
+      }
+      else if (FT_Load_Char(face_, unicode, FT_LOAD_RENDER) != 0)
+      {
+        // This character is not available
+        return NULL;
+      }
+      else
+      {
+        if (face_->glyph->format != FT_GLYPH_FORMAT_BITMAP)                 
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+          //CheckError(FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1));
+        }
+
+        Orthanc::ImageAccessor bitmap;
+        bitmap.AssignReadOnly(Orthanc::PixelFormat_Grayscale8,
+                              face_->glyph->bitmap.width,
+                              face_->glyph->bitmap.rows,
+                              face_->glyph->bitmap.pitch,
+                              face_->glyph->bitmap.buffer);
+
+        std::auto_ptr<Glyph> glyph(
+          new Glyph(bitmap.GetWidth(),
+                    bitmap.GetHeight(),
+                    face_->glyph->bitmap_left,
+                    -face_->glyph->bitmap_top,  // Positive for an upwards vertical distance
+                    face_->glyph->advance.x >> 6,
+                    face_->glyph->metrics.vertAdvance >> 6));
+
+        glyph->SetPayload(new DynamicBitmap(bitmap));
+        
+        return glyph.release();
+      }
+    }
+  };
+
+
+
+  FontRenderer::FontRenderer() :
+    pimpl_(new PImpl)
+  {
+  }
+
+  
+  void FontRenderer::LoadFont(const std::string& fontContent,
+                              unsigned int fontSize)
+  {
+    pimpl_->LoadFont(fontContent, fontSize);
+  }
+
+  
+  void FontRenderer::LoadFont(Orthanc::EmbeddedResources::FileResourceId resource,
+                              unsigned int fontSize)
+  {
+    std::string content;
+    Orthanc::EmbeddedResources::GetFileResource(content, resource);
+    LoadFont(content, fontSize);
+  }
+
+  
+  Glyph* FontRenderer::Render(uint32_t unicode)
+  {
+    return pimpl_->Render(unicode);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Fonts/FontRenderer.h	Fri Apr 19 15:11:16 2019 +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-2019 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 "Glyph.h"
+
+#include <EmbeddedResources.h>
+#include <boost/shared_ptr.hpp>
+
+
+namespace OrthancStone
+{
+  class FontRenderer : public boost::noncopyable
+  {
+  private:
+    class PImpl;
+    boost::shared_ptr<PImpl>  pimpl_;
+    
+  public:
+    FontRenderer();
+
+    void LoadFont(const std::string& fontContent,
+                  unsigned int fontSize);
+    
+    void LoadFont(Orthanc::EmbeddedResources::FileResourceId resource,
+                  unsigned int fontSize);
+
+    Glyph* Render(uint32_t unicode);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Fonts/Glyph.cpp	Fri Apr 19 15:11:16 2019 +0200
@@ -0,0 +1,93 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "Glyph.h"
+
+#include <Core/OrthancException.h>
+
+
+namespace OrthancStone
+{
+  Glyph::Glyph(const Glyph& other) : 
+    width_(other.width_),
+    height_(other.height_),
+    offsetLeft_(other.offsetLeft_),
+    offsetTop_(other.offsetTop_),
+    advanceX_(other.advanceX_),
+    lineHeight_(other.lineHeight_)
+  {
+  }
+  
+    
+  Glyph::Glyph(unsigned int width,
+               unsigned int height,
+               int offsetLeft,
+               int offsetTop,
+               int advanceX,
+               unsigned int lineHeight) :
+    width_(width),
+    height_(height),
+    offsetLeft_(offsetLeft),
+    offsetTop_(offsetTop),
+    advanceX_(advanceX),
+    lineHeight_(lineHeight)
+  {
+  }
+
+  
+  void Glyph::SetPayload(Orthanc::IDynamicObject* payload)  // Takes ownership
+  {
+    if (payload == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+    else
+    {
+      payload_.reset(payload);
+    }
+  }
+
+      
+  const Orthanc::IDynamicObject& Glyph::GetPayload() const
+  {
+    if (payload_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return *payload_;
+    }
+  }
+
+  
+  Orthanc::IDynamicObject* Glyph::ReleasePayload()
+  {
+    if (payload_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return payload_.release();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Fonts/Glyph.h	Fri Apr 19 15:11:16 2019 +0200
@@ -0,0 +1,95 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 <Core/IDynamicObject.h>
+
+#include <memory>
+
+
+namespace OrthancStone
+{
+  class Glyph : public boost::noncopyable
+  {
+  private:
+    unsigned int   width_;
+    unsigned int   height_;
+    int            offsetLeft_;
+    int            offsetTop_;
+    int            advanceX_;
+    unsigned int   lineHeight_;
+      
+    std::auto_ptr<Orthanc::IDynamicObject>  payload_;
+
+  public:
+    // WARNING: This does not copy the payload
+    Glyph(const Glyph& other);
+    
+    Glyph(unsigned int width,
+          unsigned int height,
+          int offsetLeft,
+          int offsetTop,
+          int advanceX,
+          unsigned int lineHeight);
+
+    void SetPayload(Orthanc::IDynamicObject* payload);
+
+    int GetOffsetLeft() const
+    {
+      return offsetLeft_;
+    }
+
+    int GetOffsetTop() const
+    {
+      return offsetTop_;
+    }
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    unsigned int GetAdvanceX() const
+    {
+      return advanceX_;
+    }
+
+    unsigned int GetLineHeight() const
+    {
+      return lineHeight_;
+    }
+
+    bool HasPayload() const
+    {
+      return payload_.get() != NULL;
+    }
+      
+    const Orthanc::IDynamicObject& GetPayload() const;
+      
+    Orthanc::IDynamicObject* ReleasePayload();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Fonts/GlyphAlphabet.cpp	Fri Apr 19 15:11:16 2019 +0200
@@ -0,0 +1,170 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "GlyphAlphabet.h"
+
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+
+namespace OrthancStone
+{
+  void GlyphAlphabet::Clear()
+  {
+    for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      delete it->second;
+    }
+      
+    content_.clear();
+    lineHeight_ = 0;
+  }
+    
+    
+  void GlyphAlphabet::Register(uint32_t unicode,
+                               const Glyph& glyph,
+                               Orthanc::IDynamicObject* payload)
+  {
+    std::auto_ptr<Orthanc::IDynamicObject> protection(payload);
+      
+    // Don't add twice the same character
+    if (content_.find(unicode) == content_.end())
+    {
+      std::auto_ptr<Glyph> raii(new Glyph(glyph));
+        
+      if (payload != NULL)
+      {
+        raii->SetPayload(protection.release());
+      }
+
+      content_[unicode] = raii.release();
+
+      lineHeight_ = std::max(lineHeight_, glyph.GetLineHeight());
+    }
+  }
+
+
+  void GlyphAlphabet::Register(FontRenderer& renderer,
+                               uint32_t unicode)
+  {
+    std::auto_ptr<Glyph>  glyph(renderer.Render(unicode));
+      
+    if (glyph.get() != NULL)
+    {
+      Register(unicode, *glyph, glyph->ReleasePayload());
+    }
+  }
+    
+
+#if ORTHANC_ENABLE_LOCALE == 1
+  bool GlyphAlphabet::GetUnicodeFromCodepage(uint32_t& unicode,
+                                             unsigned int index,
+                                             Orthanc::Encoding encoding)
+  {
+    if (index > 255)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+      
+    std::string character;
+    character.resize(1);
+    character[0] = static_cast<unsigned char>(index);
+    
+    std::string utf8 = Orthanc::Toolbox::ConvertToUtf8(character, encoding, false /* no code extensions */);
+      
+    if (utf8.empty())
+    {
+      // This character is not available in this codepage
+      return false;
+    }
+    else
+    {
+      size_t length;
+      Orthanc::Toolbox::Utf8ToUnicodeCharacter(unicode, length, utf8, 0);
+      assert(length != 0);
+      return true;
+    }
+  }
+#endif
+
+
+  void GlyphAlphabet::Apply(IGlyphVisitor& visitor) const
+  {
+    for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      visitor.Visit(it->first, *it->second);
+    }
+  }
+
+
+  void GlyphAlphabet::Apply(ITextVisitor& visitor,
+                            const std::string& utf8) const
+  {
+    size_t pos = 0;
+    int x = 0;
+    int y = 0;
+
+    while (pos < utf8.size())
+    {
+      if (utf8[pos] == '\r')
+      {
+        // Ignore carriage return
+        pos++;
+      }
+      else if (utf8[pos] == '\n')
+      {
+        // This is a newline character
+        x = 0;
+        y += static_cast<int>(lineHeight_);
+
+        pos++;
+      }
+      else
+      {         
+        uint32_t unicode;
+        size_t length;
+        Orthanc::Toolbox::Utf8ToUnicodeCharacter(unicode, length, utf8, pos);
+
+        Content::const_iterator glyph = content_.find(unicode);
+
+        if (glyph != content_.end())
+        {
+          assert(glyph->second != NULL);
+          const Orthanc::IDynamicObject* payload =
+            (glyph->second->HasPayload() ? &glyph->second->GetPayload() : NULL);
+            
+          visitor.Visit(unicode,
+                        x + glyph->second->GetOffsetLeft(),
+                        y + glyph->second->GetOffsetTop(),
+                        glyph->second->GetWidth(),
+                        glyph->second->GetHeight(),
+                        payload);
+          x += glyph->second->GetAdvanceX();
+        }
+        
+        assert(length != 0);
+        pos += length;
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Fonts/GlyphAlphabet.h	Fri Apr 19 15:11:16 2019 +0200
@@ -0,0 +1,105 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "FontRenderer.h"
+
+#include <Core/Enumerations.h>
+
+#include <map>
+
+namespace OrthancStone
+{
+  class GlyphAlphabet : public boost::noncopyable
+  {
+  public:
+    class ITextVisitor : public boost::noncopyable
+    {
+    public:
+      virtual ~ITextVisitor()
+      {
+      }
+
+      virtual void Visit(uint32_t unicode,
+                         int x,
+                         int y,
+                         unsigned int width,
+                         unsigned int height,
+                         const Orthanc::IDynamicObject* payload /* can be NULL */) = 0;
+    };
+
+
+    class IGlyphVisitor : public boost::noncopyable
+    {
+    public:
+      virtual ~IGlyphVisitor()
+      {
+      }
+
+      virtual void Visit(uint32_t unicode,
+                         const Glyph& glyph) = 0;
+    };
+
+    
+  private:
+    typedef std::map<uint32_t, Glyph*>  Content;
+
+    Content        content_;
+    unsigned int   lineHeight_;
+
+  public:
+    GlyphAlphabet() :
+      lineHeight_(0)
+    {
+    }
+
+    ~GlyphAlphabet()
+    {
+      Clear();
+    }
+    
+    void Clear();
+    
+    void Register(uint32_t unicode,
+                  const Glyph& glyph,
+                  Orthanc::IDynamicObject* payload);
+
+    void Register(FontRenderer& renderer,
+                  uint32_t unicode);
+
+#if ORTHANC_ENABLE_LOCALE == 1
+    static bool GetUnicodeFromCodepage(uint32_t& unicode,
+                                       unsigned int index,
+                                       Orthanc::Encoding encoding);
+#endif
+
+    size_t GetSize() const
+    {
+      return content_.size();
+    }
+
+    void Apply(IGlyphVisitor& visitor) const;
+
+    void Apply(ITextVisitor& visitor,
+               const std::string& utf8) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DynamicBitmap.cpp	Fri Apr 19 15:11:16 2019 +0200
@@ -0,0 +1,37 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "DynamicBitmap.h"
+
+#include <Core/Images/Image.h>
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  DynamicBitmap::DynamicBitmap(const Orthanc::ImageAccessor& bitmap) :
+    bitmap_(Orthanc::Image::Clone(bitmap))
+  {
+    if (bitmap_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DynamicBitmap.h	Fri Apr 19 15:11:16 2019 +0200
@@ -0,0 +1,44 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 <Core/IDynamicObject.h>
+#include <Core/Images/ImageAccessor.h>
+
+#include <memory>
+
+namespace OrthancStone
+{
+  class DynamicBitmap : public Orthanc::IDynamicObject
+  {
+  private:
+    std::auto_ptr<Orthanc::ImageAccessor>  bitmap_;
+
+  public:
+    DynamicBitmap(const Orthanc::ImageAccessor& bitmap);
+
+    const Orthanc::ImageAccessor& GetBitmap() const
+    {
+      return *bitmap_;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/FreetypeConfiguration.cmake	Fri Apr 19 15:11:16 2019 +0200
@@ -0,0 +1,87 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_FREETYPE)
+  set(FREETYPE_SOURCES_DIR ${CMAKE_BINARY_DIR}/freetype-2.9.1)
+  set(FREETYPE_URL "http://orthanc.osimis.io/ThirdPartyDownloads/freetype-2.9.1.tar.gz")
+  set(FREETYPE_MD5 "3adb0e35d3c100c456357345ccfa8056")
+
+  DownloadPackage(${FREETYPE_MD5} ${FREETYPE_URL} "${FREETYPE_SOURCES_DIR}")
+
+  include_directories(BEFORE
+    ${FREETYPE_SOURCES_DIR}/include/
+    )
+
+  add_definitions(
+    -DFT2_BUILD_LIBRARY
+    -DFT_CONFIG_OPTION_NO_ASSEMBLER
+    )
+    
+  set(FREETYPE_SOURCES
+    ${FREETYPE_SOURCES_DIR}/src/autofit/autofit.c
+    ${FREETYPE_SOURCES_DIR}/src/base/ftbase.c
+    ${FREETYPE_SOURCES_DIR}/src/base/ftbbox.c
+    ${FREETYPE_SOURCES_DIR}/src/base/ftbdf.c
+    ${FREETYPE_SOURCES_DIR}/src/base/ftbitmap.c
+    ${FREETYPE_SOURCES_DIR}/src/base/ftcid.c
+    ${FREETYPE_SOURCES_DIR}/src/base/ftfstype.c
+    ${FREETYPE_SOURCES_DIR}/src/base/ftgasp.c
+    ${FREETYPE_SOURCES_DIR}/src/base/ftglyph.c
+    ${FREETYPE_SOURCES_DIR}/src/base/ftgxval.c
+    ${FREETYPE_SOURCES_DIR}/src/base/ftinit.c
+    ${FREETYPE_SOURCES_DIR}/src/base/ftmm.c
+    ${FREETYPE_SOURCES_DIR}/src/base/ftotval.c
+    ${FREETYPE_SOURCES_DIR}/src/base/ftpatent.c
+    ${FREETYPE_SOURCES_DIR}/src/base/ftpfr.c
+    ${FREETYPE_SOURCES_DIR}/src/base/ftstroke.c
+    ${FREETYPE_SOURCES_DIR}/src/base/ftsynth.c
+    ${FREETYPE_SOURCES_DIR}/src/base/ftsystem.c
+    ${FREETYPE_SOURCES_DIR}/src/base/fttype1.c
+    ${FREETYPE_SOURCES_DIR}/src/base/ftwinfnt.c
+    ${FREETYPE_SOURCES_DIR}/src/bdf/bdf.c
+    ${FREETYPE_SOURCES_DIR}/src/bzip2/ftbzip2.c
+    ${FREETYPE_SOURCES_DIR}/src/cache/ftcache.c
+    ${FREETYPE_SOURCES_DIR}/src/cff/cff.c
+    ${FREETYPE_SOURCES_DIR}/src/cid/type1cid.c
+    ${FREETYPE_SOURCES_DIR}/src/gzip/ftgzip.c
+    ${FREETYPE_SOURCES_DIR}/src/lzw/ftlzw.c
+    ${FREETYPE_SOURCES_DIR}/src/pcf/pcf.c
+    ${FREETYPE_SOURCES_DIR}/src/pfr/pfr.c
+    ${FREETYPE_SOURCES_DIR}/src/psaux/psaux.c
+    ${FREETYPE_SOURCES_DIR}/src/pshinter/pshinter.c
+    ${FREETYPE_SOURCES_DIR}/src/psnames/psnames.c
+    ${FREETYPE_SOURCES_DIR}/src/raster/raster.c
+    ${FREETYPE_SOURCES_DIR}/src/sfnt/sfnt.c
+    ${FREETYPE_SOURCES_DIR}/src/smooth/smooth.c
+    ${FREETYPE_SOURCES_DIR}/src/truetype/truetype.c
+    ${FREETYPE_SOURCES_DIR}/src/type1/type1.c
+    ${FREETYPE_SOURCES_DIR}/src/type42/type42.c
+    ${FREETYPE_SOURCES_DIR}/src/winfonts/winfnt.c
+    )
+
+  if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
+    list(APPEND FREETYPE_SOURCES
+      ${FREETYPE_SOURCES_DIR}/builds/windows/ftdebug.c
+      )
+  endif()
+
+  foreach(header
+      ${FREETYPE_SOURCES_DIR}/include/freetype/config/ftconfig.h
+      ${FREETYPE_SOURCES_DIR}/include/freetype/config/ftoption.h
+      )
+
+    set_source_files_properties(
+      ${FREETYPE_SOURCES}
+      PROPERTIES OBJECT_DEPENDS ${header}
+      )
+  endforeach()
+
+  source_group(ThirdParty\\Freetype REGULAR_EXPRESSION ${FREETYPE_SOURCES_DIR}/.*)
+
+else()
+  include(FindFreetype)
+
+  if (NOT FREETYPE_FOUND)
+    message(FATAL_ERROR "Please install the libfreetype6-dev package")
+  endif()
+
+  include_directories(${FREETYPE_INCLUDE_DIRS})
+  link_libraries(${FREETYPE_LIBRARIES})
+endif()
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Apr 19 09:08:15 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Apr 19 15:11:16 2019 +0200
@@ -67,6 +67,7 @@
 include(FindPkgConfig)
 include(${CMAKE_CURRENT_LIST_DIR}/BoostExtendedConfiguration.cmake)
 include(${CMAKE_CURRENT_LIST_DIR}/CairoConfiguration.cmake)
+include(${CMAKE_CURRENT_LIST_DIR}/FreetypeConfiguration.cmake)
 include(${CMAKE_CURRENT_LIST_DIR}/PixmanConfiguration.cmake)
 
 
@@ -243,6 +244,9 @@
   #${ORTHANC_STONE_ROOT}/Framework/Layers/SeriesFrameRendererFactory.cpp
   #${ORTHANC_STONE_ROOT}/Framework/Layers/SingleFrameRendererFactory.cpp
 
+  ${ORTHANC_STONE_ROOT}/Framework/Fonts/FontRenderer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Fonts/Glyph.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Fonts/GlyphAlphabet.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/CircleMeasureTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/ColorFrameRenderer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/DicomSeriesVolumeSlicer.cpp
@@ -279,6 +283,7 @@
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DicomFrameConverter.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DicomStructureSet.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DownloadStack.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DynamicBitmap.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/Extent2D.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/FiniteProjectiveCamera.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GeometryToolbox.cpp
@@ -320,6 +325,7 @@
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/WidgetBase.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/WorldSceneWidget.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/ZoomMouseTracker.cpp
+
   ${ORTHANC_STONE_ROOT}/Framework/dev.h
 
   ${ORTHANC_STONE_ROOT}/Framework/Messages/ICallable.h
@@ -343,6 +349,7 @@
 
   # Mandatory components
   ${CAIRO_SOURCES}
+  ${FREETYPE_SOURCES}
   ${PIXMAN_SOURCES}
 
   # Optional components
--- a/Resources/CMake/OrthancStoneParameters.cmake	Fri Apr 19 09:08:15 2019 +0200
+++ b/Resources/CMake/OrthancStoneParameters.cmake	Fri Apr 19 15:11:16 2019 +0200
@@ -41,6 +41,7 @@
 
 # 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_PIXMAN ON CACHE BOOL "Use the system version of Pixman")
 set(USE_SYSTEM_SDL ON CACHE BOOL "Use the system version of SDL2")