From 76a896d8a60ef6d41bbd4def5745a1750baffa52 Mon Sep 17 00:00:00 2001
From: Cory Quammen <cory.quammen@kitware.com>
Date: Wed, 15 May 2019 11:53:47 -0400
Subject: [PATCH] ENH: Add vtkWordCloud to Infovos/Core

vtkWordCloud is an Image Source that creates a word cloud.

Word Clouds, AKA Tag Clouds, are a text visualization technique that displays individual works with properties that depend on the frequency of a word in a document. Numerous options are available, including the color of the words, size of the words, the orientation of the words, font files, and mask files.
Word Clouds, AKA Tag Clouds, are a text visualization technique that displays individual works with properties that depend on the frequency of a word in a document.  vtkWordCloud varies the font size base on word frequency.

Word Clouds are useful for quickly perceiving the most prominent terms in a document. Also, Word Clouds can identify trends and patterns that would otherwise be unclear or difficult to see in a tabular format. Frequently used keywords stand out better in a Word Cloud. Common words that might be overlooked in tabular form are highlighted in the larger text, making them pop out when displayed in a word cloud.

There is some controversy about the usefulness of word clouds. Their best use may be for presentations. Word clouds can be used to "compare" texts from similar subjects, e.g., Presidential Inaugural Addresses, job candidate comparisons, etc.

Several methods are available to customize the resulting visualization. The class provides defaults that provide a reasonable result.

BackgroundColorName - The vtkNamedColors name for the backgound (MidNightBlue).

BWMask - Mask image has a single channel(false). Mask images typically have three channels (r,g,b).

ColorDistribution - Distribution of random colors(.6 1.0), if WordColorName is not empty.

ColorSchemeName - Name of a color scheme from vtkColorSeries to be used to select colors for the words (), if WordColorName is empty.

DPI -  Dots per inch(200) of the rendered text. DPI is used as a scaling mechanism for the words. As DPI increases, the word size increases. If there are too, few skipped words, increase this value, too many, decrease it.

FontFileName - If empty, the built-in Arial font is used(). The FontFileName is the name of a file that contains a TrueType font.

FontMultiplier - Font multiplier(6). The final font size is this value * the word frequency.

Gap - Space gap of words (2). The gap is the number of spaces added to the beginning and end of each word.

MaskColorName - Name of the color for the mask (black). This color is the name of the vtkNamedColors that defines the foreground of the mask. Usually black or white.

MaskFileName - Mask file name(). If a mask file is specified, it will be used as the mask. Otherwise, a black square is used as the mask. The mask file should contain three channels of unsigned char values. If the mask file is just a single unsigned char, specify turn the boolean BWMask on.  If BWmask is on, the class will create a three channel image using vtkImageAppendComponents.

MaxFontSize - Maximum font size(48).

MinFontSize - Minimum font size(8).

MinFrequency - Minimum word frequency accepted(2). Word with frequencies less than this will be ignored.

OffsetDistribution - Range of uniform random offsets(-size[0]/100.0 -size{1]/100.0)(-20 20). These offsets are offsets from the generated path for word layout.

OrientationDistribution - Ranges of random orientations(-20 20). If discrete orientations are not defined, these orientations will be generated.

Orientations - Discrete orientations for displayed words. If present, this overrides OrientationDistribution.

ReplacementPairs - Replace the first word with another second word (). The first word is also added to the StopList.

Sizes - Size of image(640 480).

StopWords - User provided stop words(). vtkWordCloud has built-in stop words. The user-provided stop words are added to the built-in list.

Title - Add this word to the document's words and set a high frequency, so that is will be rendered first.

WordColorName - Name of the color for the words(). The name is selected from vtkNamedColors. If the name is empty, the ColorDistribution will generate random colors.
---
 Infovis/Core/CMakeLists.txt                   |    3 +-
 Infovis/Core/Testing/Cxx/CMakeLists.txt       |   25 +-
 Infovis/Core/Testing/Cxx/TestWordCloud.cxx    |   76 +
 .../Core/Testing/Cxx/UnitTestWordCloud.cxx    |  407 +++++
 .../Data/Baseline/TestWordCloud.png.sha512    |    1 +
 .../Data/Baseline/TestWordCloud_1.png.sha512  |    1 +
 .../Data/Baseline/TestWordCloud_2.png.sha512  |    1 +
 .../Data/Baseline/TestWordCloud_3.png.sha512  |    1 +
 Infovis/Core/vtk.module                       |    6 +
 Infovis/Core/vtkWordCloud.cxx                 | 1497 +++++++++++++++++
 Infovis/Core/vtkWordCloud.h                   |  633 +++++++
 Testing/Data/Canterbury.ttf.sha512            |    1 +
 Testing/Data/Gettysburg.txt.sha512            |    1 +
 Testing/Data/NLTKStopList.txt.sha512          |    1 +
 Testing/Data/hearts.png.sha512                |    1 +
 Testing/Data/hearts8bit.png.sha512            |    1 +
 16 files changed, 2654 insertions(+), 2 deletions(-)
 create mode 100644 Infovis/Core/Testing/Cxx/TestWordCloud.cxx
 create mode 100644 Infovis/Core/Testing/Cxx/UnitTestWordCloud.cxx
 create mode 100644 Infovis/Core/Testing/Data/Baseline/TestWordCloud.png.sha512
 create mode 100644 Infovis/Core/Testing/Data/Baseline/TestWordCloud_1.png.sha512
 create mode 100644 Infovis/Core/Testing/Data/Baseline/TestWordCloud_2.png.sha512
 create mode 100644 Infovis/Core/Testing/Data/Baseline/TestWordCloud_3.png.sha512
 create mode 100644 Infovis/Core/vtkWordCloud.cxx
 create mode 100644 Infovis/Core/vtkWordCloud.h
 create mode 100644 Testing/Data/Canterbury.ttf.sha512
 create mode 100644 Testing/Data/Gettysburg.txt.sha512
 create mode 100644 Testing/Data/NLTKStopList.txt.sha512
 create mode 100644 Testing/Data/hearts.png.sha512
 create mode 100644 Testing/Data/hearts8bit.png.sha512

diff --git a/Infovis/Core/CMakeLists.txt b/Infovis/Core/CMakeLists.txt
index 834958a474f..a437bebe8a3 100644
--- a/Infovis/Core/CMakeLists.txt
+++ b/Infovis/Core/CMakeLists.txt
@@ -42,7 +42,8 @@ set(classes
   vtkTreeDifferenceFilter
   vtkTreeFieldAggregator
   vtkTreeLevelsFilter
-  vtkVertexDegree)
+  vtkVertexDegree
+  vtkWordCloud)
 
 vtk_module_add_module(VTK::InfovisCore
   CLASSES ${classes})
diff --git a/Infovis/Core/Testing/Cxx/CMakeLists.txt b/Infovis/Core/Testing/Cxx/CMakeLists.txt
index f7b651c3d24..765829af94e 100644
--- a/Infovis/Core/Testing/Cxx/CMakeLists.txt
+++ b/Infovis/Core/Testing/Cxx/CMakeLists.txt
@@ -28,4 +28,27 @@ vtk_add_test_cxx(vtkInfovisCoreCxxTests tests
   TestTreeDifferenceFilter.cxx,NO_VALID
   )
 
-vtk_test_cxx_executable(vtkInfovisCoreCxxTests tests)
+# add to the list but don't define a test
+list(APPEND tests UnitTestWordCloud.cxx)
+list(APPEND tests TestWordCloud.cxx)
+
+ExternalData_add_test(${_vtk_build_TEST_DATA_TARGET}
+  NAME VTK::InfovisCoreCxxTests-UnitTestWordCloud
+  COMMAND vtkInfovisCoreCxxTests UnitTestWordCloud
+  DATA{../../../../Testing/Data/Gettysburg.txt} DATA{../../../../Testing/Data/Canterbury.ttf} DATA{../../../../Testing/Data/hearts.png} DATA{../../../../Testing/Data/hearts8bit.png}  DATA{../../../../Testing/Data/NLTKStopList.txt})
+
+ExternalData_add_test(${_vtk_build_TEST_DATA_TARGET}
+  NAME VTK::InfovisCoreCxxTests-TestWordCloud
+  COMMAND vtkInfovisCoreCxxTests TestWordCloud
+  DATA{../../../../Testing/Data/Gettysburg.txt} DATA{../../../../Testing/Data/Canterbury.ttf}
+  -V DATA{../Data/Baseline/TestWordCloud.png,:}
+  -T "${VTK_TEST_OUTPUT_DIR}"
+)
+
+set(all_tests
+  ${tests}
+  ${data_tests}
+  ${output_tests}
+  ${custom_tests}
+  )
+vtk_test_cxx_executable(vtkInfovisCoreCxxTests all_tests RENDERING_FACTORY)
diff --git a/Infovis/Core/Testing/Cxx/TestWordCloud.cxx b/Infovis/Core/Testing/Cxx/TestWordCloud.cxx
new file mode 100644
index 00000000000..8e7fcc64dd1
--- /dev/null
+++ b/Infovis/Core/Testing/Cxx/TestWordCloud.cxx
@@ -0,0 +1,76 @@
+/*=========================================================================
+
+  Program:   Visualization Toolkit
+  Module:    TestWordCloud.cxx
+
+-------------------------------------------------------------------------
+  Copyright 2008 Sandia Corporation.
+  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
+  the U.S. Government retains certain rights in this software.
+-------------------------------------------------------------------------
+
+  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+  All rights reserved.
+  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
+
+     This software is distributed WITHOUT ANY WARRANTY; without even
+     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+     PURPOSE.  See the above copyright notice for more information.
+
+=========================================================================*/
+#include "vtkSmartPointer.h"
+#include "vtkWordCloud.h"
+
+#include <vtkRenderWindow.h>
+#include <vtkRenderWindowInteractor.h>
+#include <vtkRenderer.h>
+#include <vtkCamera.h>
+#include <vtkImageViewer2.h>
+#include <vtkNamedColors.h>
+
+#include <iostream>
+
+int TestWordCloud(int argc, char *argv[])
+{
+  if (argc < 2)
+  {
+    std::cout << "Usage: " << argv[0] << "filename" << std::endl;
+    return EXIT_FAILURE;
+  }
+  vtkWordCloud::OffsetDistributionContainer offset;
+  offset[0] = 0;
+  offset[1] = 0;
+  auto wordCloud = vtkSmartPointer<vtkWordCloud>::New();
+  wordCloud->SetFileName(argv[1]);
+  wordCloud->SetOffsetDistribution(offset);
+  wordCloud->SetFontFileName(argv[2]);
+  wordCloud->AddOrientation(0.0);
+  wordCloud->AddOrientation(90.0);
+  wordCloud->Update();
+  std::cout << "File" << argv[1] << std::endl;
+  std::cout << "Font" << argv[2] << std::endl;
+  std::cout << "Kept Words: "    << wordCloud->GetKeptWords().size() << std::endl;
+  std::cout << "Stopped Words: " << wordCloud->GetStoppedWords().size() << std::endl;
+  std::cout << "Skipped Words: " << wordCloud->GetSkippedWords().size() << std::endl;
+
+  // Display the final image
+  auto colors = vtkSmartPointer<vtkNamedColors>::New();
+  auto interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New();
+
+  auto imageViewer = vtkSmartPointer<vtkImageViewer2>::New();
+  imageViewer->SetInputData(wordCloud->GetOutput());
+  imageViewer->SetupInteractor(interactor);
+  imageViewer->GetRenderer()->SetBackground(colors->GetColor3d("Wheat").GetData());
+  imageViewer->SetSize(wordCloud->GetSizes()[0], wordCloud->GetSizes()[1]);
+  imageViewer->GetRenderer()->ResetCamera();
+
+  // Zoom in a bit
+  vtkCamera* camera = imageViewer->GetRenderer()->GetActiveCamera();
+  camera->ParallelProjectionOn();
+  camera->SetParallelScale(wordCloud->GetAdjustedSizes()[0] * .4);
+
+  imageViewer->GetRenderWindow()->Render();
+  interactor->Start();
+
+  return EXIT_SUCCESS;
+}
diff --git a/Infovis/Core/Testing/Cxx/UnitTestWordCloud.cxx b/Infovis/Core/Testing/Cxx/UnitTestWordCloud.cxx
new file mode 100644
index 00000000000..7c6de2cd4a0
--- /dev/null
+++ b/Infovis/Core/Testing/Cxx/UnitTestWordCloud.cxx
@@ -0,0 +1,407 @@
+/*=========================================================================
+
+  Program:   Visualization Toolkit
+  Module:    UnitTestWordCloud.cxx
+
+-------------------------------------------------------------------------
+  Copyright 2008 Sandia Corporation.
+  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
+  the U.S. Government retains certain rights in this software.
+-------------------------------------------------------------------------
+
+  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+  All rights reserved.
+  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
+
+     This software is distributed WITHOUT ANY WARRANTY; without even
+     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+     PURPOSE.  See the above copyright notice for more information.
+
+=========================================================================*/
+#include "vtkWordCloud.h"
+#include "vtkSmartPointer.h"
+
+#include "vtkTestErrorObserver.h"
+#include "vtkExecutive.h"
+
+#include <iostream>
+#include <sstream>
+#include <algorithm>
+#include <random>
+
+namespace
+{
+int TestOneByOne(vtkSmartPointer<vtkWordCloud> &,
+                 std::string,
+                 size_t, size_t, size_t);
+}
+
+int UnitTestWordCloud(int argc, char *argv[])
+{
+  // This test uses random variables, so differences may exist from
+  // compiler to compiler
+#if defined(__GNUC__)
+  if (std::system("/usr/bin/gcc --version >gccversion.txt") == 0)
+  {
+    std::cout << std::ifstream("gccversion.txt").rdbuf();
+  }
+#endif
+
+  if (argc < 2)
+  {
+    std::cout << "Usage: " << argv[0] << "filename" << std::endl;
+    return EXIT_FAILURE;
+  }
+
+  auto status = 0;
+
+  // Create a word cloud source
+  auto wordCloud = vtkSmartPointer<vtkWordCloud>::New();
+
+  // Test empty print
+  std::cout << "Testing empty Print...";
+  std::ostringstream emptyPrint;
+  wordCloud->Print(emptyPrint);
+  std::cout << "Passed" << std::endl;
+
+  // Test defaults
+  wordCloud->SetFileName(argv[1]);
+  wordCloud->Update();
+
+  // Test Regressions for default settings
+  auto status1 = 0;
+  std::cout << "Testing regressions of default word cloud...";
+#if defined(__GNUC__) && (__GNUC__ == 8 && __GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ == 1)
+  size_t keptExpected = 23;
+#else
+  size_t keptExpected = 31;
+#endif
+  if (keptExpected != wordCloud->GetKeptWords().size())
+  {
+    std::cout << "\n  Default regression failed. Expected # of kept words " << keptExpected
+              << " but got " << wordCloud->GetKeptWords().size();
+    status1++;
+  }
+
+  size_t skippedExpected = 1;
+  if (skippedExpected != wordCloud->GetSkippedWords().size())
+  {
+    std::cout << "\n  Default regression failed. Expected # of skipped words " << skippedExpected
+              << " but got " << wordCloud->GetSkippedWords().size();
+    status1++;
+  }
+  size_t stoppedExpected = 65;
+  if (stoppedExpected != wordCloud->GetStoppedWords().size())
+  {
+    std::cout << "\n  Default regression failed. Expected # of stopped words " << stoppedExpected
+              << " but got " << wordCloud->GetStoppedWords().size() << std::endl;
+    status1++;
+  }
+  if (status1)
+  {
+    std::cout << "\n..Failed" << std::endl;
+    ++status;
+  }
+  else
+  {
+    std::cout << "..Passed" << std::endl;
+  }
+
+  // Check modified times for containers
+#define CHECK_CONTAINER_MTIMES(name) \
+  { \
+  auto name = wordCloud->Get##name(); \
+  auto mtime = wordCloud->GetMTime(); \
+  wordCloud->Set##name(name); \
+  auto mtimeModified = mtime; \
+  if (mtime != mtimeModified) \
+  { \
+    std::cout << "\n  Modify time is bad for " #name; \
+    status2++; \
+  } \
+  name[0] = name[name.size()-1]; \
+  wordCloud->Set##name(name); \
+  mtimeModified = wordCloud->GetMTime(); \
+  if (mtime == mtimeModified) \
+  { \
+    std::cout << "\n Modify time is bad for " #name; \
+    status2++; \
+  } \
+  }
+
+  std::cout << "Testing Container MTimes...";
+  auto status2 = 0;
+
+  vtkWordCloud::ColorDistributionContainer colorDistribution = {{.6, 1.0}};
+  wordCloud->SetColorDistribution(colorDistribution);
+
+  vtkWordCloud::OffsetDistributionContainer offsetDistribution = {{-10, 20}};
+  wordCloud->SetOffsetDistribution(offsetDistribution);
+
+  vtkWordCloud::OrientationsContainer orientations = {-90};
+  wordCloud->SetOrientations(orientations);
+  wordCloud->AddOrientation(90.0);
+
+  wordCloud->SetOrientations(orientations);
+  wordCloud->AddOrientation(0.0);
+
+  vtkWordCloud::ReplacementPairsContainer replacementPairs;
+  vtkWordCloud::PairType pt1("old", "new");
+  vtkWordCloud::PairType pt2("bill", "will");
+  replacementPairs.push_back(pt1);
+  wordCloud->SetReplacementPairs(replacementPairs);
+  wordCloud->AddReplacementPair(pt2);
+
+  vtkWordCloud::SizesContainer sizes;
+  sizes[0] = 100; sizes[1] = 10;
+  wordCloud->SetSizes(sizes);
+
+  vtkWordCloud::StopWordsContainer words;
+  words.insert("albany");
+  wordCloud->SetStopWords(words);
+  wordCloud->AddStopWord("troy");
+  wordCloud->AddStopWord("clifton");
+
+  CHECK_CONTAINER_MTIMES(ColorDistribution);
+  CHECK_CONTAINER_MTIMES(OffsetDistribution);
+  CHECK_CONTAINER_MTIMES(OrientationDistribution);
+  CHECK_CONTAINER_MTIMES(Orientations);
+  CHECK_CONTAINER_MTIMES(ReplacementPairs);
+  CHECK_CONTAINER_MTIMES(Sizes);
+
+  if (status2)
+  {
+    std::cout << " ...Failed" << std::endl;
+    ++status;
+  }
+  else
+  {
+    std::cout << " ...Passed" << std::endl;
+  }
+  std::cout << "Testing Set..";
+  wordCloud->SetBackgroundColorName("banana");
+  wordCloud->SetBWMask(1);
+  wordCloud->SetColorSchemeName("foo");
+  wordCloud->SetDPI(100);
+  wordCloud->SetFontMultiplier(3);
+  wordCloud->SetGap(5);
+  wordCloud->SetFontFileName(argv[2]);
+  wordCloud->SetMaskColorName("white");
+  wordCloud->SetMaskFileName("maskfile");
+  wordCloud->SetMaxFontSize(100);
+  wordCloud->SetMinFontSize(100);
+  wordCloud->SetMinFrequency(3);
+  wordCloud->SetWordColorName("Brown");
+  wordCloud->SetTitle("Unit Test");
+  auto wordCloudNew = wordCloud->NewInstance();
+  std::cout << "..Passed" << std::endl;
+
+  std::cout << "Testing Set one-by-one..";
+  int status4 = 0;
+  {
+    auto wc = vtkSmartPointer<vtkWordCloud>::New();
+
+    wc->SetFileName(argv[1]);
+#if defined(__GNUC__) && (__GNUC__ == 8 && __GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ == 1)
+    status4 += TestOneByOne(wc, "Defaults", 23, 1, 65);
+#else
+    status4 += TestOneByOne(wc, "Defaults", 31, 1, 65);
+#endif
+    wc->SetFontFileName(argv[2]);
+    status4 += TestOneByOne(wc, "FontFileName", 40, 1, 65);
+
+    wc->SetGap(4);
+    status4 += TestOneByOne(wc, "Gap", 28, 1, 65);
+
+    wc->SetFontMultiplier(8);
+    status4 += TestOneByOne(wc, "FontMultiplier", 20, 1, 65);
+
+    wc->SetMinFrequency(2);
+    status4 += TestOneByOne(wc, "MinFrequency", 10, 1, 65);
+
+    wc->SetMaxFontSize(100);
+    status4 += TestOneByOne(wc, "MaxFontSize", 10, 1, 65);
+
+    wc->AddStopWord("nation");
+    wc->AddStopWord("dedicated");
+    status4 += TestOneByOne(wc, "StopWords", 11, 1, 67);
+
+    vtkWordCloud::OrientationDistributionContainer oDist;
+    oDist[0] = -90.0;
+    oDist[1] =  90.0;
+    status4 += TestOneByOne(wc, "OrientationDistribution", 11, 1, 67);
+
+    wc->AddOrientation(90.0);
+    wc->AddOrientation(0.0);
+    status4 += TestOneByOne(wc, "Orientations", 11, 1, 67);
+
+    wc->SetTitle("Gettysburg");
+    status4 += TestOneByOne(wc, "Title", 11, 1, 67);
+
+    wc->SetDPI(100);
+    status4 += TestOneByOne(wc, "DPI", 11, 1, 67);
+
+    wc->SetMaskColorName("white");
+    wc->SetFontMultiplier(2);
+    wc->SetMaxFontSize(10);
+    wc->SetMaskFileName(argv[3]);
+    status4 += TestOneByOne(wc, "MaskFileName", 12, 1, 67);
+
+    wc->SetMaskFileName(argv[4]);
+    wc->SetBWMask(1);
+    status4 += TestOneByOne(wc, "MaskFileName(8bit)", 12, 1, 67);
+
+    wc->SetColorSchemeName("Brewer Qualitative Pastel2");
+    status4 += TestOneByOne(wc, "ColorSchemeName", 12, 1, 67);
+
+    auto repPair = std::make_tuple("consecrate", "consecrated");
+    wc->AddReplacementPair(repPair);
+    status4 += TestOneByOne(wc, "ReplacementPairs", 12, 1, 68);
+
+    wc->SetWordColorName("Peacock");
+    status4 += TestOneByOne(wc, "WordColorName", 12, 1, 68);
+
+    vtkWordCloud::ColorDistributionContainer colorDist = {{0.0, 1.0}};
+    wc->SetColorDistribution(colorDist);
+    status4 += TestOneByOne(wc, "ColorDistribution", 12, 1, 68);
+
+    wc->SetStopListFileName(argv[5]);
+    status4 += TestOneByOne(wc, "StopListFileName", 18, 1, 47);
+  }
+  if (status4)
+  {
+    std::cout << "\n..Failed" << std::endl;
+  }
+  else
+  {
+    std::cout << "..Passed" << std::endl;
+  }
+  status += status4;
+
+  // Test Errors
+  std::cout << "Testing Errors..";
+  auto errorObserver = vtkSmartPointer<vtkTest::ErrorObserver>::New();
+  auto errorObserver1 = vtkSmartPointer<vtkTest::ErrorObserver>::New();
+  int status5 = 0;
+  {
+    auto wc = vtkSmartPointer<vtkWordCloud>::New();
+    wc->AddObserver(vtkCommand::ErrorEvent, errorObserver);
+    wc->GetExecutive()->AddObserver(vtkCommand::ErrorEvent,errorObserver1);
+    wc->SetFileName(argv[1]);
+    wc->SetWordColorName("");
+    wc->SetColorSchemeName("foo");
+    wc->Update();
+    status5 += errorObserver->CheckErrorMessage(
+      "The color scheme foo does not exist");
+    errorObserver->Clear();
+  }
+  {
+    auto wc = vtkSmartPointer<vtkWordCloud>::New();
+    wc->AddObserver(vtkCommand::ErrorEvent, errorObserver);
+    wc->GetExecutive()->AddObserver(vtkCommand::ErrorEvent,errorObserver1);
+    wc->SetFileName("Foo.txt");
+    wc->Update();
+    status5 += errorObserver->CheckErrorMessage(
+      "FileName Foo.txt does not exist");
+    errorObserver->Clear();
+  }
+  {
+    auto wc = vtkSmartPointer<vtkWordCloud>::New();
+    wc->AddObserver(vtkCommand::ErrorEvent, errorObserver);
+    wc->GetExecutive()->AddObserver(vtkCommand::ErrorEvent,errorObserver1);
+    wc->SetFileName(argv[1]);
+    wc->SetFontFileName("BadFontFile.txt");
+    wc->Update();
+    status5 += errorObserver->CheckErrorMessage(
+      "FontFileName BadFontFile.txt does not exist");
+    errorObserver->Clear();
+  }
+  {
+    auto wc = vtkSmartPointer<vtkWordCloud>::New();
+    wc->AddObserver(vtkCommand::ErrorEvent, errorObserver);
+    wc->GetExecutive()->AddObserver(vtkCommand::ErrorEvent,errorObserver1);
+    wc->SetFileName(argv[1]);
+    wc->SetMaskFileName("BadMaskFile.txt");
+    wc->Update();
+    status5 += errorObserver->CheckErrorMessage(
+      "MaskFileName BadMaskFile.txt does not exist");
+    errorObserver->Clear();
+  }
+  {
+    auto wc = vtkSmartPointer<vtkWordCloud>::New();
+    wc->AddObserver(vtkCommand::ErrorEvent, errorObserver);
+    wc->GetExecutive()->AddObserver(vtkCommand::ErrorEvent,errorObserver1);
+    wc->SetFileName(argv[1]);
+    wc->SetMaskFileName("BadStopListFile.txt");
+    wc->Update();
+    status5 += errorObserver->CheckErrorMessage(
+      "BadStopListFile.txt does not exist");
+    errorObserver->Clear();
+  }
+  {
+    auto wc = vtkSmartPointer<vtkWordCloud>::New();
+    wc->AddObserver(vtkCommand::ErrorEvent, errorObserver);
+    wc->GetExecutive()->AddObserver(vtkCommand::ErrorEvent,errorObserver1);
+    wc->Update();
+    status5 += errorObserver->CheckErrorMessage(
+      "No FileName is set. Use SetFileName to set a file");
+    errorObserver->Clear();
+  }
+  status += status5;
+  if (status5)
+  {
+    std::cout << "..Failed" << std::endl;
+  }
+  else
+  {
+    std::cout << "..Passed" << std::endl;
+  }
+
+  std::cout << "Testing populated Print...";
+  std::ostringstream populatedPrint;
+  wordCloud->Print(populatedPrint);
+  std::cout << "..Passed" << std::endl;
+
+  auto className = wordCloud->GetClassName();
+  std::cout << "className: " << className << std::endl;
+
+  wordCloudNew->Delete();
+
+  if (status)
+  {
+    return EXIT_FAILURE;
+  }
+  return EXIT_SUCCESS;
+}
+
+namespace
+{
+int TestOneByOne(vtkSmartPointer<vtkWordCloud> &wc,
+                 std::string name,
+                 size_t keptExpected,
+                 size_t skippedExpected,
+                 size_t stoppedExpected)
+{
+  int status = 0;
+  wc->Update();
+  if (keptExpected != wc->GetKeptWords().size())
+  {
+    std::cout << "\n  Regression failed for " << name << ". Expected # of kept words " << keptExpected
+              << " but got " << wc->GetKeptWords().size();
+    status++;
+  }
+  if (skippedExpected != wc->GetSkippedWords().size())
+  {
+    std::cout << "\n  Regression failed for " << name << ". Expected # of skipped words " << skippedExpected
+              << " but got " << wc->GetSkippedWords().size();
+    status++;
+  }
+  if (stoppedExpected != wc->GetStoppedWords().size())
+  {
+    std::cout << "\n  Regression failed for " << name << ". Expected # of stopped words " << stoppedExpected
+              << " but got " << wc->GetStoppedWords().size() << std::endl;
+    status++;
+  }
+  return status;
+}
+}
diff --git a/Infovis/Core/Testing/Data/Baseline/TestWordCloud.png.sha512 b/Infovis/Core/Testing/Data/Baseline/TestWordCloud.png.sha512
new file mode 100644
index 00000000000..990d01fcb7c
--- /dev/null
+++ b/Infovis/Core/Testing/Data/Baseline/TestWordCloud.png.sha512
@@ -0,0 +1 @@
+e74d9cb19ff60205f117da692cbeca99f79d389a54d00ba1378fcef18fcea5bf515035a670885865add6e4286336f938b3a5a626014b058f58c79a5098a595b4
diff --git a/Infovis/Core/Testing/Data/Baseline/TestWordCloud_1.png.sha512 b/Infovis/Core/Testing/Data/Baseline/TestWordCloud_1.png.sha512
new file mode 100644
index 00000000000..abbbabcc133
--- /dev/null
+++ b/Infovis/Core/Testing/Data/Baseline/TestWordCloud_1.png.sha512
@@ -0,0 +1 @@
+cc2e1e5a1d74c59edc4efe2e4d9b3cc7d970337529dcf6d55a6bbfd995b28d19b29b6065156aba1815e28347839b40cb0df6f65a32669fa5d18293a386ce7e94
diff --git a/Infovis/Core/Testing/Data/Baseline/TestWordCloud_2.png.sha512 b/Infovis/Core/Testing/Data/Baseline/TestWordCloud_2.png.sha512
new file mode 100644
index 00000000000..a290810f04b
--- /dev/null
+++ b/Infovis/Core/Testing/Data/Baseline/TestWordCloud_2.png.sha512
@@ -0,0 +1 @@
+798126b4f5c0f8b6aa43089df279a42f09075cd3476e83b2663590eeb2ab6793674610e94ee130ece763174b3ba637cb24f7339530e0f696c601779b8f6998f6
diff --git a/Infovis/Core/Testing/Data/Baseline/TestWordCloud_3.png.sha512 b/Infovis/Core/Testing/Data/Baseline/TestWordCloud_3.png.sha512
new file mode 100644
index 00000000000..b99f9c17f49
--- /dev/null
+++ b/Infovis/Core/Testing/Data/Baseline/TestWordCloud_3.png.sha512
@@ -0,0 +1 @@
+a3cf52fff463054c16c282dd410dedec54ea017435ca70ac937a89e1c7786476769dac781f4cba61a144697acd8d00c0408280df119cbd61f57a1e00a2f6b92f
diff --git a/Infovis/Core/vtk.module b/Infovis/Core/vtk.module
index 847098303f5..128d0828797 100644
--- a/Infovis/Core/vtk.module
+++ b/Infovis/Core/vtk.module
@@ -8,6 +8,11 @@ DEPENDS
   VTK::CommonCore
   VTK::CommonDataModel
   VTK::CommonExecutionModel
+  VTK::CommonColor
+  VTK::IOImage
+  VTK::ImagingCore
+  VTK::ImagingSources
+  VTK::RenderingFreeType
 PRIVATE_DEPENDS
   VTK::FiltersExtraction
   VTK::FiltersGeneral
@@ -16,6 +21,7 @@ TEST_DEPENDS
   VTK::IOImage
   VTK::IOInfovis
   VTK::InfovisLayout
+  VTK::InteractionImage
   VTK::InteractionStyle
   VTK::RenderingOpenGL2
   VTK::TestingRendering
diff --git a/Infovis/Core/vtkWordCloud.cxx b/Infovis/Core/vtkWordCloud.cxx
new file mode 100644
index 00000000000..00aa02cf68c
--- /dev/null
+++ b/Infovis/Core/vtkWordCloud.cxx
@@ -0,0 +1,1497 @@
+/*=========================================================================
+
+  Program:   Visualization Toolkit
+  Module:    vtkWordCloud.cxx
+
+  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+  All rights reserved.
+  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
+
+     This software is distributed WITHOUT ANY WARRANTY; without even
+     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+     PURPOSE.  See the above copyright notice for more information.
+
+=========================================================================*/
+#include "vtkWordCloud.h"
+
+#include "vtkInformation.h"
+#include "vtkInformationVector.h"
+#include "vtkObjectFactory.h"
+#include "vtkStreamingDemandDrivenPipeline.h"
+
+#include "vtkFreeTypeTools.h"
+#include "vtkImageBlend.h"
+#include "vtkImageIterator.h"
+#include "vtkImageData.h"
+
+#include "vtkTextProperty.h"
+#include "vtkImageCanvasSource2D.h"
+#include "vtkMath.h"
+#include "vtkNamedColors.h"
+#include "vtkColorSeries.h"
+
+#include "vtkImageReader2.h"
+#include "vtkImageReader2Factory.h"
+#include "vtkImageResize.h"
+#include "vtkImageExtractComponents.h"
+#include "vtkImageAppendComponents.h"
+
+#include "vtksys/SystemTools.hxx"
+
+// stl
+#include <algorithm>
+#include <fstream>
+#include <functional>
+#include <iostream>
+#include <iterator>
+#include <map>
+#include <random>
+#include <regex>
+#include <set>
+#include <sstream>
+#include <string>
+#include <vector>
+
+vtkStandardNewMacro(vtkWordCloud);
+
+namespace
+{
+// Declaring the type of Predicate that accepts 2 pairs and return a bool
+typedef std::function<bool(
+    std::pair<std::string, int>,
+    std::pair<std::string, int>)> Comparator;
+
+std::multiset<std::pair<std::string, int>, Comparator > FindWordsSortedByFrequency(std::string &, vtkWordCloud *);
+struct ExtentOffset
+{
+   ExtentOffset(int _x = 0.0, int _y = 0.0) : x(_x), y(_y) {}
+   int x,y;
+};
+struct ArchimedesValue
+{
+   ArchimedesValue(double _x = 0.0, double _y = 0.0) : x(_x), y(_y) {}
+   double x,y;
+};
+void AddReplacementPairsToStopList(vtkWordCloud *,
+                                   vtkWordCloud::StopWordsContainer &);
+bool AddWordToFinal(vtkWordCloud *,
+                    const std::string,
+                    const int,
+                    std::mt19937_64 &,
+                    double orientation,
+                    std::vector<ExtentOffset> &,
+                    vtkImageBlend *,
+                    std::string &);
+
+
+void ArchimedesSpiral (std::vector<ExtentOffset>&,
+                       vtkWordCloud::SizesContainer&);
+void ReplaceMaskColorWithBackgroundColor(vtkImageData*, vtkWordCloud* );
+void CreateBuiltInStopList(vtkWordCloud::StopWordsContainer &StopList);
+void CreateStopListFromFile(std::string,
+                            vtkWordCloud::StopWordsContainer &);
+void ShowColorSeriesNames(ostream& os);
+}
+
+//----------------------------------------------------------------------------
+vtkWordCloud::vtkWordCloud() :
+  BackgroundColorName("MidnightBlue"),
+  BWMask(false),
+  ColorDistribution({{.6, 1.0}}),
+  ColorSchemeName(""),
+  DPI(200),
+  FileName(""),
+  FontFileName(""),
+  FontMultiplier(6),
+  Gap(2),
+  MaskColorName("black"),
+  MaskFileName(""),
+  MaxFontSize(48),
+  MinFontSize(12),
+  MinFrequency(1),
+  OrientationDistribution({{-20,20}}),
+  Sizes({{640,480}}),
+  StopListFileName(""),
+  Title(""),
+  WordColorName("")
+{
+  this->SetNumberOfInputPorts(0);
+
+  this->OffsetDistribution[0] = -this->Sizes[0] / 100.0;
+  this->OffsetDistribution[1] =  this->Sizes[1] / 100.0;
+
+  this->ImageData = vtkSmartPointer<vtkImageData>::New();
+  this->ImageData->SetDimensions(640, 480, 1);
+  this->ImageData->AllocateScalars(VTK_UNSIGNED_CHAR,3);
+
+  this->WholeExtent[0] = 0;
+  this->WholeExtent[1] = 0;
+  this->WholeExtent[2] = 0;
+  this->WholeExtent[3] = 0;
+  this->WholeExtent[4] = 0;
+  this->WholeExtent[5] = 0;
+}
+
+//--------------------------------------------------------------------------
+int vtkWordCloud::RequestInformation (vtkInformation * vtkNotUsed(request),
+                                      vtkInformationVector** vtkNotUsed( inputVector ),
+                                      vtkInformationVector *outputVector)
+{
+  // Get the info objects
+  vtkInformation* outInfo = outputVector->GetInformationObject(0);
+
+  outInfo->Set(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(),
+               this->WholeExtent,6);
+
+  outInfo->Set(vtkDataObject::SPACING(), 1.0, 1.0, 1.0);
+  outInfo->Set(vtkDataObject::ORIGIN(),  0.0, 0.0, 0.0);
+
+  vtkDataObject::SetPointDataActiveScalarInfo
+    (outInfo, this->ImageData->GetScalarType(),
+     this->ImageData->GetNumberOfScalarComponents());
+  return 1;
+}
+
+//--------------------------------------------------------------------------
+int vtkWordCloud::RequestData (vtkInformation * vtkNotUsed(request),
+                               vtkInformationVector** vtkNotUsed( inputVector ),
+                               vtkInformationVector *outputVector)
+{
+  // Get the data object
+  vtkInformation *outInfo = outputVector->GetInformationObject(0);
+
+  vtkImageData *output = vtkImageData::SafeDownCast
+    (outInfo->Get(vtkDataObject::DATA_OBJECT()) );
+
+  // Check some parameters before we start
+  if (this->FileName.size() == 0)
+  {
+    vtkErrorMacro (<< "No FileName is set. Use SetFileName to set a file.");
+    return 0;
+  }
+  if (!vtksys::SystemTools::FileExists(this->FileName, true))
+    {
+      vtkErrorMacro( << "FileName " << this->FileName << " does not exist");
+      return 0;
+    }
+  if (this->FontFileName.size() > 0 &&
+      !vtksys::SystemTools::FileExists(this->FontFileName, true))
+    {
+      vtkErrorMacro( << "FontFileName " << this->FontFileName << " does not exist");
+      return 0;
+    }
+  if (this->MaskFileName.size() > 0 &&
+      !vtksys::SystemTools::FileExists(this->MaskFileName, true))
+    {
+      vtkErrorMacro( << "MaskFileName " << this->MaskFileName << " does not exist");
+      return 0;
+    }
+  if (this->StopListFileName.size() > 0 &&
+      !vtksys::SystemTools::FileExists(this->StopListFileName, true))
+    {
+      vtkErrorMacro( << "StopListFileName " << this->StopListFileName << " does not exist");
+      return 0;
+    }
+  // Open the text file
+  std::ifstream t(this->FileName);
+  std::stringstream buffer;
+  buffer << t.rdbuf();
+  std::string s = buffer.str();
+  t.close();
+
+  this->KeptWords.resize(0);
+  this->StoppedWords.resize(0);
+  this->SkippedWords.resize(0);
+
+  // Generate a path for placement of words
+  std::vector<::ExtentOffset> offset;
+  ::ArchimedesSpiral(offset, this->Sizes);
+
+  // Sort the word by frequency
+  std::multiset<std::pair<std::string, int>, ::Comparator> sortedWords =
+            ::FindWordsSortedByFrequency(s, this);
+
+  // Create a mask image
+  auto colors = vtkSmartPointer<vtkNamedColors>::New();
+  vtkColor3ub maskColor = colors->GetColor3ub(this->MaskColorName.c_str());
+  auto maskImage = vtkSmartPointer<vtkImageData>::New();
+
+  // If a mask file is not defined, create a rectangular
+  if (this->MaskFileName == "")
+  {
+    auto defaultMask = vtkSmartPointer<vtkImageCanvasSource2D>::New();
+    defaultMask->SetScalarTypeToUnsignedChar();
+    defaultMask->SetNumberOfScalarComponents(3);
+    defaultMask->SetExtent(0, this->Sizes[0] - 1,
+                           0, this->Sizes[1] - 1,
+                           0, 0);
+    defaultMask->SetDrawColor(maskColor.GetData()[0],
+                              maskColor.GetData()[1],
+                              maskColor.GetData()[2]);
+    defaultMask->FillBox(0, this->Sizes[0] - 1, 0, this->Sizes[1] - 1);
+    defaultMask->Update();
+    maskImage = defaultMask->GetOutput();
+    this->AdjustedSizes[0] = this->Sizes[0];
+    this->AdjustedSizes[1] = this->Sizes[1];
+  }
+  else
+  {
+    // Read the mask file
+    auto readerFactory =
+      vtkSmartPointer<vtkImageReader2Factory>::New();
+    vtkSmartPointer<vtkImageReader2> reader;
+    reader.TakeReference(
+      readerFactory->CreateImageReader2(this->MaskFileName.c_str()));
+      reader->SetFileName(this->MaskFileName.c_str());
+    reader->Update();
+    int dimensions[3];
+    reader->GetOutput()->GetDimensions(dimensions);
+
+    // Resize the mask image to match the size of the final image
+    auto resize = vtkSmartPointer<vtkImageResize>::New();
+    resize->SetInputData(reader->GetOutput());
+    resize->InterpolateOff();
+    double aspect =
+      static_cast<double>(dimensions[1]) / static_cast<double>(dimensions[0]) *
+      static_cast<double>(this->Sizes[0]) / static_cast<double>(this->Sizes[1]);
+    this->AdjustedSizes[0] = this->Sizes[0];
+    this->AdjustedSizes[1] = aspect * this->Sizes[1];
+    resize->SetOutputDimensions(this->AdjustedSizes[0],
+                                this->AdjustedSizes[1],
+                                1);
+    // If the mask file has a single channel, create a 3-channel mask
+    // image
+    if (this->BWMask)
+    {
+      auto appendFilter = vtkSmartPointer<vtkImageAppendComponents>::New();
+      appendFilter->SetInputConnection(0, resize->GetOutputPort());
+      appendFilter->AddInputConnection(0, resize->GetOutputPort());
+      appendFilter->AddInputConnection(0, resize->GetOutputPort());
+      appendFilter->Update();
+      maskImage = appendFilter->GetOutput();
+    }
+    else
+    {
+      auto rgbImage = vtkSmartPointer<vtkImageExtractComponents>::New();
+      rgbImage->SetInputConnection(resize->GetOutputPort());
+      rgbImage->SetComponents(0, 1, 2);
+      rgbImage->Update();
+      maskImage = rgbImage->GetOutput();
+    }
+  }
+  // Create an image that will hold the final image
+  auto final = vtkSmartPointer<vtkImageBlend>::New();
+  final->AddInputData(maskImage);
+  final->SetOpacity(0, .5);
+  final->Update();
+
+  // Initialize error message
+  std::string errorMessage;
+
+  // Try to add each word
+  this->GetSkippedWords().resize(0);
+
+  bool added;
+  // Create a vector of orientations to try.
+  std::mt19937_64 mt(4355412); //Standard mersenne twister engine
+  for (auto element : sortedWords)
+  {
+    std::vector<double> orientations;
+    // If discrete orientations are present use them, otherwise
+    // generate random orientations
+    if (this->Orientations.size() != 0)
+    {
+      orientations = this->Orientations;
+    }
+    else
+    {
+      std::uniform_real_distribution<> orientationDist(
+        this->OrientationDistribution[0], this->OrientationDistribution[1]);
+      orientations.push_back(orientationDist(mt));
+    }
+    std::shuffle(std::begin(orientations), std::end(orientations), mt);
+    for (auto o : orientations)
+    {
+      added = ::AddWordToFinal(
+        this,
+        element.first,
+        element.second,
+        mt,
+        o,
+        offset,
+        final,
+        errorMessage);
+      if (errorMessage.size() != 0)
+      {
+        vtkErrorMacro( << errorMessage);
+        return 0;
+      }
+      if (added)
+      {
+        this->GetKeptWords().push_back(element.first);
+        break;
+      }
+      else
+      {
+        std::string skippedWord;
+        std::transform((element.first).begin(),
+                       (element.first).end(),
+                       skippedWord.begin(),
+                       ::tolower);
+
+        this->GetSkippedWords().push_back(skippedWord);
+      }
+    }
+  }
+
+  // If a mack file exists, replace the maskColor with the background color
+  ::ReplaceMaskColorWithBackgroundColor(final->GetOutput(), this);
+
+  output->DeepCopy(final->GetOutput());
+
+  // Remove duplicates in generated word vectors.
+  std::sort(this->StoppedWords.begin(), this->StoppedWords.end());
+  this->StoppedWords.erase(std::unique(this->StoppedWords.begin(),
+                                       this->StoppedWords.end()),
+                           this->StoppedWords.end());
+  std::sort(this->SkippedWords.begin(), this->SkippedWords.end());
+  this->SkippedWords.erase(std::unique(this->SkippedWords.begin(),
+                                       this->SkippedWords.end()),
+                           this->SkippedWords.end());
+  std::sort(this->KeptWords.begin(), this->KeptWords.end());
+  this->KeptWords.erase(std::unique(this->KeptWords.begin(),
+                                    this->KeptWords.end()),
+                           this->KeptWords.end());
+
+  return 1;
+}
+
+//--------------------------------------------------------------------------
+void vtkWordCloud::PrintSelf(ostream& os, vtkIndent indent)
+{
+  this->Superclass::PrintSelf(os,indent);
+
+  os << "  BackgroundColorName: " << this->GetBackgroundColorName() << std::endl;
+  os << "  BWMask: " << (this->GetBWMask() ? "true" : "false") << std::endl;
+  os << "  ColorDistribution: " << this->GetColorDistribution()[0] << " " << this->GetColorDistribution()[1] << std::endl;
+  os << "  ColorSchemeName: " <<  this->GetColorSchemeName() << std::endl;
+  os << "  DPI: " << this->GetDPI() << std::endl;
+  os << "  FontFileName: " << this->GetFontFileName() << std::endl;
+  os << "  FontMultiplier: " << this->GetFontMultiplier() << std::endl;
+  os << "  Gap: " << this->GetGap() << std::endl;
+  os << "  MaskColorName: " << this->GetMaskColorName() << std::endl;
+  os << "  MaskFileName: " << this->GetMaskFileName() << std::endl;
+  os << "  MinFontSize: " << this->GetMinFontSize() << std::endl;
+  os << "  MaxFontSize: " << this->GetMaxFontSize() << std::endl;
+  os << "  MinFrequency: " << this->GetMinFrequency() << std::endl;
+  os << "  OffsetDistribution: "
+     << this->GetOffsetDistribution()[0] << " "
+     << this->GetOffsetDistribution()[1] << std::endl;
+  os << "  OrientationDistribution: "
+     << this->GetOrientationDistribution()[0] << " "
+     << this->GetOrientationDistribution()[1] << std::endl;
+  os << "  Orientations: ";
+  for (auto o : this->Orientations)
+  {
+    os << o << " ";
+  }
+  os << std::endl;
+  os << "  ReplacementPairs: ";
+  for (auto p : this->ReplacementPairs)
+  {
+    os << std::get<0>(p) << "->" << std::get<1>(p) << " ";
+  }
+  os << std::endl;
+  os << "  Sizes: "
+     << this->GetSizes()[0] << " "
+     << this->GetSizes()[1] << std::endl;
+  os << "  StopWords: ";
+  for (auto s : this->GetStopWords())
+  {
+    os << s << " ";
+  }
+  os << std::endl;
+  os << "  StopListFileName: " << this->GetStopListFileName() << std::endl;
+  os << "  FileName: " << this->GetFileName() << std::endl;
+  os << "  Title: " << GetTitle() << std::endl;
+  os << "  WordColorName: " << GetWordColorName() << std::endl;
+}
+
+namespace
+{
+std::multiset<std::pair<std::string, int>, Comparator > FindWordsSortedByFrequency(std::string &s, vtkWordCloud *wordCloud)
+{
+  // Create a stop list
+  vtkWordCloud::StopWordsContainer stopList;
+
+  // If a StopListFileName is defined, use it, otherwise use the
+  // built-in StopList.
+  if (wordCloud->GetStopListFileName().size() > 0)
+  {
+    CreateStopListFromFile(wordCloud->GetStopListFileName(), stopList);
+  }
+  else
+  {
+    CreateBuiltInStopList(stopList);
+  }
+
+  // Add user stop words
+  for (auto stop : wordCloud->GetStopWords())
+  {
+    stopList.insert(stop);
+  }
+
+  // Add replacement pairs to StopList
+  ::AddReplacementPairsToStopList(wordCloud, stopList);
+
+  // Drop the case of all words
+  std::transform(s.begin(), s.end(), s.begin(), ::tolower);
+
+  // Extract words
+  std::regex wordRegex("(\\w+)");
+  auto wordsBegin =
+    std::sregex_iterator(s.begin(), s.end(), wordRegex);
+  auto wordsEnd = std::sregex_iterator();
+
+  // Store the words in a map that will contain frequencies
+  std::map<std::string, int> wordContainer;
+
+  // If a title is present add it with a high frequency
+  if (wordCloud->GetTitle().length() > 0)
+  {
+    wordContainer[wordCloud->GetTitle()] = 1000;
+  }
+  const int N = 1;
+
+  for (std::sregex_iterator i = wordsBegin; i != wordsEnd; ++i)
+  {
+    std::string matchStr = (*i).str();
+
+    // Replace words with another
+    for (auto p : wordCloud->GetReplacementPairs())
+    {
+      std::string from = std::get<0>(p);
+      std::string to = std::get<1>(p);
+      size_t pos = 0;
+      pos = matchStr.find(from, pos);
+      if (matchStr.length() == from.length() && pos == 0)
+      {
+        matchStr.replace(pos, to.length(), to);
+        stopList.insert(from);
+      }
+    }
+
+    // Skip the word if it is in the stop list or contains a digit
+    auto it = stopList.find(matchStr);
+    const auto digit = (*i).str().find_first_of("0123456789");
+    if (it != stopList.end() || digit != std::string::npos)
+    {
+      wordCloud->GetStoppedWords().push_back(*it);
+      continue;
+    }
+
+    // Only include words that have more than N characters
+    if (matchStr.size() > N)
+    {
+      // Raise the case of the first letter in the word
+      std::transform(matchStr.begin(), matchStr.begin() + 1,
+                     matchStr.begin(), ::toupper);
+      wordContainer[matchStr]++;
+    }
+  }
+
+  // Defining a lambda function to compare two pairs. It will compare
+  // two pairs using second field
+  Comparator compFunctor =
+    [](const std::pair<std::string, int>& elem1 ,const std::pair<std::string, int>& elem2)
+    {
+      if (elem1.second == elem2.second)
+      {
+        return elem1.first.length() > elem2.first.length();
+      }
+      return elem1.second  > elem2.second;
+    };
+
+  // Declaring a multiset that will store the pairs using above
+  // comparison logic
+  std::multiset<std::pair<std::string, int>, Comparator> setOfWords(
+    wordContainer.begin(), wordContainer.end(), compFunctor);
+  return setOfWords;
+}
+
+void AddReplacementPairsToStopList(
+  vtkWordCloud * wordCloud,
+  vtkWordCloud::StopWordsContainer & stopList)
+{
+  std::regex wordRegex("(\\w+)");
+  for (auto p : wordCloud->GetReplacementPairs())
+  {
+    std::string from = std::get<0>(p);
+    std::string to = std::get<1>(p);
+    stopList.insert(from);
+
+    // The replacement may contain multiple strings and may have upper
+    // case letters
+    std::transform(to.begin(), to.end(), to.begin(), ::tolower);
+
+    // Add each replacement to the stop list
+    auto wordsBegin =
+      std::sregex_iterator(to.begin(), to.end(), wordRegex);
+    auto wordsEnd = std::sregex_iterator();
+    for (std::sregex_iterator i = wordsBegin; i != wordsEnd; ++i)
+    {
+      std::cout << "Replacement: " << (*i).str() << std::endl;
+      stopList.insert((*i).str());
+    }
+  }
+}
+
+bool AddWordToFinal(vtkWordCloud *wordCloud,
+                    const std::string word,
+                    const int frequency,
+                    std::mt19937_64 &mt,
+                    double orientation,
+                    std::vector<ExtentOffset> &offset,
+                    vtkImageBlend *final,
+                    std::string &errorMessage)
+{
+  // Skip words below MinFrequency
+  if (frequency < wordCloud->GetMinFrequency())
+  {
+    return false;
+  }
+
+  // Create random distributions
+  std::uniform_real_distribution<> colorDist(
+    wordCloud->GetColorDistribution()[0], wordCloud->GetColorDistribution()[1]);
+
+  // Setup a property for the strings containing fixed parameters
+  auto colors = vtkSmartPointer<vtkNamedColors>::New();
+  auto textProperty = vtkSmartPointer<vtkTextProperty>::New();
+  if (wordCloud->GetWordColorName().length() > 0)
+  {
+    textProperty->SetColor(colors->GetColor3d(wordCloud->GetWordColorName()).GetData());
+  }
+  else if (wordCloud->GetColorSchemeName().length() > 0)
+  {
+    auto colorScheme = vtkSmartPointer<vtkColorSeries>::New();
+    colorScheme->SetColorSchemeByName(wordCloud->GetColorSchemeName());
+    vtkColor3ub color =
+      colorScheme->GetColorRepeating(static_cast<int>(wordCloud->GetKeptWords().size()));
+    if (color.Compare(colors->GetColor3ub("black"), 1) && wordCloud->GetKeptWords().size() == 0)
+      {
+        std::ostringstream validNames;
+        ShowColorSeriesNames(validNames);
+        std::ostringstream message;
+        message << "The color scheme "
+                << wordCloud->GetColorSchemeName()
+                << " does not exist.\n"
+                << validNames.str();
+        errorMessage = message.str();
+      }
+    textProperty->SetColor(color.GetRed() * 255.0, color.GetGreen() * 255.0 , color.GetBlue() * 255.0);
+  }
+  else
+  {
+    textProperty->SetColor(colorDist(mt), colorDist(mt), colorDist(mt));
+  }
+  textProperty->SetVerticalJustificationToCentered();
+  textProperty->SetJustificationToCentered();
+  textProperty->SetLineOffset(4);
+
+  // Check if a font file is present
+  if (wordCloud->GetFontFileName().length() > 0)
+  {
+    textProperty->SetFontFile(wordCloud->GetFontFileName().c_str());
+    textProperty->SetFontFamily(VTK_FONT_FILE);
+  }
+  else
+  {
+    textProperty->SetFontFamilyToArial();
+  }
+
+  // Set the font size
+  int fontSize = wordCloud->GetFontMultiplier() * frequency;
+  if (fontSize > wordCloud->GetMaxFontSize())
+  {
+    fontSize = wordCloud->GetMaxFontSize();
+  }
+  if (fontSize < wordCloud->GetMinFontSize())
+  {
+    fontSize = wordCloud->GetMinFontSize();
+  }
+  if (frequency == 1000)
+  {
+    fontSize *= 1.2;;
+  }
+  textProperty->SetFontSize(fontSize);
+  textProperty->SetOrientation(orientation);
+
+  // Add gap
+  std::string spaces;
+  for (int p = 0; p < wordCloud->GetGap(); ++p)
+  {
+    spaces.push_back(' ');
+  }
+
+  // For each string, create an image and see if it overlaps with other images,
+  // if so, skip it
+  // Create an image of the string
+  vtkFreeTypeTools *freeType = vtkFreeTypeTools::GetInstance();
+  freeType->ScaleToPowerTwoOff();
+
+  auto textImage = vtkSmartPointer<vtkImageData>::New();
+  freeType->RenderString(textProperty,
+                         spaces + word + spaces,
+                         wordCloud->GetDPI(),
+                         textImage.GetPointer());
+
+  // Set the extent of the text image
+  std::array<int, 4> bb;
+  freeType->GetBoundingBox(textProperty,
+                           spaces + word + spaces,
+                           wordCloud->GetDPI(),
+                           bb.data());
+  vtkColor3ub maskColor = colors->GetColor3ub(wordCloud->GetMaskColorName().c_str());
+  unsigned char maskR = maskColor.GetData()[0];
+  unsigned char maskG = maskColor.GetData()[1];
+  unsigned char maskB = maskColor.GetData()[2];
+
+  std::uniform_real_distribution<> offsetDist(
+    wordCloud->GetOffsetDistribution()[0],
+    wordCloud->GetOffsetDistribution()[1]);
+
+  for (auto of : offset)
+  {
+    int offsetX = of.x + offsetDist(mt); // add some noise to the offset
+    int offsetY = of.y + offsetDist(mt);
+    // Make sure the text image will fit on the final image
+    if (offsetX + bb[1] - bb[0] < wordCloud->GetAdjustedSizes()[0] - 1 &&
+        offsetY + bb[3] - bb[2] < wordCloud->GetAdjustedSizes()[1] - 1 &&
+        offsetX >= 0 && offsetY >= 0)
+    {
+      textImage->SetExtent(offsetX, offsetX + bb[1] - bb[0],
+                           offsetY, offsetY + bb[3] - bb[2],
+                           0, 0);
+      auto image = vtkSmartPointer<vtkImageData>::New();
+      final->Update();
+
+      // Does the text image overlap with images on the final image
+      vtkImageIterator<unsigned char> finalIt(final->GetOutput(),
+                                              textImage->GetExtent());
+      bool good = true;
+      while( !finalIt.IsAtEnd())
+      {
+        auto finalSpan = finalIt.BeginSpan();
+        while(finalSpan != finalIt.EndSpan())
+        {
+          unsigned char R, G, B;
+          R = *finalSpan++;
+          G = *finalSpan++;
+          B = *finalSpan++;
+          // If the pixel does not contain the background color, the word will not fit
+          if (R != maskR && G != maskG && B != maskB)
+          {
+            good = false;
+            break;
+          }
+        }
+        if (!good)
+        {
+          break;
+        }
+        finalIt.NextSpan();
+      }
+      if (good)
+      {
+        final->AddInputData(textImage);
+        final->Update();
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+void ArchimedesSpiral(std::vector<ExtentOffset> &offset, vtkWordCloud::SizesContainer &sizes)
+{
+   const int centerX = sizes[0] / 2.0;
+   const int centerY = sizes[1] / 2.0;
+
+   const std::size_t N = 10000;
+   constexpr auto pi = 3.141592653589793238462643383279502884L; /* pi */
+   const double deltaAngle = pi * 20 / N;
+   double maxX = -1000.0;
+   double minX = 1000.0;
+   double maxY = -1000.0;
+   double minY = 1000.0;
+   double range = -1000;
+   double e = sizes[0] / sizes[1];
+   std::vector<ArchimedesValue> archimedes;
+   for (std::size_t i = 0; i < N; i += 10)
+   {
+     double x, y;
+     double angle = deltaAngle * i;
+     x = e * angle * std::cos(angle);
+     y = e * angle * std::sin(angle);
+     archimedes.push_back(ArchimedesValue(x, y));
+     maxX = std::max(maxX, x);
+     minX = std::min(minX, x);
+     maxY = std::max(maxY, y);
+     minY = std::min(minY, y);
+     range = std::max(maxX - minX, maxY - minY);
+   }
+   double scaleX = 1.0 / range * sizes[0];
+   for (auto ar : archimedes)
+   {
+     if (ar.x * scaleX + centerX - 50 < 0
+         || ar.y * scaleX + centerY  < 0) continue;
+     offset.push_back(ExtentOffset(ar.x * scaleX + centerX - 50,
+                                   ar.y * scaleX + centerY));
+   }
+}
+void ReplaceMaskColorWithBackgroundColor(
+  vtkImageData* finalImage, vtkWordCloud* wordCloud)
+{
+  auto colors = vtkSmartPointer<vtkNamedColors>::New();
+
+  vtkColor3ub backgroundColor =
+    colors->GetColor3ub(wordCloud->GetBackgroundColorName().c_str());
+  unsigned char bkgR = backgroundColor.GetData()[0];
+  unsigned char bkgG = backgroundColor.GetData()[1];
+  unsigned char bkgB = backgroundColor.GetData()[2];
+
+  vtkColor3ub maskColor =
+    colors->GetColor3ub(wordCloud->GetMaskColorName().c_str());
+  unsigned char maskR = maskColor.GetData()[0];
+  unsigned char maskG = maskColor.GetData()[1];
+  unsigned char maskB = maskColor.GetData()[2];
+
+  vtkImageIterator<unsigned char> finalIt(finalImage,
+                                          finalImage->GetExtent());
+  while( !finalIt.IsAtEnd())
+  {
+    auto finalSpan = finalIt.BeginSpan();
+    while(finalSpan != finalIt.EndSpan())
+    {
+      unsigned char R, G, B;
+      R = *finalSpan;
+      G = *(finalSpan + 1);
+      B = *(finalSpan + 2);
+      // If the pixel does not contain the background color, skip
+      // it. Otherwise replace it with the background color.
+      if (R != maskR && G != maskG && B != maskB)
+      {
+        finalSpan += 3;
+        continue;
+      }
+      else
+      {
+        *finalSpan = bkgR;
+        *(finalSpan + 1) = bkgG;
+        *(finalSpan + 2) = bkgB;
+      }
+      finalSpan += 3;
+    }
+  finalIt.NextSpan();
+  }
+}
+
+void ShowColorSeriesNames(ostream& os)
+{
+  auto colorSeries = vtkSmartPointer<vtkColorSeries>::New();
+  os << "Valid schemes" << std::endl;
+  for (auto i = 0; i < colorSeries->GetNumberOfColorSchemes(); ++i)
+  {
+    colorSeries->SetColorScheme(i);
+    os << "  " << colorSeries->GetColorSchemeName() << std::endl;
+  }
+}
+
+void CreateStopListFromFile(std::string fileName, vtkWordCloud::StopWordsContainer &stopList)
+{
+  std::ifstream t(fileName);
+  std::stringstream buffer;
+  buffer << t.rdbuf();
+  std::string s = buffer.str();
+  t.close();
+
+  // Extract words
+  std::regex wordRegex("(\\w+)");
+  auto wordsBegin =
+    std::sregex_iterator(s.begin(), s.end(), wordRegex);
+  auto wordsEnd = std::sregex_iterator();
+  for (std::sregex_iterator i = wordsBegin; i != wordsEnd; ++i)
+  {
+    std::string matchStr = (*i).str();
+    stopList.insert(matchStr);
+  }
+}
+
+void CreateBuiltInStopList(vtkWordCloud::StopWordsContainer &stopList)
+{
+  stopList.insert("a");
+  stopList.insert("able");
+  stopList.insert("about");
+  stopList.insert("above");
+  stopList.insert("abst");
+  stopList.insert("accordance");
+  stopList.insert("according");
+  stopList.insert("accordingly");
+  stopList.insert("across");
+  stopList.insert("act");
+  stopList.insert("actually");
+  stopList.insert("added");
+  stopList.insert("adj");
+  stopList.insert("affected");
+  stopList.insert("affecting");
+  stopList.insert("affects");
+  stopList.insert("after");
+  stopList.insert("afterwards");
+  stopList.insert("again");
+  stopList.insert("against");
+  stopList.insert("ah");
+  stopList.insert("all");
+  stopList.insert("almost");
+  stopList.insert("alone");
+  stopList.insert("along");
+  stopList.insert("already");
+  stopList.insert("also");
+  stopList.insert("although");
+  stopList.insert("always");
+  stopList.insert("am");
+  stopList.insert("among");
+  stopList.insert("amongst");
+  stopList.insert("an");
+  stopList.insert("and");
+  stopList.insert("announce");
+  stopList.insert("another");
+  stopList.insert("any");
+  stopList.insert("anybody");
+  stopList.insert("anyhow");
+  stopList.insert("anymore");
+  stopList.insert("anyone");
+  stopList.insert("anything");
+  stopList.insert("anyway");
+  stopList.insert("anyways");
+  stopList.insert("anywhere");
+  stopList.insert("apparently");
+  stopList.insert("approximately");
+  stopList.insert("are");
+  stopList.insert("aren");
+  stopList.insert("arent");
+  stopList.insert("arise");
+  stopList.insert("around");
+  stopList.insert("as");
+  stopList.insert("aside");
+  stopList.insert("ask");
+  stopList.insert("asking");
+  stopList.insert("at");
+  stopList.insert("auth");
+  stopList.insert("available");
+  stopList.insert("away");
+  stopList.insert("awfully");
+  stopList.insert("b");
+  stopList.insert("back");
+  stopList.insert("be");
+  stopList.insert("became");
+  stopList.insert("because");
+  stopList.insert("become");
+  stopList.insert("becomes");
+  stopList.insert("becoming");
+  stopList.insert("been");
+  stopList.insert("before");
+  stopList.insert("beforehand");
+  stopList.insert("begin");
+  stopList.insert("beginning");
+  stopList.insert("beginnings");
+  stopList.insert("begins");
+  stopList.insert("behind");
+  stopList.insert("being");
+  stopList.insert("believe");
+  stopList.insert("below");
+  stopList.insert("beside");
+  stopList.insert("besides");
+  stopList.insert("between");
+  stopList.insert("beyond");
+  stopList.insert("biol");
+  stopList.insert("both");
+  stopList.insert("brief");
+  stopList.insert("briefly");
+  stopList.insert("but");
+  stopList.insert("by");
+  stopList.insert("c");
+  stopList.insert("ca");
+  stopList.insert("came");
+  stopList.insert("can");
+  stopList.insert("cannot");
+  stopList.insert("can't");
+  stopList.insert("cause");
+  stopList.insert("causes");
+  stopList.insert("certain");
+  stopList.insert("certainly");
+  stopList.insert("co");
+  stopList.insert("com");
+  stopList.insert("come");
+  stopList.insert("comes");
+  stopList.insert("contain");
+  stopList.insert("containing");
+  stopList.insert("contains");
+  stopList.insert("could");
+  stopList.insert("couldnt");
+  stopList.insert("cum");
+  stopList.insert("d");
+  stopList.insert("date");
+  stopList.insert("did");
+  stopList.insert("didn't");
+  stopList.insert("different");
+  stopList.insert("do");
+  stopList.insert("does");
+  stopList.insert("doesn't");
+  stopList.insert("doing");
+  stopList.insert("done");
+  stopList.insert("don't");
+  stopList.insert("down");
+  stopList.insert("downwards");
+  stopList.insert("due");
+  stopList.insert("dr");
+  stopList.insert("during");
+  stopList.insert("e");
+  stopList.insert("each");
+  stopList.insert("ed");
+  stopList.insert("edu");
+  stopList.insert("effect");
+  stopList.insert("eg");
+  stopList.insert("eight");
+  stopList.insert("eighty");
+  stopList.insert("either");
+  stopList.insert("else");
+  stopList.insert("elsewhere");
+  stopList.insert("end");
+  stopList.insert("ending");
+  stopList.insert("enough");
+  stopList.insert("especially");
+  stopList.insert("et");
+  stopList.insert("et-al");
+  stopList.insert("etc");
+  stopList.insert("even");
+  stopList.insert("ever");
+  stopList.insert("every");
+  stopList.insert("everybody");
+  stopList.insert("everyone");
+  stopList.insert("everything");
+  stopList.insert("everywhere");
+  stopList.insert("ex");
+  stopList.insert("except");
+  stopList.insert("f");
+  stopList.insert("far");
+  stopList.insert("few");
+  stopList.insert("ff");
+  stopList.insert("fifth");
+  stopList.insert("first");
+  stopList.insert("five");
+  stopList.insert("fix");
+  stopList.insert("followed");
+  stopList.insert("following");
+  stopList.insert("follows");
+  stopList.insert("for");
+  stopList.insert("former");
+  stopList.insert("formerly");
+  stopList.insert("forth");
+  stopList.insert("found");
+  stopList.insert("four");
+  stopList.insert("from");
+  stopList.insert("further");
+  stopList.insert("furthermore");
+  stopList.insert("g");
+  stopList.insert("gave");
+  stopList.insert("get");
+  stopList.insert("gets");
+  stopList.insert("getting");
+  stopList.insert("give");
+  stopList.insert("given");
+  stopList.insert("gives");
+  stopList.insert("giving");
+  stopList.insert("go");
+  stopList.insert("goes");
+  stopList.insert("gone");
+  stopList.insert("got");
+  stopList.insert("gotten");
+  stopList.insert("h");
+  stopList.insert("had");
+  stopList.insert("happens");
+  stopList.insert("hardly");
+  stopList.insert("has");
+  stopList.insert("hasn");
+  stopList.insert("have");
+  stopList.insert("haven");
+  stopList.insert("having");
+  stopList.insert("he");
+  stopList.insert("hed");
+  stopList.insert("hence");
+  stopList.insert("her");
+  stopList.insert("here");
+  stopList.insert("hereafter");
+  stopList.insert("hereby");
+  stopList.insert("herein");
+  stopList.insert("heres");
+  stopList.insert("hereupon");
+  stopList.insert("hers");
+  stopList.insert("herself");
+  stopList.insert("hes");
+  stopList.insert("hi");
+  stopList.insert("hid");
+  stopList.insert("him");
+  stopList.insert("himself");
+  stopList.insert("his");
+  stopList.insert("hither");
+  stopList.insert("home");
+  stopList.insert("how");
+  stopList.insert("howbeit");
+  stopList.insert("however");
+  stopList.insert("hundred");
+  stopList.insert("i");
+  stopList.insert("id");
+  stopList.insert("ie");
+  stopList.insert("if");
+  stopList.insert("im");
+  stopList.insert("immediate");
+  stopList.insert("immediately");
+  stopList.insert("importance");
+  stopList.insert("important");
+  stopList.insert("in");
+  stopList.insert("inc");
+  stopList.insert("indeed");
+  stopList.insert("index");
+  stopList.insert("information");
+  stopList.insert("instead");
+  stopList.insert("into");
+  stopList.insert("invention");
+  stopList.insert("inward");
+  stopList.insert("is");
+  stopList.insert("isn");
+  stopList.insert("it");
+  stopList.insert("itd");
+  stopList.insert("it");
+  stopList.insert("its");
+  stopList.insert("itself");
+  stopList.insert("j");
+  stopList.insert("jr");
+  stopList.insert("just");
+  stopList.insert("k");
+  stopList.insert("keep");
+  stopList.insert("keeps");
+  stopList.insert("kept");
+  stopList.insert("kg");
+  stopList.insert("km");
+  stopList.insert("know");
+  stopList.insert("known");
+  stopList.insert("knows");
+  stopList.insert("l");
+  stopList.insert("largely");
+  stopList.insert("last");
+  stopList.insert("lately");
+  stopList.insert("later");
+  stopList.insert("latter");
+  stopList.insert("latterly");
+  stopList.insert("laude");
+  stopList.insert("least");
+  stopList.insert("less");
+  stopList.insert("lest");
+  stopList.insert("let");
+  stopList.insert("lets");
+  stopList.insert("like");
+  stopList.insert("liked");
+  stopList.insert("likely");
+  stopList.insert("line");
+  stopList.insert("little");
+  stopList.insert("ll");
+  stopList.insert("look");
+  stopList.insert("looking");
+  stopList.insert("looks");
+  stopList.insert("ltd");
+  stopList.insert("m");
+  stopList.insert("made");
+  stopList.insert("mainly");
+  stopList.insert("make");
+  stopList.insert("makes");
+  stopList.insert("many");
+  stopList.insert("may");
+  stopList.insert("maybe");
+  stopList.insert("me");
+  stopList.insert("mean");
+  stopList.insert("means");
+  stopList.insert("meantime");
+  stopList.insert("meanwhile");
+  stopList.insert("merely");
+  stopList.insert("met");
+  stopList.insert("mg");
+  stopList.insert("mic");
+  stopList.insert("might");
+  stopList.insert("million");
+  stopList.insert("miss");
+  stopList.insert("ml");
+  stopList.insert("more");
+  stopList.insert("moreover");
+  stopList.insert("most");
+  stopList.insert("mostly");
+  stopList.insert("mr");
+  stopList.insert("mrs");
+  stopList.insert("much");
+  stopList.insert("mug");
+  stopList.insert("must");
+  stopList.insert("my");
+  stopList.insert("myself");
+  stopList.insert("n");
+  stopList.insert("na");
+  stopList.insert("name");
+  stopList.insert("namely");
+  stopList.insert("nay");
+  stopList.insert("nd");
+  stopList.insert("near");
+  stopList.insert("nearly");
+  stopList.insert("necessarily");
+  stopList.insert("necessary");
+  stopList.insert("need");
+  stopList.insert("needs");
+  stopList.insert("neither");
+  stopList.insert("never");
+  stopList.insert("nevertheless");
+  stopList.insert("new");
+  stopList.insert("next");
+  stopList.insert("nine");
+  stopList.insert("ninety");
+  stopList.insert("no");
+  stopList.insert("nobody");
+  stopList.insert("non");
+  stopList.insert("none");
+  stopList.insert("nonetheless");
+  stopList.insert("noone");
+  stopList.insert("nor");
+  stopList.insert("normally");
+  stopList.insert("nos");
+  stopList.insert("not");
+  stopList.insert("noted");
+  stopList.insert("nothing");
+  stopList.insert("now");
+  stopList.insert("nowhere");
+  stopList.insert("o");
+  stopList.insert("obtain");
+  stopList.insert("obtained");
+  stopList.insert("obviously");
+  stopList.insert("of");
+  stopList.insert("off");
+  stopList.insert("often");
+  stopList.insert("oh");
+  stopList.insert("ok");
+  stopList.insert("okay");
+  stopList.insert("old");
+  stopList.insert("omitted");
+  stopList.insert("on");
+  stopList.insert("once");
+  stopList.insert("one");
+  stopList.insert("ones");
+  stopList.insert("only");
+  stopList.insert("onto");
+  stopList.insert("or");
+  stopList.insert("ord");
+  stopList.insert("org");
+  stopList.insert("other");
+  stopList.insert("others");
+  stopList.insert("otherwise");
+  stopList.insert("ought");
+  stopList.insert("our");
+  stopList.insert("ours");
+  stopList.insert("ourselves");
+  stopList.insert("out");
+  stopList.insert("outside");
+  stopList.insert("over");
+  stopList.insert("overall");
+  stopList.insert("owing");
+  stopList.insert("own");
+  stopList.insert("p");
+  stopList.insert("page");
+  stopList.insert("pages");
+  stopList.insert("part");
+  stopList.insert("particular");
+  stopList.insert("particularly");
+  stopList.insert("past");
+  stopList.insert("per");
+  stopList.insert("perhaps");
+  stopList.insert("ph");
+  stopList.insert("placed");
+  stopList.insert("please");
+  stopList.insert("plus");
+  stopList.insert("poorly");
+  stopList.insert("possible");
+  stopList.insert("possibly");
+  stopList.insert("potentially");
+  stopList.insert("pp");
+  stopList.insert("predominantly");
+  stopList.insert("present");
+  stopList.insert("previously");
+  stopList.insert("primarily");
+  stopList.insert("probably");
+  stopList.insert("promptly");
+  stopList.insert("proud");
+  stopList.insert("provides");
+  stopList.insert("put");
+  stopList.insert("q");
+  stopList.insert("que");
+  stopList.insert("quickly");
+  stopList.insert("quite");
+  stopList.insert("qv");
+  stopList.insert("r");
+  stopList.insert("ran");
+  stopList.insert("rather");
+  stopList.insert("rd");
+  stopList.insert("re");
+  stopList.insert("readily");
+  stopList.insert("really");
+  stopList.insert("recent");
+  stopList.insert("recently");
+  stopList.insert("ref");
+  stopList.insert("refs");
+  stopList.insert("regarding");
+  stopList.insert("regardless");
+  stopList.insert("regards");
+  stopList.insert("related");
+  stopList.insert("relatively");
+  stopList.insert("research");
+  stopList.insert("respectively");
+  stopList.insert("resulted");
+  stopList.insert("resulting");
+  stopList.insert("results");
+  stopList.insert("right");
+  stopList.insert("run");
+  stopList.insert("s");
+  stopList.insert("said");
+  stopList.insert("same");
+  stopList.insert("saw");
+  stopList.insert("sat");
+  stopList.insert("say");
+  stopList.insert("saying");
+  stopList.insert("says");
+  stopList.insert("sec");
+  stopList.insert("section");
+  stopList.insert("see");
+  stopList.insert("seeing");
+  stopList.insert("seem");
+  stopList.insert("seemed");
+  stopList.insert("seeming");
+  stopList.insert("seems");
+  stopList.insert("seen");
+  stopList.insert("self");
+  stopList.insert("selves");
+  stopList.insert("sent");
+  stopList.insert("seven");
+  stopList.insert("several");
+  stopList.insert("shall");
+  stopList.insert("she");
+  stopList.insert("shed");
+  stopList.insert("shes");
+  stopList.insert("should");
+  stopList.insert("shouldn");
+  stopList.insert("show");
+  stopList.insert("showed");
+  stopList.insert("shown");
+  stopList.insert("showns");
+  stopList.insert("shows");
+  stopList.insert("significant");
+  stopList.insert("significantly");
+  stopList.insert("similar");
+  stopList.insert("similarly");
+  stopList.insert("since");
+  stopList.insert("six");
+  stopList.insert("slightly");
+  stopList.insert("so");
+  stopList.insert("some");
+  stopList.insert("somebody");
+  stopList.insert("somehow");
+  stopList.insert("someone");
+  stopList.insert("somethan");
+  stopList.insert("something");
+  stopList.insert("sometime");
+  stopList.insert("sometimes");
+  stopList.insert("somewhat");
+  stopList.insert("somewhere");
+  stopList.insert("soon");
+  stopList.insert("sorry");
+  stopList.insert("specifically");
+  stopList.insert("specified");
+  stopList.insert("specify");
+  stopList.insert("specifying");
+  stopList.insert("still");
+  stopList.insert("stop");
+  stopList.insert("strongly");
+  stopList.insert("sub");
+  stopList.insert("substantially");
+  stopList.insert("successfully");
+  stopList.insert("such");
+  stopList.insert("sufficiently");
+  stopList.insert("suggest");
+  stopList.insert("sup");
+  stopList.insert("sure");
+  stopList.insert("t");
+  stopList.insert("take");
+  stopList.insert("taken");
+  stopList.insert("taking");
+  stopList.insert("tell");
+  stopList.insert("tends");
+  stopList.insert("th");
+  stopList.insert("than");
+  stopList.insert("thank");
+  stopList.insert("thanks");
+  stopList.insert("thanx");
+  stopList.insert("that");
+  stopList.insert("thats");
+  stopList.insert("the");
+  stopList.insert("their");
+  stopList.insert("theirs");
+  stopList.insert("them");
+  stopList.insert("themselves");
+  stopList.insert("then");
+  stopList.insert("thence");
+  stopList.insert("there");
+  stopList.insert("thereafter");
+  stopList.insert("thereby");
+  stopList.insert("thered");
+  stopList.insert("therefore");
+  stopList.insert("therein");
+  stopList.insert("thereof");
+  stopList.insert("therere");
+  stopList.insert("theres");
+  stopList.insert("thereto");
+  stopList.insert("thereupon");
+  stopList.insert("these");
+  stopList.insert("they");
+  stopList.insert("theyd");
+  stopList.insert("theyre");
+  stopList.insert("think");
+  stopList.insert("this");
+  stopList.insert("those");
+  stopList.insert("thou");
+  stopList.insert("though");
+  stopList.insert("thoughh");
+  stopList.insert("thousand");
+  stopList.insert("throug");
+  stopList.insert("through");
+  stopList.insert("throughout");
+  stopList.insert("thru");
+  stopList.insert("thus");
+  stopList.insert("til");
+  stopList.insert("tip");
+  stopList.insert("to");
+  stopList.insert("together");
+  stopList.insert("too");
+  stopList.insert("took");
+  stopList.insert("toward");
+  stopList.insert("towards");
+  stopList.insert("tried");
+  stopList.insert("tries");
+  stopList.insert("truly");
+  stopList.insert("try");
+  stopList.insert("trying");
+  stopList.insert("ts");
+  stopList.insert("twice");
+  stopList.insert("two");
+  stopList.insert("u");
+  stopList.insert("un");
+  stopList.insert("under");
+  stopList.insert("unfortunately");
+  stopList.insert("unless");
+  stopList.insert("unlike");
+  stopList.insert("unlikely");
+  stopList.insert("until");
+  stopList.insert("unto");
+  stopList.insert("up");
+  stopList.insert("upon");
+  stopList.insert("ups");
+  stopList.insert("us");
+  stopList.insert("use");
+  stopList.insert("used");
+  stopList.insert("useful");
+  stopList.insert("usefully");
+  stopList.insert("usefulness");
+  stopList.insert("uses");
+  stopList.insert("using");
+  stopList.insert("usually");
+  stopList.insert("v");
+  stopList.insert("value");
+  stopList.insert("various");
+  stopList.insert("ve");
+  stopList.insert("very");
+  stopList.insert("via");
+  stopList.insert("viz");
+  stopList.insert("vol");
+  stopList.insert("vols");
+  stopList.insert("vs");
+  stopList.insert("w");
+  stopList.insert("want");
+  stopList.insert("wants");
+  stopList.insert("was");
+  stopList.insert("wasnt");
+  stopList.insert("wasnt");
+  stopList.insert("way");
+  stopList.insert("we");
+  stopList.insert("wed");
+  stopList.insert("welcome");
+  stopList.insert("went");
+  stopList.insert("were");
+  stopList.insert("werent");
+  stopList.insert("what");
+  stopList.insert("whatever");
+  stopList.insert("whats");
+  stopList.insert("when");
+  stopList.insert("whence");
+  stopList.insert("whenever");
+  stopList.insert("where");
+  stopList.insert("whereafter");
+  stopList.insert("whereas");
+  stopList.insert("whereby");
+  stopList.insert("wherein");
+  stopList.insert("wheres");
+  stopList.insert("whereupon");
+  stopList.insert("wherever");
+  stopList.insert("whether");
+  stopList.insert("which");
+  stopList.insert("while");
+  stopList.insert("whim");
+  stopList.insert("whither");
+  stopList.insert("who");
+  stopList.insert("whod");
+  stopList.insert("whoever");
+  stopList.insert("whole");
+  stopList.insert("whom");
+  stopList.insert("whomever");
+  stopList.insert("whos");
+  stopList.insert("whose");
+  stopList.insert("why");
+  stopList.insert("widely");
+  stopList.insert("will");
+  stopList.insert("willing");
+  stopList.insert("wish");
+  stopList.insert("with");
+  stopList.insert("within");
+  stopList.insert("without");
+  stopList.insert("wont");
+  stopList.insert("words");
+  stopList.insert("world");
+  stopList.insert("would");
+  stopList.insert("wouldnt");
+  stopList.insert("www");
+  stopList.insert("x");
+  stopList.insert("y");
+  stopList.insert("yes");
+  stopList.insert("yet");
+  stopList.insert("you");
+  stopList.insert("youd");
+  stopList.insert("your");
+  stopList.insert("youre");
+  stopList.insert("yours");
+  stopList.insert("yourself");
+  stopList.insert("yourselves");
+  stopList.insert("z");
+  stopList.insert("zero");
+}
+}
diff --git a/Infovis/Core/vtkWordCloud.h b/Infovis/Core/vtkWordCloud.h
new file mode 100644
index 00000000000..3c53f452bf2
--- /dev/null
+++ b/Infovis/Core/vtkWordCloud.h
@@ -0,0 +1,633 @@
+/*=========================================================================
+
+  Program:   Visualization Toolkit
+  Module:    vtkWordCloud.h
+
+  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+  All rights reserved.
+  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
+
+     This software is distributed WITHOUT ANY WARRANTY; without even
+     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+     PURPOSE.  See the above copyright notice for more information.
+
+=========================================================================*/
+#ifndef vtkWordCloud_h
+#define vtkWordCloud_h
+
+#include "vtkInfovisCoreModule.h" // For export macro
+#include "vtkImageAlgorithm.h"
+#include "vtkSmartPointer.h" // For SmartPointer
+#include "vtkImageData.h" // For ImageData
+
+#include <vector> // For stl vector
+#include <array> // For stl array
+#include <string> // For stl string
+#include <set> // for stl multiset
+#include <functional> // for function
+
+/**
+ * @class   vtkWordCloud
+ * @brief   generate a word cloud visualization of a text document
+ *
+ * Word Clouds, AKA Tag Clouds
+ * (https://en.wikipedia.org/wiki/Tag_cloud), are a text visualization
+ * technique that displays individual words with properties that
+ * depend on the frequency of a word in a document. vtkWordCloud
+ * varies the font size based on word frequency. Word Clouds are useful
+ * for quickly perceiving the most prominent terms in a document.
+ * Also, Word Clouds can identify trends and patterns that would
+ * otherwise be unclear or difficult to see in a tabular
+ * format. Frequently used keywords stand out better in a word
+ * cloud. Common words that might be overlooked in tabular form are
+ * highlighted in the larger text, making them pop out when displayed
+ * in a word cloud.
+ *
+ * There is some controversy about the usefulness of word
+ * clouds. Their best use may be for presentations, see
+ * https://tinyurl.com/y59hy7oa
+ *
+ * The generation of the word cloud proceeds as follows:
+ * 1. Read the text file
+ * 2. Split text into words to be processed
+ *    Extract words from the text
+ *    Drop the case of each word for filtering
+ *    Filter the words
+ *      Replace words from the ReplacementPairs list
+ *      Skip the word if it is in the stop list or contains a digit
+ *      Skip single character words
+ *    Raise the case of the first letter in each word
+ *    Sort the word list by frequency
+ * 3. Create a rectangular mask image or read a mask image
+ * 4. For each word
+ *    Render the word into an image
+ *    Try to add the word to the word cloud image.
+ *      For each orientation, see if the word "fits"
+ *        If no fit, move along a path to try another location
+ *
+ * NOTE: A word fits if all of the non-zero word cloud pixels in the
+ * extent of the text image are background pixels.
+ *
+ * NOTE: The path is an Archimedean Spiral
+ * (https://en.wikipedia.org/wiki/Archimedean_spiral)
+
+ * NOTE: vtkWordCloud has a built-in list of stop word. Stop words are
+ * words that are filtered out before processing of the text, such as
+ * the, is, at, which, and so on.
+ *
+ * NOTE: Color names are defined in vtkNamedColors. A visual
+ * representation of color names is here: https://tinyurl.com/y3yxcxj6
+ *
+ * NOTE: vtkWordCloud offers Several methods to customize the resulting
+ * visualization. The class provides defaults that provide a reasonable
+ * result.
+ *
+ * BackgroundColorName - The vtkNamedColors name for the background
+ * (MidNightBlue). See https://tinyurl.com/y3yxcxj6 for a visual
+ * representation of the named colors.
+ *
+ * ColorDistribution - Distribution of random colors(.6 1.0), if
+ * WordColorName is empty.
+ *
+ * ColorSchemeName - Name of a color scheme from vtkColorSeries to be
+ * used to select colors for the words (), if WordColorName is empty.
+ * See https://tinyurl.com/y3j6c27o for a visual representation of the
+ * color schemes.
+ *
+ * DPI - Dots per inch(200) of the rendered text. DPI is used as a
+ * scaling mechanism for the words. As DPI increases, the word size
+ * increases. If there are too, few skipped words, increase this value,
+ * too many, decrease it.
+ *
+ * FontFileName - If empty, the built-in Arial font is used(). The
+ * FontFileName is the name of a file that contains a TrueType font.
+ * https://www.1001freefonts.com/ is a good source for free TrueType
+ * fonts.
+ *
+ * FontMultiplier - Font multiplier(6). The final FontSize is this value
+ * times the word frequency.
+ *
+ * Gap - Space gap of words (2). The gap is the number of spaces added to
+ * the beginning and end of each word.
+ *
+ * MaskColorName - Name of the color for the mask (black). This is the
+ * name of the vtknamedColors that defines the foreground of the
+ * mask. Usually black or white.  See https://tinyurl.com/y3yxcxj6 for
+ * a visual representation of the named colors.
+ *
+ * MaskFileName - Mask file name(). If a mask file is specified, it will be
+ * used as the mask. Otherwise, a black square is used as the mask. The
+ * mask file should contain three channels of unsigned char values. If
+ * the mask file is just a single unsigned char, specify turn the boolean
+ * BWMask on.  If BWmask is on, the class will create a three channel
+ * image using vtkImageAppendComponents.
+ *
+ * BWMask - Mask image has a single channel(false). Mask images typically
+ * have three channels (r,g,b).
+ *
+ * MaxFontSize - Maximum font size(48).
+ *
+ * MinFontSize - Minimum font size(8).
+ *
+ * MinFrequency - Minimum word frequency accepted(2). Word with
+ * frequencies less than this will be ignored.
+ *
+ * OffsetDistribution - Range of uniform random offsets(-size[0]/100.0
+ * -size{1]/100.0)(-20 20). These offsets are offsets from the generated
+ * path for word layout.
+ *
+ * OrientationDistribution - Ranges of random orientations(-20 20). If
+ * discrete orientations are not defined, these orientations will be
+ * generated.
+ *
+ * Orientations - Vector of discrete orientations(). If non-empty,
+ * these will be used instead of the orientations distribution");
+ *
+ * ReplacementPairs - Replace the first word with another second word
+ * ().  The each word will also added to the StopList. The second
+ * argument can contain multiple words. For example you could replace
+ * "bill" with "Bill Lorensen" or, "vtk" with "VTK . Remember that
+ * words are always stored internally with lower case, even though the
+ * first letter is capitalized in the Word Cloud.
+ *
+ * Sizes - Size of image(640 480).
+ *
+ * StopWords - User provided stop words(). Stop words are words that
+ * are filtered out before processing of the text, such as the, is,
+ * at, which, and so on.  vtkWordClass has built-in stop words. The
+ * user-provided stop words are added to the built-in list. See
+ * https://en.wikipedia.org/wiki/Stop_words for a description.  The
+ * built-in stop words were derived from the english stop words at
+ * https://www.ranks.nl/stopwords. Stop words for other languages are
+ * also available.
+ *
+ * StopListFileName - the name of a file that contains stop words,
+ * one word per line (). If present, the stop words in the file
+ * replace the built-in stop list.
+ *
+ * Title - Add this word to the document's words and set a high
+ * frequency, so that is will be rendered first.
+ *
+ * WordColorName - Name of the color for the words(). The name is
+ * selected from vtkNamedColors. If the name is empty, the
+ * ColorDistribution will generate random colors.  See
+ * https://tinyurl.com/y3yxcxj6 for a visual representation of the
+ * named colors.
+ *
+ * The class also provided Get methods that return vectors
+ * StopWords, SkippedWords and KeptWords.
+*/
+
+class VTKINFOVISCORE_EXPORT vtkWordCloud : public vtkImageAlgorithm
+{
+public:
+  vtkTypeMacro(vtkWordCloud,vtkImageAlgorithm);
+  void PrintSelf(ostream& os, vtkIndent indent) override;
+
+  /**
+   * Construct object with vertex cell generation turned off.
+   */
+  static vtkWordCloud *New();
+
+  // Typedefs
+  using ColorDistributionContainer = std::array<double, 2>;
+  using OffsetDistributionContainer = std::array<int, 2>;
+  using OrientationDistributionContainer = std::array<double, 2>;
+  using OrientationsContainer = std::vector<double>;
+  using PairType = std::tuple<std::string,std::string>;
+  using ReplacementPairsContainer = std::vector<PairType>;
+  using SizesContainer = std::array<int, 2>;
+  using StopWordsContainer = std::set<std::string>;
+  using StringContainer = std::vector<std::string>;
+
+  //@{
+  /**
+   * Return the AdjustedSizes of the resized mask file.
+   */
+  //@}
+  virtual SizesContainer GetAdjustedSizes() {return AdjustedSizes;}
+
+#define SetStdContainerMacro(name,container) \
+  virtual void Set##name(container arg)        \
+  { \
+    bool changed = false; \
+    if (arg.size() !=  name.size()) \
+      { \
+        changed = true; \
+      } \
+    else \
+    { \
+      auto a = arg.begin(); \
+      for (auto r : name) \
+      { \
+        if (*a != r) \
+        { \
+          changed = true; \
+        } \
+        a++; \
+      } \
+    } \
+    if (changed) \
+    { \
+      name = arg; \
+      this->Modified(); \
+    } \
+  }
+
+  //@{
+  /**
+   * Set/Get the vtkNamedColors name for the background(MidNightBlue).
+   */
+  //@}
+  virtual void SetBackgroundColorName(std::string arg)
+  {
+    if (arg != BackgroundColorName)
+    {
+      this->Modified();
+      BackgroundColorName = arg;
+    }
+  }
+  virtual std::string GetBackgroundColorName() {return BackgroundColorName;}
+
+  //@{
+  /**
+    * Set/Get boolean that indicates the mask image is a single
+    * channel(false).
+    */
+  //@}
+  virtual void SetBWMask(bool arg)
+  {
+    if (BWMask != arg)
+    {
+      this->Modified();
+      BWMask = arg;
+    }
+  }
+  virtual bool GetBWMask() {return BWMask;}
+
+  //@{
+  /**
+    * Set/Get ColorSchemeName, the name of a color scheme from
+    * vtkColorScheme to be used to select colors for the words (), if
+    * WordColorName is empty. See https://tinyurl.com/y3j6c27o for a
+    * visual representation of the color schemes.
+    */
+  //@}
+  virtual void SetColorSchemeName(std::string arg)
+  {
+    if (ColorSchemeName != arg)
+    {
+      this->Modified();
+      ColorSchemeName = arg;
+    }
+  }
+  virtual std::string GetColorSchemeName() {return ColorSchemeName;}
+
+  //@{
+  /**
+    * Set/GetDPI - Dots per inch(200) of the rendered text. DPI is
+    * used as a scaling mechanism for the words. As DPI increases,
+    * the word size increases. If there are too, few skipped words,
+    * increase this value, too many, decrease it.
+    */
+  //@}
+  vtkSetMacro(DPI, int);
+  vtkGetMacro(DPI, int);
+
+//@{
+  /**
+    * Set/Get FileName, the name of the file that contains the text to
+    * be processed.
+    */
+  //@}
+  virtual   void SetFileName(std::string arg)
+  {
+    if (FileName != arg)
+    {
+      this->Modified();
+      FileName = arg;
+    }
+  }
+  virtual   std::string GetFileName () {return FileName;}
+
+  //@{
+  /**
+    * Set/Get FontFileName, If empty, the built-in Arial font is
+    * used(). The FontFileName is the name of a file that contains a
+    * TrueType font.
+    */
+  //@}
+  virtual void SetFontFileName(std::string arg)
+  {
+    if (FontFileName != arg)
+    {
+      this->Modified();
+      FontFileName = arg;
+    }
+  }
+  virtual std::string GetFontFileName() {return FontFileName;}
+
+  //@{
+  /**
+    * Set/Get Gap, the space gap of words (2). The gap is the number
+    * of spaces added to the beginning and end of each word.
+    */
+  //@}
+  vtkSetMacro(Gap, int);
+  vtkGetMacro(Gap, int);
+
+  //@{
+  /**
+    * Set/Get MaskColorName, the name of the color for the mask
+    * (black). This is the name of the vtkNamedColors that defines
+    * the foreground of the mask. Usually black or white.
+    */
+  //@}
+  virtual void SetMaskColorName(std::string arg)
+  {
+    if (MaskColorName != arg)
+    {
+      this->Modified();
+      MaskColorName = arg;
+    }
+  }
+  virtual std::string GetMaskColorName() {return MaskColorName;}
+
+  //@{
+  /**
+    * Set/Get MaskFileName, the mask file name(). If a mask file is
+    * specified, it will be used as the mask. Otherwise, a black
+    * square is used as the mask. The mask file should contain three
+    * channels of unsigned char values. If the mask file is just a
+    * single unsigned char, specify turn the boolean BWMask on.  If
+    * BWmask is on, the class will create a three channel image using
+    * vtkImageAppendComponents.
+    */
+  //@}
+  virtual void SetMaskFileName(std::string arg)
+  {
+    if (MaskFileName != arg)
+    {
+      this->Modified();
+      MaskFileName = arg;
+    }
+  }
+  virtual std::string GetMaskFileName() {return MaskFileName;}
+
+  //@{
+  /**
+    * Set/Get MaxFontSize, the maximum font size(48).
+    */
+  //@}
+  vtkSetMacro(MaxFontSize, int);
+  vtkGetMacro(MaxFontSize, int);
+
+  //@{
+  /**
+    * Set/Get MinFontSize, the minimum font size(8).
+    */
+  //@}
+  vtkSetMacro(MinFontSize, int);
+  vtkGetMacro(MinFontSize, int);
+
+  //@{
+  /**
+    * Set/Get MinFrequency, the minimum word frequency
+    * accepted(2). Words with frequencies less than this will be
+    * ignored.
+    */
+  //@}
+  vtkSetMacro(MinFrequency, int);
+  vtkGetMacro(MinFrequency, int);
+
+  //@{
+  /**
+    * Set/Get FontMultiplier, the font multiplier(6). The final
+    * FontSize is this value the word frequency.
+    */
+  //@}
+  vtkSetMacro(FontMultiplier, int);
+  vtkGetMacro(FontMultiplier, int);
+
+  //@{
+  /**
+    * Set/Get ColorDistribution, the distribution of random colors(.6
+    * 1.0), if WordColorName is empty.
+    */
+  //@}
+  SetStdContainerMacro(ColorDistribution,ColorDistributionContainer);
+  virtual ColorDistributionContainer GetColorDistribution() {return ColorDistribution;}
+
+  //@{
+  /**
+    * Set/Get OffsetDistribution, the range of uniform random
+    * offsets(-size[0]/100.0 -size{1]/100.0)(-20 20). These offsets
+    * are offsets from the generated path for word layout.
+    */
+  //@}
+  SetStdContainerMacro(OffsetDistribution,OffsetDistributionContainer);
+  virtual OffsetDistributionContainer GetOffsetDistribution() {return OffsetDistribution;}
+
+  //@{
+  /**
+    * Set/Get OrientationDistribution, ranges of random
+    * orientations(-20 20). If discrete orientations are not defined,
+    * these orientations will be generated.
+    */
+  //@}
+  SetStdContainerMacro(OrientationDistribution,OrientationDistributionContainer);
+  virtual OrientationDistributionContainer GetOrientationDistribution() {return OrientationDistribution;}
+
+  //@{
+  /**
+    * Set/Add/Get Orientations, a vector of discrete orientations (). If
+    * non-empty, these will be used instead of the orientations
+    * distribution").
+    */
+  //@}
+  SetStdContainerMacro(Orientations,OrientationsContainer);
+  void AddOrientation(double arg)
+  {
+    Orientations.push_back(arg);
+    this->Modified();
+  }
+  virtual OrientationsContainer GetOrientations() {return Orientations;}
+
+  //@{
+  /**
+    * Set/Add/Get ReplacementPairs, a vector of words that replace the
+    * first word with another second word (). The first word is also
+    * added to the StopList.
+    */
+  //@}
+  SetStdContainerMacro(ReplacementPairs,ReplacementPairsContainer);
+  void AddReplacementPair(PairType arg)
+  {
+    ReplacementPairs.push_back(arg);
+    this->Modified();
+  }
+
+  virtual   ReplacementPairsContainer GetReplacementPairs() {return ReplacementPairs;}
+
+  //@{
+  /**
+    * Set/Get Sizes, the size of the output image(640 480).
+    */
+  //@}
+  SetStdContainerMacro(Sizes,SizesContainer);
+  virtual   SizesContainer GetSizes () {return Sizes;}
+
+  //@{
+  /**
+    * Set/Add/Get StopWords, a set of user provided stop
+    * words(). vtkWordClass has built-in stop words. The user-provided
+    * stop words are added to the built-in list.
+    */
+  //@}
+  SetStdContainerMacro(StopWords,StopWordsContainer);
+  void AddStopWord(std::string word)
+  {
+    StopWords.insert(word);
+    this->Modified();
+  }
+  void ClearStopWords()
+  {
+    StopWords.clear();
+    this->Modified();
+  }
+  virtual   StopWordsContainer GetStopWords () {return StopWords;}
+
+  //@{
+  /**
+    * Set/Get StopListFileName, the name of the file that contains the
+    * stop words, one per line.
+    */
+  //@}
+  virtual   void SetStopListFileName(std::string arg)
+  {
+    if (StopListFileName != arg)
+    {
+      this->Modified();
+      StopListFileName = arg;
+    }
+  }
+  virtual   std::string GetStopListFileName () {return StopListFileName;}
+
+  //@{
+  /**
+    * Set/Get Title, add this word to the document's words and set a
+    * high frequency, so that is will be rendered first.
+    */
+  //@}
+  virtual void SetTitle(std::string arg)
+  {
+    if (Title != arg)
+    {
+      this->Modified();\
+      Title = arg;
+    }
+  }
+  virtual   std::string GetTitle () {return Title;}
+
+  //@{
+  /**
+    * Set/Get WordColorName, the name of the color for the
+    * words(). The name is selected from vtkNamedColors. If the name
+    * is empty, the ColorDistribution will generate random colors.
+    */
+  //@}
+  virtual void SetWordColorName(std::string arg)
+  {
+    if (WordColorName != arg)
+    {
+      this->Modified();\
+      WordColorName = arg;
+    }
+  }
+  virtual   std::string GetWordColorName () {return WordColorName;}
+  //@{
+  /**
+    * Get a vector of words that are kept in the final image.
+    */
+  //@}
+  virtual   std::vector<std::string>& GetKeptWords () {return KeptWords;}
+
+  //@{
+  /**
+    * Get a vector of words that are skipped. Skipped wors do not fit
+    * in the final image.
+    */
+  //@}
+  virtual   std::vector<std::string>& GetSkippedWords () {return SkippedWords;}
+
+  //@{
+  /**
+    * Get a vector of words that were stopped in the final image.
+    */
+  //@}
+  virtual   std::vector<std::string>& GetStoppedWords () {return StoppedWords;}
+
+protected:
+  vtkWordCloud();
+  ~vtkWordCloud() override {}
+
+  int RequestInformation (vtkInformation *,
+                          vtkInformationVector**,
+                          vtkInformationVector *) override;
+
+  int RequestData (vtkInformation *,
+                   vtkInformationVector**,
+                   vtkInformationVector *) override;
+
+  vtkSmartPointer<vtkImageData>    ImageData;
+  int                              WholeExtent[6];
+
+  SizesContainer                   AdjustedSizes;
+  std::string                      BackgroundColorName;
+  bool                             BWMask;
+  ColorDistributionContainer       ColorDistribution;
+  std::string                      ColorSchemeName;
+  int                              DPI;
+  std::string                      FileName;
+  std::string                      FontFileName;
+  int                              FontMultiplier;
+  int                              Gap;
+  std::string                      MaskColorName;
+  std::string                      MaskFileName;
+  int                              MaxFontSize;
+  int                              MinFontSize;
+  int                              MinFrequency;
+  OffsetDistributionContainer      OffsetDistribution;
+  OrientationDistributionContainer OrientationDistribution;
+  OrientationsContainer            Orientations;
+  ReplacementPairsContainer        ReplacementPairs;
+  SizesContainer                   Sizes;
+  StopWordsContainer               StopWords;
+  std::string                      StopListFileName;
+  std::string                      Title;
+  std::string                      WordColorName;
+
+  std::vector<std::string>         KeptWords;
+  std::vector<std::string>         SkippedWords;
+  std::vector<std::string>         StoppedWords;
+
+private:
+  vtkWordCloud(const vtkWordCloud&) = delete;
+  void operator=(const vtkWordCloud&) = delete;
+
+// Declaring the type of Predicate that accepts 2 pairs and returns a bool
+typedef std::function<bool(
+    std::pair<std::string, int>,
+    std::pair<std::string, int>)> Comparator;
+
+std::multiset<std::pair<std::string, int>, Comparator > FindWordsSortedByFrequency(std::string &, vtkWordCloud *);
+struct ExtentOffset
+{
+   ExtentOffset(int _x = 0.0, int _y = 0.0) : x(_x), y(_y) {}
+   int x,y;
+};
+
+};
+#endif
+
+//  LocalWords:  vtkNamedColors SetMaskColorName
diff --git a/Testing/Data/Canterbury.ttf.sha512 b/Testing/Data/Canterbury.ttf.sha512
new file mode 100644
index 00000000000..0b484ef6b46
--- /dev/null
+++ b/Testing/Data/Canterbury.ttf.sha512
@@ -0,0 +1 @@
+ae780e61477b98aedf9cb47ea86637f45a7d9ecb2c6cf9ce4fb7360eabfcd9659b0fd79a5cf42e0ea44ae2140f83d3a305da1b088853fd8a20dfcf497c9b6dcf
diff --git a/Testing/Data/Gettysburg.txt.sha512 b/Testing/Data/Gettysburg.txt.sha512
new file mode 100644
index 00000000000..5383ce186d5
--- /dev/null
+++ b/Testing/Data/Gettysburg.txt.sha512
@@ -0,0 +1 @@
+a8edbf80252f7ec17d324dd766db68d4aa5afa12203b2a81d73fcaf59ac4c23dc9586a0d82370ab0ef92eafcf8453a67047d720828145526b1ef28c7358f8150
diff --git a/Testing/Data/NLTKStopList.txt.sha512 b/Testing/Data/NLTKStopList.txt.sha512
new file mode 100644
index 00000000000..42278760f73
--- /dev/null
+++ b/Testing/Data/NLTKStopList.txt.sha512
@@ -0,0 +1 @@
+9552144f2af135d070315dedf5e530daf3ba93e480c2d64c77e3099395f491011d5fbd7679c4daaed2709a83832c09a31c308d346eb4dffbeda969fcbe99e9f4
diff --git a/Testing/Data/hearts.png.sha512 b/Testing/Data/hearts.png.sha512
new file mode 100644
index 00000000000..1a9dfdd8f40
--- /dev/null
+++ b/Testing/Data/hearts.png.sha512
@@ -0,0 +1 @@
+0ce83e87c630a4eada6cdb116549353621baaaf35e386046b4992708ecae2ee0a9fa7f826dcc87dac617506befeaa588e012beecab0999ac5b7c9baa698d447e
diff --git a/Testing/Data/hearts8bit.png.sha512 b/Testing/Data/hearts8bit.png.sha512
new file mode 100644
index 00000000000..e4525b54aa3
--- /dev/null
+++ b/Testing/Data/hearts8bit.png.sha512
@@ -0,0 +1 @@
+aa229211252470922b25995bd73b6d7ce540eff76738cab92b727a6712ff0494b27b799eefad978406a200d6e7a7f1386143f17c6a4de8054e474a985206afa0
-- 
GitLab