[Libreoffice-commits] libvisio.git: Branch 'zf.com' - build/win32 src/conv src/lib

Ji???­ Posp?­??il jiri.pospisil.certicon at zf.com
Thu Dec 15 16:44:30 UTC 2016


 build/win32/libvisio.vcxproj                      |   26 +
 src/conv/svg/vsd2xhtml.cpp                        |  276 +++++++++--
 src/lib/VSDContentCollector.cpp                   |  494 ++++++++++++--------
 src/lib/VSDContentCollector.h                     |    7 
 src/lib/VSDMetaData.cpp                           |    2 
 src/lib/VSDOutputElementList.cpp                  |  282 -----------
 src/lib/VSDOutputElementList.h                    |  317 +++++++++++++
 src/lib/preprocess/SvgDC.cpp                      |    1 
 src/lib/preprocess/SvgDC.h                        |   32 +
 src/lib/preprocess/SvgFont.cpp                    |   61 ++
 src/lib/preprocess/SvgFont.h                      |   38 +
 src/lib/preprocess/SvgParagraph.cpp               |  152 ++++++
 src/lib/preprocess/SvgParagraph.h                 |   69 ++
 src/lib/preprocess/SvgSpan.cpp                    |   74 +++
 src/lib/preprocess/SvgSpan.h                      |   51 ++
 src/lib/preprocess/SvgTextObject.cpp              |  473 +++++++++++++++++++
 src/lib/preprocess/SvgTextObject.h                |  109 ++++
 src/lib/preprocess/SvgUtils.cpp                   |   61 ++
 src/lib/preprocess/SvgUtils.h                     |   28 +
 src/lib/preprocess/VsdElementListPreprocessor.cpp |  521 ++++++++++++++++++++++
 src/lib/preprocess/VsdElementListPreprocessor.h   |   71 ++
 src/lib/preprocess/windows/WindowsSvgDC.cpp       |  188 +++++++
 src/lib/preprocess/windows/WindowsSvgDC.h         |   61 ++
 src/lib/preprocess/windows/WindowsSvgFont.cpp     |   54 ++
 src/lib/preprocess/windows/WindowsSvgFont.h       |   30 +
 25 files changed, 2964 insertions(+), 514 deletions(-)

New commits:
commit 80720108347665f6051795f3fbb7d8d0f262513a
Author: Ji???­ Posp?­??il <jiri.pospisil.certicon at zf.com>
Date:   Wed Dec 14 14:46:02 2016 +0100

    SVG generation fixes
    
    - text wrapping
    - text decoration
    - line connectors
    - path filling
    - unordered item lists

diff --git a/build/win32/libvisio.vcxproj b/build/win32/libvisio.vcxproj
index c8cad9f..4a96a9f 100644
--- a/build/win32/libvisio.vcxproj
+++ b/build/win32/libvisio.vcxproj
@@ -111,6 +111,16 @@
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
+    <ClCompile Include="..\..\src\lib\libvisio_xml.cpp" />
+    <ClCompile Include="..\..\src\lib\preprocess\SvgDC.cpp" />
+    <ClCompile Include="..\..\src\lib\preprocess\SvgFont.cpp" />
+    <ClCompile Include="..\..\src\lib\preprocess\SvgParagraph.cpp" />
+    <ClCompile Include="..\..\src\lib\preprocess\SvgSpan.cpp" />
+    <ClCompile Include="..\..\src\lib\preprocess\SvgTextObject.cpp" />
+    <ClCompile Include="..\..\src\lib\preprocess\SvgUtils.cpp" />
+    <ClCompile Include="..\..\src\lib\preprocess\VsdElementListPreprocessor.cpp" />
+    <ClCompile Include="..\..\src\lib\preprocess\windows\WindowsSvgDC.cpp" />
+    <ClCompile Include="..\..\src\lib\preprocess\windows\WindowsSvgFont.cpp" />
     <ClCompile Include="..\..\src\lib\VDXParser.cpp">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
@@ -165,6 +175,8 @@
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
+    <ClCompile Include="..\..\src\lib\VSDLayerList.cpp" />
+    <ClCompile Include="..\..\src\lib\VSDMetaData.cpp" />
     <ClCompile Include="..\..\src\lib\VSDOutputElementList.cpp">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
@@ -249,6 +261,16 @@
     <ClInclude Include="..\..\inc\libvisio\libvisio.h" />
     <ClInclude Include="..\..\inc\libvisio\VisioDocument.h" />
     <ClInclude Include="..\..\src\lib\libvisio_utils.h" />
+    <ClInclude Include="..\..\src\lib\libvisio_xml.h" />
+    <ClInclude Include="..\..\src\lib\preprocess\SvgDC.h" />
+    <ClInclude Include="..\..\src\lib\preprocess\SvgFont.h" />
+    <ClInclude Include="..\..\src\lib\preprocess\SvgParagraph.h" />
+    <ClInclude Include="..\..\src\lib\preprocess\SvgSpan.h" />
+    <ClInclude Include="..\..\src\lib\preprocess\SvgTextObject.h" />
+    <ClInclude Include="..\..\src\lib\preprocess\SvgUtils.h" />
+    <ClInclude Include="..\..\src\lib\preprocess\VsdElementListPreprocessor.h" />
+    <ClInclude Include="..\..\src\lib\preprocess\windows\WindowsSvgDC.h" />
+    <ClInclude Include="..\..\src\lib\preprocess\windows\WindowsSvgFont.h" />
     <ClInclude Include="..\..\src\lib\tokenhash.h" />
     <ClInclude Include="..\..\src\lib\tokens.h" />
     <ClInclude Include="..\..\src\lib\VDXParser.h" />
@@ -261,6 +283,8 @@
     <ClInclude Include="..\..\src\lib\VSDFieldList.h" />
     <ClInclude Include="..\..\src\lib\VSDGeometryList.h" />
     <ClInclude Include="..\..\src\lib\VSDInternalStream.h" />
+    <ClInclude Include="..\..\src\lib\VSDLayerList.h" />
+    <ClInclude Include="..\..\src\lib\VSDMetaData.h" />
     <ClInclude Include="..\..\src\lib\VSDOutputElementList.h" />
     <ClInclude Include="..\..\src\lib\VSDPages.h" />
     <ClInclude Include="..\..\src\lib\VSDParagraphList.h" />
@@ -290,4 +314,4 @@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>
+</Project>
\ No newline at end of file
diff --git a/src/conv/svg/vsd2xhtml.cpp b/src/conv/svg/vsd2xhtml.cpp
index cc91c85..dc1b7cc 100644
--- a/src/conv/svg/vsd2xhtml.cpp
+++ b/src/conv/svg/vsd2xhtml.cpp
@@ -1,4 +1,3 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /*
  * This file is part of the libvisio project.
  *
@@ -11,108 +10,275 @@
 #include "config.h"
 #endif
 
+#include <fstream>
 #include <iostream>
 #include <sstream>
-#include <stdio.h>
-#include <string.h>
+#include <string>
+#include <vector>
+
+#include <boost/algorithm/string.hpp>
 #include <librevenge-stream/librevenge-stream.h>
 #include <librevenge-generators/librevenge-generators.h>
 #include <librevenge/librevenge.h>
 #include <libvisio/libvisio.h>
 
+using namespace librevenge;
+using namespace libvisio;
+using namespace std;
+
+
 #ifndef VERSION
 #define VERSION "UNKNOWN VERSION"
 #endif
 
+
 namespace
 {
 
+string UnquoteString(const string &str)
+{
+  unsigned int strSize = str.size();
+  unsigned int pos1 = strSize > 0 && str[0] == '"' ? 1 : 0;
+  unsigned int pos2 = strSize > 1 && str[strSize - 1] == '"' ? strSize - 1 : strSize;
+  return str.substr(pos1, pos2 - pos1);
+}
+
+bool ExpandFileNameList(const string &listFileName, vector<string> &fileNames)
+{
+  if (listFileName.find(".lst") == listFileName.size() - 4)
+  {
+    ifstream in(listFileName);
+
+    if (in.is_open())
+    {
+      while (in && !in.eof())
+      {
+        string line;
+        getline(in, line);
+
+        if (in)
+        {
+          fileNames.push_back(UnquoteString(line));
+        }
+      }
+
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void WriteXhtmlHeader(ostream &out)
+{
+  out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << endl;
+  out << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" << endl;
+  out << "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">" << endl;
+  out << "<body>" << endl;
+  out << "<?import namespace=\"svg\" urn=\"http://www.w3.org/2000/svg\"?>" << endl;
+}
+
+void WriteXhtmlFooter(ostream &out)
+{
+  out << "</body>" << endl;
+  out << "</html>" << endl;
+}
+
+void WriteSvg(const char *svgStr, ostream &out)
+{
+  out << "<!-- " << endl;
+  out << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" << endl;
+  out << "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"";
+  out << " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">" << endl;
+  out << " -->"  << endl;
+  out << svgStr << endl;
+}
+
 int printUsage()
 {
-  printf("`vsd2xhtml' converts Microsoft Visio documents to SVG.\n");
-  printf("\n");
-  printf("Usage: vsd2xhtml [OPTION] INPUT\n");
-  printf("\n");
-  printf("Options:\n");
-  printf("\t--help                show this help message\n");
-  printf("\t--version             show version information\n");
-  printf("\n");
-  printf("Report bugs to <https://bugs.documentfoundation.org/>.\n");
+  cout << "'vsd2xhtml' converts Microsoft Visio documents to SVG." << endl;
+  cout << endl;
+  cout << "Usage: vsd2xhtml [OPTION] INPUT {INPUT}" << endl;
+  cout << endl;
+  cout << "OPTIONS:" << endl;
+  cout << "    --help       show this help message" << endl;
+  cout << "    --version    show version information" << endl;
+  cout << "    --onedoc     merge all visio pages into one output document" << endl;
+  cout << endl;
+  cout << "INPUT:           VSD file path or a path to a text file containing list" << endl;
+  cout << "                 of VSD file paths, each on a separate line" << endl;
+  cout << endl;
+  cout << "Report bugs to <https://bugs.documentfoundation.org/>." << endl;
   return -1;
 }
 
 int printVersion()
 {
-  printf("vsd2xhtml " VERSION "\n");
+  cout << "vsd2xhtml " << VERSION << endl;
   return 0;
 }
 
 } // anonymous namespace
 
+/**
+ * Carries out conversion of specified VSD files in SVG.
+ *
+ * @param argc
+ *        number of supplied arguments
+ *
+ * @param argv
+ *        an array of arguments:
+ *
+ *        --version     will return the version of this tool
+ *
+ *        --onedoc      all pages of a particular VSD document will be included in a single SVG file,
+ *                      otherwise a SVG file is generated for each page, while appending the page
+ *                      number to its name
+ *
+ *         [fileName]+  a name of the VSD file to convert or a name of a file with LST extension that
+ *                      contains a list of paths to VSD files to process, each on a separate line.
+ *                      Generated output documents will have the same name as the respective input but
+ *                      SVG extension, plus the name will be appended by the page number if --onedoc
+ *                      option was not specified
+ *
+ * @return 0 if conversion (all conversions) was successful
+ *
+ *         1 if some (all conversion) failed for any reason, such as because of not recognized
+ *           format of the input file or parsing failure due to invalid contents
+ *
+ *        -1 if invalid command line arguments were supplied
+ */
 int main(int argc, char *argv[])
 {
   if (argc < 2)
+  {
     return printUsage();
+  }
 
-  char *file = 0;
+  vector<string> fileNames;
+  bool oneDoc = false;
 
   for (int i = 1; i < argc; i++)
   {
-    if (!strcmp(argv[i], "--version"))
+    if (strcmp(argv[i], "--version") == 0)
+    {
       return printVersion();
-    else if (!file && strncmp(argv[i], "--", 2))
-      file = argv[i];
+    }
+    else if (strcmp(argv[i], "--onedoc") == 0)
+    {
+      oneDoc = true;
+    }
+    else if (strncmp(argv[i], "--", 2) != 0)
+    {
+      string fileName = UnquoteString(argv[i]);
+
+      if (!ExpandFileNameList(fileName, fileNames))
+      {
+        fileNames.push_back(fileName);
+      }
+    }
     else
+    {
       return printUsage();
+    }
   }
 
-  if (!file)
+  if (fileNames.size() == 0)
+  {
     return printUsage();
+  }
 
-  librevenge::RVNGFileStream input(file);
+  bool wasError = false;
 
-  if (!libvisio::VisioDocument::isSupported(&input))
+  for (unsigned int i = 0; i < fileNames.size(); i++)
   {
-    std::cerr << "ERROR: Unsupported file format (unsupported version) or file is encrypted!" << std::endl;
-    return 1;
-  }
+    cout << "#" << (i + 1) << "/" << fileNames.size() << endl;
+    cout << "Parsing \"" << fileNames[i] << "\"...";
 
-  librevenge::RVNGStringVector output;
-  librevenge::RVNGSVGDrawingGenerator generator(output, "svg");
-  if (!libvisio::VisioDocument::parse(&input, &generator))
-  {
-    std::cerr << "ERROR: SVG Generation failed!" << std::endl;
-    return 1;
-  }
-  if (output.empty())
-  {
-    std::cerr << "ERROR: No SVG document generated!" << std::endl;
-    return 1;
-  }
+    {
+      ifstream in(fileNames[i].c_str());
 
-  std::cout << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
-  std::cout << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" << std::endl;
-  std::cout << "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">" << std::endl;
-  std::cout << "<body>" << std::endl;
-  std::cout << "<?import namespace=\"svg\" urn=\"http://www.w3.org/2000/svg\"?>" << std::endl;
+      if (!in.is_open())
+      {
+        cerr << "ERROR: file not found" << endl;
+        wasError = true;
+        continue;
+      }
+    }
 
-  for (unsigned k = 0; k<output.size(); ++k)
-  {
-    if (k>0)
-      std::cout << "<hr/>\n";
+    RVNGFileStream input(fileNames[i].c_str());
 
-    std::cout << "<!-- \n";
-    std::cout << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n";
-    std::cout << "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"";
-    std::cout << " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n";
-    std::cout << " -->\n";
+    if (!VisioDocument::isSupported(&input))
+    {
+      cerr << "ERROR: Unsupported file format (unsupported version) or file is encrypted!" << endl;
+      wasError = true;
+      continue;
+    }
 
-    std::cout << output[k].cstr() << std::endl;
-  }
+    RVNGStringVector output;
+    RVNGSVGDrawingGenerator generator(output, ""); // do not use SVG namespace
 
-  std::cout << "</body>" << std::endl;
-  std::cout << "</html>" << std::endl;
+    if (!VisioDocument::parse(&input, &generator))
+    {
+      cerr << "ERROR: SVG Generation failed!" << endl;
+      wasError = true;
+      continue;
+    }
 
-  return 0;
+    if (output.empty())
+    {
+      cerr << "ERROR: No SVG document generated!" << endl;
+      wasError = true;
+      continue;
+    }
+
+    cout << "done" << endl;
+
+    string fileName = fileNames[i];
+    boost::algorithm::to_lower(fileName);
+
+    fileName = fileName.find(".vsd") == fileName.size() - 4
+               ? fileNames[i].substr(0, fileName.size() - 4) : fileNames[i];
+
+    if (oneDoc) // render all pages to one file
+    {
+      fileName += ".svg";
+      ofstream out(fileName.c_str());
+
+      cout << "Writing \"" << fileName.c_str() << "\"...";
+      WriteXhtmlHeader(out);
+
+      for (unsigned k = 0; k < output.size(); k++)
+      {
+        if (k > 0)
+        {
+          out << "<hr/>" << endl;
+        }
+
+        WriteSvg(output[k].cstr(), out);
+      }
+
+      WriteXhtmlFooter(out);
+      cout << "done" << endl;
+    }
+    else // render each page to a separate file
+    {
+      for (unsigned k = 0; k < output.size(); k++)
+      {
+        ostringstream oss;
+        oss << fileName << "-" << (k + 1) << ".svg";
+        ofstream out(oss.str().c_str());
+
+        cout << "Writing \"" << oss.str().c_str() << "\"...";
+        WriteXhtmlHeader(out);
+        WriteSvg(output[k].cstr(), out);
+        WriteXhtmlFooter(out);
+        cout << "done" << endl;
+      }
+    }
+
+    cout << endl;
+  }
+
+  return wasError ? 1 : 0;
 }
-/* vim:set shiftwidth=2 softtabstop=2 expandtab: */
diff --git a/src/lib/VSDContentCollector.cpp b/src/lib/VSDContentCollector.cpp
index eb9a366..c2b9784 100644
--- a/src/lib/VSDContentCollector.cpp
+++ b/src/lib/VSDContentCollector.cpp
@@ -7,10 +7,12 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
-#include <cassert>
 #include <string.h> // for memcpy
 #include <set>
 #include <stack>
+#include <sstream>
+#include <iomanip>
+
 #include <boost/spirit/include/classic.hpp>
 #include <unicode/ucnv.h>
 #include <unicode/utf8.h>
@@ -18,6 +20,12 @@
 #include "VSDContentCollector.h"
 #include "VSDParser.h"
 #include "VSDInternalStream.h"
+#include "librevenge/SvgConstants.h"
+
+using namespace librevenge;
+using namespace std;
+using namespace svgconstants;
+
 
 #ifndef DUMP_BITMAP
 #define DUMP_BITMAP 0
@@ -25,13 +33,14 @@
 
 #if DUMP_BITMAP
 static unsigned bitmapId = 0;
-#include <sstream>
 #endif
 
 #ifndef M_PI
 #define M_PI 3.14159265358979323846
 #endif
 
+#define SURROGATE_VALUE(h,l) (((h) - 0xd800) * 0x400 + (l) - 0xdc00 + 0x10000)
+
 namespace
 {
 
@@ -139,39 +148,90 @@ libvisio::VSDContentCollector::VSDContentCollector(
 {
 }
 
-const char *libvisio::VSDContentCollector::_linePropertiesMarkerViewbox(unsigned marker)
+const char *libvisio::VSDContentCollector::_linePropertiesMarkerPathTransform(unsigned marker, bool reverse)
 {
   switch (marker)
   {
-  case 1:
-  case 2:
-  case 9:
-  case 15:
-    return "0 0 20 10";
-  case 8:
-    return "0 0 20 18";
-  case 3:
-  case 4:
-  case 5:
-  case 6:
-  case 11:
-  case 16:
-  case 17:
-  case 18:
-    return "0 0 20 20";
-  case 12:
-  case 13:
-  case 14:
-    return "0 0 20 30";
-  case 22:
-  case 39:
-    return "0 0 20 40";
-  case 21:
-    return "0 0 30 30";
-  case 10:
-    return "0 0 1131 1131";
-  default:
-    return "0 0 20 30";
+  case 1 :
+    return reverse
+           ? "rotate(270, 0, 0) translate(-10, 4)" : "rotate(90, 0, 0) translate(-10, 4)";
+
+  case 3 :
+    return reverse
+           ? "rotate(270, 0, 0) translate(-10, 8)" : "rotate(90, 0, 0) translate(-10, 8)";
+
+  case 9 :
+    return "translate(0, 10)";
+
+  case 10 :
+  case 20 :
+  case 42 :
+    return "";
+
+  case 11 :
+    return "translate(-5, -5)";
+
+  case 12 :
+    return reverse
+           ? "rotate(270, 0, 0) translate(-10, 12)" : "rotate(90, 0, 0) translate(-10, 12)";
+
+  case 21 :
+    return "translate(-15, -15)";
+
+  default :
+    return reverse
+           ? "rotate(270, 0, 0) translate(-10, 0)" : "rotate(90, 0, 0) translate(-10, 0)";
+  }
+}
+
+const char *libvisio::VSDContentCollector::_linePropertiesMarkerViewbox(unsigned marker, bool reverse)
+{
+  switch (marker)
+  {
+  case 1 :
+    return reverse ? "0 -14 28 28" : "-18 -14 28 28";
+
+  case 2 :
+  case 15 :
+    return reverse ? "0 -10 10 20" : "-10 -10 10 20";
+
+  case 3 :
+    return reverse ? "0 -14 31 28" : "-31 -14 31 28";
+
+  case 4 :
+  case 5 :
+  case 16 :
+  case 17 :
+  case 22 :
+  case 39 :
+    return reverse ? "0 -10 20 20" : "-20 -10 20 20";
+
+  case 6 :
+  case 18 :
+    return reverse ? "0 -10 23 20" : "-23 -10 23 20";
+
+  case 8 :
+    return reverse ? "0 -10 18 20" : "-18 -10 18 20";
+
+  case 9 :
+    return "-2 -2 24 24";
+
+  case 11 :
+    return "-5 -5 10 10";
+
+  case 12 :
+    return reverse ? "0 -14 45 28" : "-45 -14 45 28";
+
+  case 10 :
+  case 20 :
+  case 42 :
+    return "-10 -10 20 20";
+
+  case 21 :
+    return "-15 -15 30 30";
+
+  default :
+    return reverse ? "0 -10 30 20" : "-30 -10 30 20";
   }
 }
 
@@ -195,8 +255,10 @@ const char *libvisio::VSDContentCollector::_linePropertiesMarkerPath(unsigned ma
     return "m10 0q-2.6,13.4 -10,18q10,-5 20,0q-7.4,-4.6 -10,-18";
   case 9:
     return "m-2 -8l4 -4l20 20l-4 4z";
-  case 10: // Copied from what LO exports when using the "circle" marker
-    return "m462 1118-102-29-102-51-93-72-72-93-51-102-29-102-13-105 13-102 29-106 51-102 72-89 93-72 102-50 102-34 106-9 101 9 106 34 98 50 93 72 72 89 51 102 29 106 13 102-13 105-29 102-51 102-72 93-93 72-98 51-106 29-101 13z";
+  case 10:
+  case 20:
+  case 42:
+    return "M-10,0A10,10 0 0,0 10,0A10,10 0 0,0 -10,0";
   case 11:
     return "m0 0v10h10v-10z";
   case 12:
@@ -219,6 +281,7 @@ const char *libvisio::VSDContentCollector::_linePropertiesMarkerPath(unsigned ma
     return "m10 0-10 20l10 20l10 -20z m0 8l-6 12l6 12l6 -12z";
   case 39:
     return "m10 0-10 20h20z m0 20-10 20h20z";
+
   default:
     return "m10 0-10 30h20z";
   }
@@ -228,18 +291,24 @@ double libvisio::VSDContentCollector::_linePropertiesMarkerScale(unsigned marker
 {
   switch (marker)
   {
-  case 11:
-  case 10:
-    return 0.7;
-  case 14:
-  case 15:
-  case 16:
-  case 17:
-  case 18:
-  case 22:
+  case 11 :
+  case 14 :
+  case 15 :
+  case 16 :
+  case 17 :
+  case 18 :
+  case 22 :
     return 1.2;
-  default:
-    return 1.0;
+
+  case 10 :
+  case 20 :
+    return 0.6;
+
+  case 42 :
+    return 0.4;
+
+  default :
+    return 0.75;
   }
 }
 
@@ -300,174 +369,209 @@ void libvisio::VSDContentCollector::_flushShape()
   m_isShapeStarted = false;
 }
 
-void libvisio::VSDContentCollector::_flushCurrentPath(unsigned shapeId)
+struct PathActionS
 {
-  librevenge::RVNGPropertyList styleProps;
-  _lineProperties(m_lineStyle, styleProps);
-  _fillAndShadowProperties(m_fillStyle, styleProps);
-  librevenge::RVNGPropertyList fillPathProps(styleProps);
-  fillPathProps.insert("draw:stroke", "none");
-  librevenge::RVNGPropertyList linePathProps(styleProps);
-  linePathProps.insert("draw:fill", "none");
+  string name;
+  double x;
+  double y;
 
-  std::vector<librevenge::RVNGPropertyList> tmpPath;
-  if (m_fillStyle.pattern && !m_currentFillGeometry.empty())
+  PathActionS(const string &name, double x, double y)
+    : name(name),
+      x(x),
+      y(y)
+  {}
+};
+
+typedef deque<PathActionS> PathActionsTp;
+
+void FlushPath(const PathActionsTp &actionsIn, vector<RVNGPropertyList> &actionsOut)
+{
+  for (unsigned int i = 0, cnt = actionsIn.size(); i < cnt; i++)
+  {
+    RVNGPropertyList action;
+    action.insert(PROP_LIBREV_ACTION, actionsIn[i].name.c_str());
+    action.insert(PROP_SVG_X, actionsIn[i].x, RVNG_INCH);
+    action.insert(PROP_SVG_Y, actionsIn[i].y, RVNG_INCH);
+    actionsOut.push_back(action);
+  }
+}
+
+/**
+ * Fixes a specified path by ordering linear segments so that they connect consecutive vertices.
+ * Addresses a know issue that sometimes segments are not defined in a consecutive order, which
+ * causes the path to get filled incorrectly/incompletely in the generated document.
+ *
+ * @param actionIn
+ *        the original path definition
+ *
+ * @param actionsOut
+ *        the modified path
+ */
+void GetCorrectedPath(
+  const vector<RVNGPropertyList> &actionsIn, vector<RVNGPropertyList> &actionsOut)
+{
+  PathActionsTp actions;
+  double x1 = 0.0, y1 = 0.0, x2 = 0.0, y2 = 0.0; // current line segment
+  string lastActionName;
+
+  for (unsigned int i = 0, cnt = actionsIn.size(); i < cnt; i++)
   {
-    bool firstPoint = true;
-    bool wasMove = false;
-    for (unsigned i = 0; i < m_currentFillGeometry.size(); i++)
+    string actionName = actionsIn[i][PROP_LIBREV_ACTION]->getStr().cstr();
+
+    if (actionName == "M")
+    {
+      x1 = actionsIn[i][PROP_SVG_X]->getDouble();
+      y1 = actionsIn[i][PROP_SVG_Y]->getDouble();
+    }
+    else if (actionName == "L")
     {
-      if (firstPoint)
+      x2 = actionsIn[i][PROP_SVG_X]->getDouble();
+      y2 = actionsIn[i][PROP_SVG_Y]->getDouble();
+
+      bool connected = false;
+
+      if (lastActionName != "M") // follows anything else than M
       {
-        firstPoint = false;
-        wasMove = true;
+        connected = true;
+        actions.push_back(PathActionS("L", x2, y2));
       }
-      else if (m_currentFillGeometry[i]["librevenge:path-action"]->getStr() == "M")
+
+      if (!connected && actions.size() > 1) // connect with the end
       {
-        if (!tmpPath.empty())
+        unsigned int lastIdx = actions.size() - 1;
+
+        if (actions[lastIdx].x == x1 && actions[lastIdx].y == y1)
         {
-          if (!wasMove)
-          {
-            if (tmpPath.back()["librevenge:path-action"]->getStr() != "Z")
-            {
-              librevenge::RVNGPropertyList closedPath;
-              closedPath.insert("librevenge:path-action", "Z");
-              tmpPath.push_back(closedPath);
-            }
-          }
-          else
-          {
-            tmpPath.pop_back();
-          }
+          connected = true;
+          actions.push_back(PathActionS("L", x2, y2));
         }
-        wasMove = true;
-      }
-      else
-        wasMove = false;
-      tmpPath.push_back(m_currentFillGeometry[i]);
-    }
-    if (!tmpPath.empty())
-    {
-      if (!wasMove)
-      {
-        if (tmpPath.back()["librevenge:path-action"]->getStr() != "Z")
+        else if (actions[lastIdx].x == x2 && actions[lastIdx].y == y2)
         {
-          librevenge::RVNGPropertyList closedPath;
-          closedPath.insert("librevenge:path-action", "Z");
-          tmpPath.push_back(closedPath);
+          connected = true;
+          actions.push_back(PathActionS("L", x1, y1));
         }
       }
-      else
-        tmpPath.pop_back();
-    }
-    if (!tmpPath.empty())
-    {
-      librevenge::RVNGPropertyListVector path;
-      _convertToPath(tmpPath, path, m_scale*m_lineStyle.rounding);
-      m_shapeOutputDrawing->addStyle(fillPathProps);
-      librevenge::RVNGPropertyList propList;
-      propList.insert("svg:d", path);
-      if (shapeId && shapeId != MINUS_ONE)
-      {
-        librevenge::RVNGString stringId;
-        stringId.sprintf("id%u", shapeId);
-        propList.insert("draw:id", stringId);
-        shapeId = MINUS_ONE;
-      }
-      _appendVisibleAndPrintable(propList);
-      m_shapeOutputDrawing->addPath(propList);
-    }
-  }
-  m_currentFillGeometry.clear();
-  tmpPath.clear();
 
-  if (m_lineStyle.pattern && !m_currentLineGeometry.empty())
-  {
-    bool firstPoint = true;
-    bool wasMove = false;
-    double x = 0.0;
-    double y = 0.0;
-    double prevX = 0.0;
-    double prevY = 0.0;
-    for (unsigned i = 0; i < m_currentLineGeometry.size(); i++)
-    {
-      if (firstPoint)
-      {
-        firstPoint = false;
-        wasMove = true;
-        x = m_currentLineGeometry[i]["svg:x"]->getDouble();
-        y = m_currentLineGeometry[i]["svg:y"]->getDouble();
-      }
-      else if (m_currentLineGeometry[i]["librevenge:path-action"]->getStr() == "M")
+      if (!connected && actions.size() > 0 && actions[0].name  == "M") // connect with the beginning
       {
-        if (!tmpPath.empty())
+        if (actions[0].x == x1 && actions[0].y == y1)
         {
-          if (!wasMove)
-          {
-            if (VSD_ALMOST_ZERO(x - prevX) && VSD_ALMOST_ZERO(y - prevY))
-            {
-              if (tmpPath.back()["librevenge:path-action"]->getStr() != "Z")
-              {
-                librevenge::RVNGPropertyList closedPath;
-                closedPath.insert("librevenge:path-action", "Z");
-                tmpPath.push_back(closedPath);
-              }
-            }
-          }
-          else
-          {
-            tmpPath.pop_back();
-          }
+          connected = true;
+          actions[0].name = "L";
+          actions.push_front(PathActionS("M", x2, y2));
+        }
+        else if (actions[0].x == x2 && actions[0].y == y2)
+        {
+          connected = true;
+          actions[0].name = "L";
+          actions.push_front(PathActionS("M", x1, y1));
         }
-        x = m_currentLineGeometry[i]["svg:x"]->getDouble();
-        y = m_currentLineGeometry[i]["svg:y"]->getDouble();
-        wasMove = true;
       }
-      else
-        wasMove = false;
-      tmpPath.push_back(m_currentLineGeometry[i]);
-      if (m_currentLineGeometry[i]["svg:x"])
-        prevX = m_currentLineGeometry[i]["svg:x"]->getDouble();
-      if (m_currentLineGeometry[i]["svg:y"])
-        prevY = m_currentLineGeometry[i]["svg:y"]->getDouble();
+
+      if (!connected) // store the first segment
+      {
+        actions.push_back(PathActionS("M", x1, y1));
+        actions.push_back(PathActionS("L", x2, y2));
+      }
+
+      x1 = x2;
+      y1 = y2;
     }
-    if (!tmpPath.empty())
+    else // not M nor L
     {
-      if (!wasMove)
+      if (lastActionName == "M")
       {
-        if (VSD_ALMOST_ZERO(x - prevX) && VSD_ALMOST_ZERO(y - prevY))
-        {
-          if (tmpPath.back()["librevenge:path-action"]->getStr() != "Z")
-          {
-            librevenge::RVNGPropertyList closedPath;
-            closedPath.insert("librevenge:path-action", "Z");
-            tmpPath.push_back(closedPath);
-          }
-        }
+        actionsOut.push_back(actionsIn[i - 1]);
       }
       else
       {
-        tmpPath.pop_back();
+        FlushPath(actions, actionsOut);
       }
+
+      actions.clear();
+      actionsOut.push_back(actionsIn[i]);
     }
-    if (!tmpPath.empty())
+
+    lastActionName = actionName;
+  }
+
+  FlushPath(actions, actionsOut);
+
+  int lastIdx = actionsOut.size() - 1;
+
+  // Close the path if the first and last points are (close to) identical. If the points are really
+  // identical, Z is not necessary and just serves as an indication of the closed path.
+  //
+  if (lastIdx > 0 && actionsOut[0][PROP_SVG_X] && actionsOut[0][PROP_SVG_Y]
+      && actionsOut[lastIdx][PROP_SVG_X] && actionsOut[lastIdx][PROP_SVG_Y])
+  {
+    x1 = actionsOut[0][PROP_SVG_X]->getDouble();
+    y1 = actionsOut[0][PROP_SVG_Y]->getDouble();
+    x2 = actionsOut[lastIdx][PROP_SVG_X]->getDouble();
+    y2 = actionsOut[lastIdx][PROP_SVG_Y]->getDouble();
+
+    if (VSD_ALMOST_ZERO(x1 - x2) && VSD_ALMOST_ZERO(y1 - y2))
     {
-      librevenge::RVNGPropertyListVector path;
-      _convertToPath(tmpPath, path, m_scale*m_lineStyle.rounding);
-      m_shapeOutputDrawing->addStyle(linePathProps);
-      librevenge::RVNGPropertyList propList;
-      propList.insert("svg:d", path);
-      if (shapeId && shapeId != MINUS_ONE)
-      {
-        librevenge::RVNGString stringId;
-        stringId.sprintf("id%u", shapeId);
-        propList.insert("draw:id", stringId);
-        shapeId = MINUS_ONE;
-      }
-      _appendVisibleAndPrintable(propList);
-      m_shapeOutputDrawing->addPath(propList);
+      RVNGPropertyList action;
+      action.insert(PROP_LIBREV_ACTION, "Z");
+      actionsOut.push_back(action);
+    }
+  }
+}
+
+void libvisio::VSDContentCollector::AddPath(
+  const vector<RVNGPropertyList> &inPath, const RVNGPropertyList &styleProps, unsigned shapeId)
+{
+  if (!inPath.empty())
+  {
+    RVNGPropertyListVector path;
+    _convertToPath(inPath, path, m_scale * m_lineStyle.rounding);
+    m_shapeOutputDrawing->addStyle(styleProps);
+
+    RVNGPropertyList propList;
+    propList.insert("svg:d", path);
+
+    if (shapeId && shapeId != MINUS_ONE)
+    {
+      RVNGString stringId;
+      stringId.sprintf("id%u", shapeId);
+      propList.insert("draw:id", stringId);
+      shapeId = MINUS_ONE;
     }
+
+    _appendVisibleAndPrintable(propList);
+    m_shapeOutputDrawing->addPath(propList);
+  }
+}
+
+void libvisio::VSDContentCollector::_flushCurrentPath(unsigned shapeId)
+{
+  RVNGPropertyList styleProps;
+  _lineProperties(m_lineStyle, styleProps);
+  _fillAndShadowProperties(m_fillStyle, styleProps);
+
+  if (m_fillStyle.pattern && !m_currentFillGeometry.empty())
+  {
+    RVNGPropertyList fillPathProps(styleProps);
+    fillPathProps.insert("draw:stroke", "none");
+    vector<RVNGPropertyList> outPath;
+
+    GetCorrectedPath(m_currentFillGeometry, outPath);
+    AddPath(outPath, fillPathProps, shapeId);
+  }
+
+  m_currentFillGeometry.clear();
+
+  if (m_lineStyle.pattern && !m_currentLineGeometry.empty())
+  {
+    RVNGPropertyList linePathProps(styleProps);
+    linePathProps.insert("draw:fill", "none");
+    vector<RVNGPropertyList> outPath;
+
+    GetCorrectedPath(m_currentLineGeometry, outPath);
+    AddPath(outPath, linePathProps, shapeId);
   }
+
   m_currentLineGeometry.clear();
 }
 
@@ -628,6 +732,12 @@ void libvisio::VSDContentCollector::_flushText()
   textBlockProps.insert("fo:padding-right", m_textBlockStyle.rightMargin);
   textBlockProps.insert("librevenge:rotate", angle*180/M_PI, librevenge::RVNG_GENERIC);
 
+  if (m_textBlockStyle.isTextBkgndFilled)
+  {
+    textBlockProps.insert(
+      PROP_FO_BACKGROUND_COLOR, getColourString(m_textBlockStyle.textBkgndColour));
+  }
+
   switch (m_textBlockStyle.verticalAlign)
   {
   case 0: // Top
@@ -2657,15 +2767,17 @@ void libvisio::VSDContentCollector::_lineProperties(const VSDLineStyle &style, l
   // Deal with line markers (arrows, etc.)
   if (style.startMarker > 0)
   {
-    styleProps.insert("draw:marker-start-viewbox", _linePropertiesMarkerViewbox(style.startMarker));
+    styleProps.insert("draw:marker-start-viewbox", _linePropertiesMarkerViewbox(style.startMarker, true));
     styleProps.insert("draw:marker-start-path", _linePropertiesMarkerPath(style.startMarker));
+    styleProps.insert("draw:marker-start-path-transform", _linePropertiesMarkerPathTransform(style.startMarker, true));
     double w =  m_scale*_linePropertiesMarkerScale(style.startMarker)*(0.1/(style.width*style.width+1)+2.54*style.width);
     styleProps.insert("draw:marker-start-width", (std::max)(w, 0.05));
   }
   if (style.endMarker > 0)
   {
-    styleProps.insert("draw:marker-end-viewbox", _linePropertiesMarkerViewbox(style.endMarker));
+    styleProps.insert("draw:marker-end-viewbox", _linePropertiesMarkerViewbox(style.endMarker, false));
     styleProps.insert("draw:marker-end-path", _linePropertiesMarkerPath(style.endMarker));
+    styleProps.insert("draw:marker-end-path-transform", _linePropertiesMarkerPathTransform(style.endMarker, false));
     double w =  m_scale*_linePropertiesMarkerScale(style.endMarker)*(0.1/(style.width*style.width+1)+2.54*style.width);
     styleProps.insert("draw:marker-end-width", (std::max)(w, 0.05));
   }
@@ -3515,6 +3627,12 @@ void libvisio::VSDContentCollector::collectLayerMem(unsigned level, const VSDNam
   memcpy(&tmpData[0], layerMem.m_data.getDataBuffer(), layerMem.m_data.size());
   appendCharacters(text, tmpData, layerMem.m_format);
 
+  if (tmpData.size() > 0)
+  {
+    memcpy(&tmpData[0], layerMem.m_data.getDataBuffer(), layerMem.m_data.size());
+    appendCharacters(text, tmpData, layerMem.m_format);
+  }
+
   m_currentLayerMem.clear();
 
   bool bRes = parse(text.cstr(),
@@ -3620,7 +3738,7 @@ void libvisio::VSDContentCollector::_listLevelFromBullet(librevenge::RVNGPropert
   propList.insert("librevenge:level", 1);
   propList.insert("text:bullet-char", bullet.m_bulletStr);
   if (!(bullet.m_bulletFont.empty()))
-    propList.insert("fo:font-family", bullet.m_bulletFont);
+    propList.insert("fo:font-name", bullet.m_bulletFont);
   if (bullet.m_bulletFontSize > 0.0)
     propList.insert("fo:font-size", bullet.m_bulletFontSize*72.0, librevenge::RVNG_POINT);
   else if (bullet.m_bulletFontSize < 0.0)
diff --git a/src/lib/VSDContentCollector.h b/src/lib/VSDContentCollector.h
index 4460e35..eb8666c 100644
--- a/src/lib/VSDContentCollector.h
+++ b/src/lib/VSDContentCollector.h
@@ -205,7 +205,8 @@ private:
   void _fillAndShadowProperties(const VSDFillStyle &style, librevenge::RVNGPropertyList &styleProps);
 
   void _applyLinePattern();
-  const char *_linePropertiesMarkerViewbox(unsigned marker);
+  const char *_linePropertiesMarkerPathTransform(unsigned marker, bool reverse);
+  const char *_linePropertiesMarkerViewbox(unsigned marker, bool reverse);
   const char *_linePropertiesMarkerPath(unsigned marker);
   double _linePropertiesMarkerScale(unsigned marker);
 
@@ -233,6 +234,10 @@ private:
   void _convertToPath(const std::vector<librevenge::RVNGPropertyList> &segmentVector,
                       librevenge::RVNGPropertyListVector &path, double rounding);
 
+  void AddPath(
+    const std::vector<librevenge::RVNGPropertyList> &path,
+    const librevenge::RVNGPropertyList &styleProps, unsigned int shapeId);
+
   bool m_isPageStarted;
   double m_pageWidth;
   double m_pageHeight;
diff --git a/src/lib/VSDMetaData.cpp b/src/lib/VSDMetaData.cpp
index 7241b00..52b6575 100644
--- a/src/lib/VSDMetaData.cpp
+++ b/src/lib/VSDMetaData.cpp
@@ -307,7 +307,7 @@ bool libvisio::VSDMetaData::parseTimes(librevenge::RVNGInputStream *input)
   uint32_t firstDirSectorLocation = readU32(input);
 
   // Seek to the Root Directory Entry
-  size_t sectorSize = std::pow(2, sectorShift);
+  size_t sectorSize = static_cast<size_t>(std::pow(2.0, sectorShift));
   input->seek((firstDirSectorLocation + 1) * sectorSize, librevenge::RVNG_SEEK_SET);
   // DirectoryEntryName: 64 bytes
   // DirectoryEntryNameLength: 2 bytes
diff --git a/src/lib/VSDOutputElementList.cpp b/src/lib/VSDOutputElementList.cpp
index 5b06fb8..928dbe4 100644
--- a/src/lib/VSDOutputElementList.cpp
+++ b/src/lib/VSDOutputElementList.cpp
@@ -8,6 +8,10 @@
  */
 
 #include "VSDOutputElementList.h"
+#include "VsdElementListPreprocessor.h"
+
+using namespace std;
+
 
 namespace libvisio
 {
@@ -25,7 +29,7 @@ static void separateSpacesAndInsertText(librevenge::RVNGDrawingInterface *iface,
     return;
   }
   librevenge::RVNGString tmpText;
-  int numConsecutiveSpaces = 0;
+  int numConsecutiveSpaces = 1; // force initial single space replacement
   librevenge::RVNGString::Iter i(text);
   for (i.rewind(); i.next();)
   {
@@ -42,8 +46,7 @@ static void separateSpacesAndInsertText(librevenge::RVNGDrawingInterface *iface,
         tmpText.clear();
       }
 
-      if (iface)
-        iface->insertSpace();
+      iface->insertSpace();
     }
     else
     {
@@ -55,269 +58,6 @@ static void separateSpacesAndInsertText(librevenge::RVNGDrawingInterface *iface,
 
 } // anonymous namespace
 
-class VSDOutputElement
-{
-public:
-  VSDOutputElement() {}
-  virtual ~VSDOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter) = 0;
-  virtual VSDOutputElement *clone() = 0;
-};
-
-
-class VSDStyleOutputElement : public VSDOutputElement
-{
-public:
-  VSDStyleOutputElement(const librevenge::RVNGPropertyList &propList);
-  virtual ~VSDStyleOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter);
-  virtual VSDOutputElement *clone()
-  {
-    return new VSDStyleOutputElement(m_propList);
-  }
-private:
-  librevenge::RVNGPropertyList m_propList;
-};
-
-
-class VSDPathOutputElement : public VSDOutputElement
-{
-public:
-  VSDPathOutputElement(const librevenge::RVNGPropertyList &propList);
-  virtual ~VSDPathOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter);
-  virtual VSDOutputElement *clone()
-  {
-    return new VSDPathOutputElement(m_propList);
-  }
-private:
-  librevenge::RVNGPropertyList m_propList;
-};
-
-
-class VSDGraphicObjectOutputElement : public VSDOutputElement
-{
-public:
-  VSDGraphicObjectOutputElement(const librevenge::RVNGPropertyList &propList);
-  virtual ~VSDGraphicObjectOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter);
-  virtual VSDOutputElement *clone()
-  {
-    return new VSDGraphicObjectOutputElement(m_propList);
-  }
-private:
-  librevenge::RVNGPropertyList m_propList;
-};
-
-
-class VSDStartTextObjectOutputElement : public VSDOutputElement
-{
-public:
-  VSDStartTextObjectOutputElement(const librevenge::RVNGPropertyList &propList);
-  virtual ~VSDStartTextObjectOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter);
-  virtual VSDOutputElement *clone()
-  {
-    return new VSDStartTextObjectOutputElement(m_propList);
-  }
-private:
-  librevenge::RVNGPropertyList m_propList;
-};
-
-
-class VSDOpenParagraphOutputElement : public VSDOutputElement
-{
-public:
-  VSDOpenParagraphOutputElement(const librevenge::RVNGPropertyList &propList);
-  virtual ~VSDOpenParagraphOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter);
-  virtual VSDOutputElement *clone()
-  {
-    return new VSDOpenParagraphOutputElement(m_propList);
-  }
-private:
-  librevenge::RVNGPropertyList m_propList;
-};
-
-
-class VSDStartLayerOutputElement : public VSDOutputElement
-{
-public:
-  VSDStartLayerOutputElement(const librevenge::RVNGPropertyList &propList);
-  virtual ~VSDStartLayerOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter);
-  virtual VSDOutputElement *clone()
-  {
-    return new VSDStartLayerOutputElement(m_propList);
-  }
-private:
-  librevenge::RVNGPropertyList m_propList;
-};
-
-
-class VSDEndLayerOutputElement : public VSDOutputElement
-{
-public:
-  VSDEndLayerOutputElement();
-  virtual ~VSDEndLayerOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter);
-  virtual VSDOutputElement *clone()
-  {
-    return new VSDEndLayerOutputElement();
-  }
-};
-
-
-class VSDOpenSpanOutputElement : public VSDOutputElement
-{
-public:
-  VSDOpenSpanOutputElement(const librevenge::RVNGPropertyList &propList);
-  virtual ~VSDOpenSpanOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter);
-  virtual VSDOutputElement *clone()
-  {
-    return new VSDOpenSpanOutputElement(m_propList);
-  }
-private:
-  librevenge::RVNGPropertyList m_propList;
-};
-
-
-class VSDInsertTextOutputElement : public VSDOutputElement
-{
-public:
-  VSDInsertTextOutputElement(const librevenge::RVNGString &text);
-  virtual ~VSDInsertTextOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter);
-  virtual VSDOutputElement *clone()
-  {
-    return new VSDInsertTextOutputElement(m_text);
-  }
-private:
-  librevenge::RVNGString m_text;
-};
-
-
-class VSDInsertLineBreakOutputElement : public VSDOutputElement
-{
-public:
-  VSDInsertLineBreakOutputElement();
-  virtual ~VSDInsertLineBreakOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter);
-  virtual VSDOutputElement *clone()
-  {
-    return new VSDInsertLineBreakOutputElement();
-  }
-};
-
-
-class VSDInsertTabOutputElement : public VSDOutputElement
-{
-public:
-  VSDInsertTabOutputElement();
-  virtual ~VSDInsertTabOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter);
-  virtual VSDOutputElement *clone()
-  {
-    return new VSDInsertTabOutputElement();
-  }
-};
-
-
-class VSDCloseSpanOutputElement : public VSDOutputElement
-{
-public:
-  VSDCloseSpanOutputElement();
-  virtual ~VSDCloseSpanOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter);
-  virtual VSDOutputElement *clone()
-  {
-    return new VSDCloseSpanOutputElement();
-  }
-};
-
-
-class VSDCloseParagraphOutputElement : public VSDOutputElement
-{
-public:
-  VSDCloseParagraphOutputElement();
-  virtual ~VSDCloseParagraphOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter);
-  virtual VSDOutputElement *clone()
-  {
-    return new VSDCloseParagraphOutputElement();
-  }
-};
-
-
-class VSDEndTextObjectOutputElement : public VSDOutputElement
-{
-public:
-  VSDEndTextObjectOutputElement();
-  virtual ~VSDEndTextObjectOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter);
-  virtual VSDOutputElement *clone()
-  {
-    return new VSDEndTextObjectOutputElement();
-  }
-};
-
-class VSDOpenListElementOutputElement : public VSDOutputElement
-{
-public:
-  VSDOpenListElementOutputElement(const librevenge::RVNGPropertyList &propList);
-  virtual ~VSDOpenListElementOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter);
-  virtual VSDOutputElement *clone()
-  {
-    return new VSDOpenListElementOutputElement(m_propList);
-  }
-private:
-  librevenge::RVNGPropertyList m_propList;
-};
-
-
-class VSDCloseListElementOutputElement : public VSDOutputElement
-{
-public:
-  VSDCloseListElementOutputElement();
-  virtual ~VSDCloseListElementOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter);
-  virtual VSDOutputElement *clone()
-  {
-    return new VSDCloseListElementOutputElement();
-  }
-};
-
-
-class VSDOpenUnorderedListLevelOutputElement : public VSDOutputElement
-{
-public:
-  VSDOpenUnorderedListLevelOutputElement(const librevenge::RVNGPropertyList &propList);
-  virtual ~VSDOpenUnorderedListLevelOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter);
-  virtual VSDOutputElement *clone()
-  {
-    return new VSDOpenUnorderedListLevelOutputElement(m_propList);
-  }
-private:
-  librevenge::RVNGPropertyList m_propList;
-};
-
-
-class VSDCloseUnorderedListLevelOutputElement : public VSDOutputElement
-{
-public:
-  VSDCloseUnorderedListLevelOutputElement();
-  virtual ~VSDCloseUnorderedListLevelOutputElement() {}
-  virtual void draw(librevenge::RVNGDrawingInterface *painter);
-  virtual VSDOutputElement *clone()
-  {
-    return new VSDCloseUnorderedListLevelOutputElement();
-  }
-};
-
-
 } // namespace libvisio
 
 libvisio::VSDStyleOutputElement::VSDStyleOutputElement(const librevenge::RVNGPropertyList &propList) :
@@ -529,8 +269,16 @@ libvisio::VSDOutputElementList::~VSDOutputElementList()
 
 void libvisio::VSDOutputElementList::draw(librevenge::RVNGDrawingInterface *painter) const
 {
-  for (std::vector<VSDOutputElement *>::const_iterator iter = m_elements.begin(); iter != m_elements.end(); ++iter)
+  VsdElementListPreprocessorC velp;
+  vector<VSDOutputElement *> outElements;
+  velp.Process(m_elements, outElements);
+
+  for (std::vector<VSDOutputElement *>::const_iterator iter = outElements.begin();
+       iter != outElements.end(); ++iter)
+  {
     (*iter)->draw(painter);
+    delete *iter;
+  }
 }
 
 void libvisio::VSDOutputElementList::addStyle(const librevenge::RVNGPropertyList &propList)
diff --git a/src/lib/VSDOutputElementList.h b/src/lib/VSDOutputElementList.h
index 0a500e2..8d5b68e 100644
--- a/src/lib/VSDOutputElementList.h
+++ b/src/lib/VSDOutputElementList.h
@@ -18,7 +18,322 @@
 namespace libvisio
 {
 
-class VSDOutputElement;
+class VSDOutputElement
+{
+public:
+  VSDOutputElement() {}
+  virtual ~VSDOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter) = 0;
+  virtual VSDOutputElement *clone() = 0;
+};
+
+
+class VSDStyleOutputElement : public VSDOutputElement
+{
+public:
+  VSDStyleOutputElement(const librevenge::RVNGPropertyList &propList);
+  virtual ~VSDStyleOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter);
+  virtual VSDOutputElement *clone()
+  {
+    return new VSDStyleOutputElement(m_propList);
+  }
+private:
+  librevenge::RVNGPropertyList m_propList;
+};
+
+
+class VSDPathOutputElement : public VSDOutputElement
+{
+public:
+  VSDPathOutputElement(const librevenge::RVNGPropertyList &propList);
+  virtual ~VSDPathOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter);
+  virtual VSDOutputElement *clone()
+  {
+    return new VSDPathOutputElement(m_propList);
+  }
+private:
+  librevenge::RVNGPropertyList m_propList;
+};
+
+
+class VSDGraphicObjectOutputElement : public VSDOutputElement
+{
+public:
+  VSDGraphicObjectOutputElement(const librevenge::RVNGPropertyList &propList);
+  virtual ~VSDGraphicObjectOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter);
+  virtual VSDOutputElement *clone()
+  {
+    return new VSDGraphicObjectOutputElement(m_propList);
+  }
+private:
+  librevenge::RVNGPropertyList m_propList;
+};
+
+
+class VSDStartTextObjectOutputElement : public VSDOutputElement
+{
+public:
+  VSDStartTextObjectOutputElement(const librevenge::RVNGPropertyList &propList);
+  virtual ~VSDStartTextObjectOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter);
+  virtual VSDOutputElement *clone()
+  {
+    return new VSDStartTextObjectOutputElement(m_propList);
+  }
+
+  librevenge::RVNGPropertyList &GetPropertyList()
+  {
+    return m_propList;
+  }
+
+private:
+  librevenge::RVNGPropertyList m_propList;
+};
+
+
+class VSDOpenParagraphOutputElement : public VSDOutputElement
+{
+public:
+  VSDOpenParagraphOutputElement(const librevenge::RVNGPropertyList &propList);
+  virtual ~VSDOpenParagraphOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter);
+  virtual VSDOutputElement *clone()
+  {
+    return new VSDOpenParagraphOutputElement(m_propList);
+  }
+
+  librevenge::RVNGPropertyList &GetPropertyList()
+  {
+    return m_propList;
+  }
+  const librevenge::RVNGPropertyList &GetPropertyList() const
+  {
+    return m_propList;
+  }
+
+private:
+  librevenge::RVNGPropertyList m_propList;
+};
+
+
+class VSDStartLayerOutputElement : public VSDOutputElement
+{
+public:
+  VSDStartLayerOutputElement(const librevenge::RVNGPropertyList &propList);
+  virtual ~VSDStartLayerOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter);
+  virtual VSDOutputElement *clone()
+  {
+    return new VSDStartLayerOutputElement(m_propList);
+  }
+private:
+  librevenge::RVNGPropertyList m_propList;
+};
+
+
+class VSDEndLayerOutputElement : public VSDOutputElement
+{
+public:
+  VSDEndLayerOutputElement();
+  virtual ~VSDEndLayerOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter);
+  virtual VSDOutputElement *clone()
+  {
+    return new VSDEndLayerOutputElement();
+  }
+};
+
+
+class VSDOpenSpanOutputElement : public VSDOutputElement
+{
+public:
+  VSDOpenSpanOutputElement(const librevenge::RVNGPropertyList &propList);
+  virtual ~VSDOpenSpanOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter);
+  virtual VSDOutputElement *clone()
+  {
+    return new VSDOpenSpanOutputElement(m_propList);
+  }
+
+  librevenge::RVNGPropertyList &GetPropertyList()
+  {
+    return m_propList;
+  }
+  const librevenge::RVNGPropertyList &GetPropertyList() const
+  {
+    return m_propList;
+  }
+
+private:
+  librevenge::RVNGPropertyList m_propList;
+};
+
+
+class VSDInsertTextOutputElement : public VSDOutputElement
+{
+public:
+  VSDInsertTextOutputElement(const librevenge::RVNGString &text);
+  virtual ~VSDInsertTextOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter);
+  virtual VSDOutputElement *clone()
+  {
+    return new VSDInsertTextOutputElement(m_text);
+  }
+
+  const librevenge::RVNGString &GetText() const
+  {
+    return m_text;
+  }
+
+private:
+  librevenge::RVNGString m_text;
+};
+
+
+class VSDInsertLineBreakOutputElement : public VSDOutputElement
+{
+public:
+  VSDInsertLineBreakOutputElement();
+  virtual ~VSDInsertLineBreakOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter);
+  virtual VSDOutputElement *clone()
+  {
+    return new VSDInsertLineBreakOutputElement();
+  }
+};
+
+
+class VSDInsertTabOutputElement : public VSDOutputElement
+{
+public:
+  VSDInsertTabOutputElement();
+  virtual ~VSDInsertTabOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter);
+  virtual VSDOutputElement *clone()
+  {
+    return new VSDInsertTabOutputElement();
+  }
+};
+
+
+class VSDCloseSpanOutputElement : public VSDOutputElement
+{
+public:
+  VSDCloseSpanOutputElement();
+  virtual ~VSDCloseSpanOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter);
+  virtual VSDOutputElement *clone()
+  {
+    return new VSDCloseSpanOutputElement();
+  }
+};
+
+
+class VSDCloseParagraphOutputElement : public VSDOutputElement
+{
+public:
+  VSDCloseParagraphOutputElement();
+  virtual ~VSDCloseParagraphOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter);
+  virtual VSDOutputElement *clone()
+  {
+    return new VSDCloseParagraphOutputElement();
+  }
+};
+
+
+class VSDEndTextObjectOutputElement : public VSDOutputElement
+{
+public:
+  VSDEndTextObjectOutputElement();
+  virtual ~VSDEndTextObjectOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter);
+  virtual VSDOutputElement *clone()
+  {
+    return new VSDEndTextObjectOutputElement();
+  }
+};
+
+
+class VSDOpenListElementOutputElement : public VSDOutputElement
+{
+public:
+  VSDOpenListElementOutputElement(const librevenge::RVNGPropertyList &propList);
+  virtual ~VSDOpenListElementOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter);
+  virtual VSDOutputElement *clone()
+  {
+    return new VSDOpenListElementOutputElement(m_propList);
+  }
+
+  librevenge::RVNGPropertyList &GetPropertyList()
+  {
+    return m_propList;
+  }
+  const librevenge::RVNGPropertyList &GetPropertyList() const
+  {
+    return m_propList;
+  }
+
+private:
+  librevenge::RVNGPropertyList m_propList;
+};
+
+
+class VSDCloseListElementOutputElement : public VSDOutputElement
+{
+public:
+  VSDCloseListElementOutputElement();
+  virtual ~VSDCloseListElementOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter);
+  virtual VSDOutputElement *clone()
+  {
+    return new VSDCloseListElementOutputElement();
+  }
+
+};
+
+
+class VSDOpenUnorderedListLevelOutputElement : public VSDOutputElement
+{
+public:
+  VSDOpenUnorderedListLevelOutputElement(const librevenge::RVNGPropertyList &propList);
+  virtual ~VSDOpenUnorderedListLevelOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter);
+  virtual VSDOutputElement *clone()
+  {
+    return new VSDOpenUnorderedListLevelOutputElement(m_propList);
+  }
+
+  librevenge::RVNGPropertyList &GetPropertyList()
+  {
+    return m_propList;
+  }
+  const librevenge::RVNGPropertyList &GetPropertyList() const
+  {
+    return m_propList;
+  }
+
+private:
+  librevenge::RVNGPropertyList m_propList;
+};
+
+
+class VSDCloseUnorderedListLevelOutputElement : public VSDOutputElement
+{
+public:
+  VSDCloseUnorderedListLevelOutputElement();
+  virtual ~VSDCloseUnorderedListLevelOutputElement() {}
+  virtual void draw(librevenge::RVNGDrawingInterface *painter);
+  virtual VSDOutputElement *clone()
+  {
+    return new VSDCloseUnorderedListLevelOutputElement();
+  }
+};
+
 
 class VSDOutputElementList
 {
diff --git a/src/lib/preprocess/SvgDC.cpp b/src/lib/preprocess/SvgDC.cpp
new file mode 100644
index 0000000..a0daa20
--- /dev/null
+++ b/src/lib/preprocess/SvgDC.cpp
@@ -0,0 +1 @@
+#include "SvgDC.h"
diff --git a/src/lib/preprocess/SvgDC.h b/src/lib/preprocess/SvgDC.h
new file mode 100644
index 0000000..a852bd8
--- /dev/null
+++ b/src/lib/preprocess/SvgDC.h
@@ -0,0 +1,32 @@
+#ifndef _SVG_DC_H_INCLUDED_
+#define _SVG_DC_H_INCLUDED_
+
+
+#include <string>
+#include <vector>
+
+
+class SvgFontC;
+
+class SvgDC
+{
+public:
+  virtual ~SvgDC() {}
+
+  virtual const SvgFontC *GetFont(
+    double heightInch, unsigned int weight, bool isItalic, const std::string &name) const = 0;
+
+  virtual double GetFontBaseLineHeightRatio(unsigned int fontId) const = 0;
+
+  virtual double GetTextPartialExtents(
+    const std::wstring &text, unsigned int fontId, double offsetInch,
+    std::vector<double> &extentsInch) const = 0;
+
+  virtual double GetTabCharExtent(unsigned int fontId, double offsetInch) const = 0;
+};
+
+
+extern SvgDC &GetSystemDC();
+
+
+#endif // _SVG_DC_H_
diff --git a/src/lib/preprocess/SvgFont.cpp b/src/lib/preprocess/SvgFont.cpp
new file mode 100644
index 0000000..4309188
--- /dev/null
+++ b/src/lib/preprocess/SvgFont.cpp
@@ -0,0 +1,61 @@
+#include "SvgFont.h"
+
+using namespace std;
+
+
+SvgFontC::SvgFontC(
+  unsigned int id, double heightInch, unsigned int weight, bool isItalic, const string &faceName
+)
+  : m_id(id),
+    m_heightInch(heightInch),
+    m_weight(weight),
+    m_isItalic(isItalic),
+    m_faceName(faceName)
+{
+}
+
+unsigned int SvgFontC::GetId() const
+{
+  return m_id;
+}
+
+double SvgFontC::GetHeightInch() const
+{
+  return m_heightInch;
+}
+
+unsigned int SvgFontC::GetWeight() const
+{
+  return m_weight;
+}
+
+bool SvgFontC::IsItalic() const
+{
+  return m_isItalic;
+}
+
+string SvgFontC::GetFaceName() const
+{
+  return m_faceName;
+}
+
+
+bool SvgFontCompareS::operator()(const SvgFontC *pFont1, const SvgFontC *pFont2) const
+{
+  if (pFont1->GetHeightInch() != pFont2->GetHeightInch())
+  {
+    return pFont1->GetHeightInch() < pFont2->GetHeightInch();
+  }
+
+  if (pFont1->GetWeight() != pFont2->GetWeight())
+  {
+    return pFont1->GetWeight() < pFont2->GetWeight();
+  }
+
+  if (pFont1->IsItalic() != pFont2->IsItalic())
+  {
+    return !pFont1->IsItalic();
+  }
+
+  return pFont1->GetFaceName().compare(pFont2->GetFaceName()) < 0;
+}
diff --git a/src/lib/preprocess/SvgFont.h b/src/lib/preprocess/SvgFont.h
new file mode 100644
index 0000000..af859c4
--- /dev/null
+++ b/src/lib/preprocess/SvgFont.h
@@ -0,0 +1,38 @@
+#ifndef _SVG_FONT_H_INCLUDED_
+#define _SVG_FONT_H_INCLUDED_
+
+
+#include <string>
+
+
+class SvgFontC
+{
+public:
+  SvgFontC(
+    unsigned int id, double heightInch, unsigned int weight, bool isItalic,
+    const std::string &faceName);
+
+  virtual ~SvgFontC() {}
+
+  unsigned int GetId() const;
+  double GetHeightInch() const;
+  unsigned int GetWeight() const;
+  bool IsItalic() const;
+  std::string GetFaceName() const;
+
+protected:
+  unsigned int m_id;
+  double m_heightInch;
+  unsigned int m_weight;
+  bool m_isItalic;
+  const std::string m_faceName;
+};
+
+
+struct SvgFontCompareS
+{
+  bool operator()(const SvgFontC *pFont1, const SvgFontC *pFont2) const;
+};
+
+
+#endif // _SVG_FONT_H_
diff --git a/src/lib/preprocess/SvgParagraph.cpp b/src/lib/preprocess/SvgParagraph.cpp
new file mode 100644
index 0000000..47b2f4d
--- /dev/null
+++ b/src/lib/preprocess/SvgParagraph.cpp
@@ -0,0 +1,152 @@
+#include "librevenge/SvgConstants.h"
+#include "SvgParagraph.h"
+#include "SvgUtils.h"
+#include "VSDOutputElementList.h"
+
+#include <assert.h>
+
+using namespace librevenge;
+using namespace libvisio;
+using namespace std;
+using namespace svgconstants;
+
+
+ParagraphC::ParagraphC()
+  : m_margLeftInch(0.0),
+    m_margRightInch(0.0),
+    m_margTopInch(0.0),
+    m_margBottomInch(0.0),
+    m_lineHeight(1.0),
+    m_isLineHeightRel(true)
+{
+}
+
+double ParagraphC::GetMarginLeftInch() const
+{
+  return m_margLeftInch;
+}
+
+double ParagraphC::GetMarginRightInch() const
+{
+  return m_margRightInch;
+}
+
+double ParagraphC::GetMarginTopInch() const
+{
+  return m_margTopInch;
+}
+
+double ParagraphC::GetMarginBottomInch() const
+{
+  return m_margBottomInch;
+}
+
+double ParagraphC::GetLineHeightInch(double fontSizeInch) const
+{
+  if (m_isLineHeightRel)
+  {
+    return fontSizeInch * m_lineHeight;
+  }
+
+  return m_lineHeight;
+}
+
+void ParagraphC::Reset(const VSDOpenParagraphOutputElement *pOpenParagraph)
+{
+  Reset(pOpenParagraph->GetPropertyList());
+}
+
+void ParagraphC::Reset(const VSDOpenListElementOutputElement *pOpenListElem)
+{
+  Reset(pOpenListElem->GetPropertyList());
+}
+
+void ParagraphC::Reset(const RVNGPropertyList &properties)
+{
+  m_margLeftInch = properties[PROP_FO_MARGIN_LEFT]
+                   ? SvgUtilsC::GetInchValue(*properties[PROP_FO_MARGIN_LEFT]) : 0.0;
+
+  m_margRightInch = properties[PROP_FO_MARGIN_RIGHT]
+                    ? SvgUtilsC::GetInchValue(*properties[PROP_FO_MARGIN_RIGHT]) : 0.0;
+
+  m_margTopInch = properties[PROP_FO_MARGIN_TOP]
+                  ? SvgUtilsC::GetInchValue(*properties[PROP_FO_MARGIN_TOP]) : 0.0;
+
+  m_margBottomInch = properties[PROP_FO_MARGIN_BOTTOM]
+                     ? SvgUtilsC::GetInchValue(*properties[PROP_FO_MARGIN_BOTTOM]) : 0.0;
+
+  m_horizontalAlignment = properties[PROP_FO_TEXT_ALIGN]
+                          ? (*properties[PROP_FO_TEXT_ALIGN]).getStr().cstr() : PVAL_FO_TEXT_ALIGN_CENTER;
+
+  if (properties[PROP_FO_LINE_HEIGHT])
+  {
+    switch (properties[PROP_FO_LINE_HEIGHT]->getUnit())
+    {
+    case RVNG_UNIT_ERROR :
+      m_lineHeight = 1.0;
+      m_isLineHeightRel = true;
+      break;
+
+    case RVNG_PERCENT :
+      m_lineHeight = properties[PROP_FO_LINE_HEIGHT]->getDouble();
+      m_isLineHeightRel = true;
+      break;
+
+    default :
+      m_lineHeight = SvgUtilsC::GetInchValue(*properties[PROP_FO_LINE_HEIGHT]);
+      m_isLineHeightRel = false;
+      break;
+    }
+  }
+  else
+  {
+    m_lineHeight = 1.0;
+    m_isLineHeightRel = true;
+  }
+
+  m_text.clear();
+  m_textExtents.clear();
+  m_spanMarks.clear();
+}
+
+void ParagraphC::AddSpan(const VSDOpenSpanOutputElement *pSpan)
+{
+  m_spanMarks[m_text.size()] = pSpan;
+}
+
+void ParagraphC::AddText(const wstring &text, const TextExtentsTp &extents)
+{
+  assert(text.size() == extents.size());
+  m_text += text;
+  m_textExtents.insert(m_textExtents.end(), extents.begin(), extents.end());
+}
+
+double ParagraphC::GetCurrentTextExtentInch() const
+{
+  if (m_textExtents.size() > 0)
+  {
+    return m_textExtents[m_textExtents.size() - 1];
+  }
+
+  return 0;
+}
+
+const string &ParagraphC::GetHorizontalAlignment() const
+{
+  return m_horizontalAlignment;
+}
+
+const wstring &ParagraphC::GetText() const
+{
+  return m_text;
+}
+
+const ParagraphC::TextExtentsTp &ParagraphC::GetTextExtents() const
+{
+  return m_textExtents;
+}
+
+const ParagraphC::SpanMarksTp &ParagraphC::GetSpanMarks() const
+{
+  return m_spanMarks;
+}
diff --git a/src/lib/preprocess/SvgParagraph.h b/src/lib/preprocess/SvgParagraph.h
new file mode 100644
index 0000000..578c909
--- /dev/null
+++ b/src/lib/preprocess/SvgParagraph.h
@@ -0,0 +1,69 @@
+#ifndef _SVG_PARAGRAPH_H_INCLUDED_
+#define _SVG_PARAGRAPH_H_INCLUDED_
+
+
+#include <map>
+#include <string>
+#include <vector>
+
+
+namespace librevenge
+{
+class RVNGPropertyList;
+}
+
+namespace libvisio
+{
+class VSDOpenParagraphOutputElement;
+class VSDOpenListElementOutputElement;
+class VSDOpenSpanOutputElement;
+}
+
+/**
+ * Represents a paragraph of text within the original text box. Manages individual spans of text
+ * and stores information about partial text extents.
+ */
+class ParagraphC
+{
+public:
+  typedef std::vector<double> TextExtentsTp;
+  typedef std::map<int, const libvisio::VSDOpenSpanOutputElement *> SpanMarksTp;
+  typedef SpanMarksTp::const_iterator SpanMarksConstItTp;
+
+  ParagraphC();
+
+  double GetMarginLeftInch() const;
+  double GetMarginRightInch() const;
+  double GetMarginTopInch() const;
+  double GetMarginBottomInch() const;
+  double GetLineHeightInch(double fontSizeInch) const;
+
+  void Reset(const libvisio::VSDOpenParagraphOutputElement *pOpenParagraph);
+  void Reset(const libvisio::VSDOpenListElementOutputElement *pOpenListElem);
+  void Reset(const librevenge::RVNGPropertyList &properties);
+  void AddSpan(const libvisio::VSDOpenSpanOutputElement *pSpan);
+  void AddText(const std::wstring &text, const TextExtentsTp &extents);
+
+  double GetCurrentTextExtentInch() const;
+  const std::string &GetHorizontalAlignment() const;
+  const std::wstring &GetText() const;
+  const TextExtentsTp &GetTextExtents() const;
+  const SpanMarksTp &GetSpanMarks() const;
+  const libvisio::VSDOpenSpanOutputElement *GetSpanFromIndex(unsigned int index);
+
+private:
+  double m_margLeftInch; ///< left margin in inches
+  double m_margRightInch; ///< right margin in inches
+  double m_margTopInch; ///<< top margin in inches
+  double m_margBottomInch; ///< bottom margin in inches
+
+  double m_lineHeight; ///< line height (relative or absolute in inches)
+  bool m_isLineHeightRel; ///< indicates if m_lineHeight is a relative value
+  std::string m_horizontalAlignment; ///< horizontal alignment of the text
+  std::wstring m_text; ///< paragraph text
+  TextExtentsTp m_textExtents; ///< contiguous line of extents of characters within the paragraph
+  SpanMarksTp m_spanMarks; ///< maps indices of characters within paragraph text to Span elements
+};
+
+
+#endif // _SVG_PARAGRAPH_H_INCLUDED_
diff --git a/src/lib/preprocess/SvgSpan.cpp b/src/lib/preprocess/SvgSpan.cpp
new file mode 100644
index 0000000..5e33261
--- /dev/null
+++ b/src/lib/preprocess/SvgSpan.cpp
@@ -0,0 +1,74 @@
+#include "SvgSpan.h"
+
+using namespace libvisio;
+using namespace std;
+
+
+SpanC::SpanC(
+  const VSDOpenSpanOutputElement *pSpan, const wstring &text, double offsetInch,
+  double widthInch, double lastCharWidthInch, bool hasNewLine)
+  : m_pSpan(pSpan),
+    m_text(text),
+    m_offsetInch(offsetInch),
+    m_widthInch(widthInch),
+    m_lastCharWidthInch(lastCharWidthInch),
+    m_rowTotalWidthInch(-1.0),
+    m_endsWithNewLine(hasNewLine)
+{
+}
+
+const VSDOpenSpanOutputElement *SpanC::GetSpan() const
+{
+  return m_pSpan;
+}
+
+const wstring SpanC::GetText(bool exclTrailWhiteSpace) const
+{
+  unsigned int textLen = m_text.size();
+
+  if (m_endsWithNewLine && exclTrailWhiteSpace
+      && textLen > 0 && iswspace(m_text[textLen - 1]))
+  {
+    return m_text.substr(0, textLen - 1);
+  }
+
+  return m_text;
+}
+
+double SpanC::GetSpanOffsetInch() const
+{
+  return m_offsetInch;
+}
+
+double SpanC::GetSpanWidthInch(bool exclTrailWhiteSpace) const
+{
+  unsigned int textLen = m_text.size();
+
+  if (m_endsWithNewLine && exclTrailWhiteSpace
+      && textLen > 0 && iswspace(m_text[textLen - 1]))
+  {
+    return m_widthInch - m_lastCharWidthInch;
+  }
+
+  return m_widthInch;
+}
+
+double SpanC::GetRowTotalWidthInch() const
+{
+  return m_rowTotalWidthInch;
+}
+
+bool SpanC::EndsWithNewLine() const
+{
+  return m_endsWithNewLine;
+}
+
+void SpanC::SetNewLine()
+{
+  m_endsWithNewLine = true;
+}
+
+void SpanC::SetRowTotalWidthInch(double width)
+{
+  m_rowTotalWidthInch = width;
+}
diff --git a/src/lib/preprocess/SvgSpan.h b/src/lib/preprocess/SvgSpan.h
new file mode 100644
index 0000000..f8de70d
--- /dev/null
+++ b/src/lib/preprocess/SvgSpan.h
@@ -0,0 +1,51 @@
+#ifndef _SVG_SPAN_H_INCLUDED_
+#define _SVG_SPAN_H_INCLUDED_
+
+
+#include <string>
+#include <vector>
+
+
+namespace libvisio
+{
+class VSDOpenSpanOutputElement;
+}
+
+
+/**
+ * Represents an SVG text span and stores its properties required for laying out/wrapping the text
+ * within the parent text box.
+ */
+class SpanC
+{
+public:
+  SpanC(
+    const libvisio::VSDOpenSpanOutputElement *pSpan, const std::wstring &text,
+    double offsetInch, double widthInch, double lastCharWidthInch, bool hasNewLine);
+
+  const libvisio::VSDOpenSpanOutputElement *GetSpan() const;
+  const std::wstring GetText(bool exclTrailWhiteSpace = true) const;
+  double GetSpanOffsetInch() const;
+  double GetSpanWidthInch(bool exclTrailWhiteSpace = true) const;
+  double GetRowTotalWidthInch() const;
+  bool EndsWithNewLine() const;
+  void SetNewLine();
+  void SetRowTotalWidthInch(double width);
+
+private:
+  const libvisio::VSDOpenSpanOutputElement *m_pSpan; ///< the original span element
+  std::wstring m_text; ///< span text (UTF-8 encoded)
+  double m_offsetInch; ///< horizontal offset of the span counted from the end of the previous span
+  double m_widthInch; ///< width of the span in inches
+  double m_lastCharWidthInch; ///< width in inches of the last span character
+  double m_rowTotalWidthInch; ///< width of all spans in inches on the same row of text
+  bool m_endsWithNewLine; ///< indicates a new line after this span
+};
+
+
+typedef std::vector<SpanC> SpansTp;
+typedef SpansTp::iterator SpansItTp;
+typedef SpansTp::const_iterator SpansContItTp;
+
+
+#endif // _SVG_SPAN_H_INCLUDED_H_
diff --git a/src/lib/preprocess/SvgTextObject.cpp b/src/lib/preprocess/SvgTextObject.cpp
new file mode 100644
index 0000000..784f709
--- /dev/null
+++ b/src/lib/preprocess/SvgTextObject.cpp
@@ -0,0 +1,473 @@
+#include "librevenge/SvgConstants.h"
+#include "SvgDC.h"
+#include "SvgFont.h"
+#include "SvgTextObject.h"
+#include "SvgUtils.h"
+#include "VSDOutputElementList.h"
+
+#include <assert.h>
+
+using namespace librevenge;
+using namespace libvisio;
+using namespace std;
+using namespace svgconstants;
+
+
+SvgTextObjectC::SvgTextObjectC(VSDStartTextObjectOutputElement *pStartText, SvgDC &dc)
+  : m_dc(dc),
+    m_pCurrentFont(NULL),
+    m_stage(PS_NONE),
+    m_firstLineOffsetYInch(0.0),
+    m_currentLineYInch(0.0),
+    m_textLeftBoundInch(DBL_MAX),
+    m_textRightBoundInch(DBL_MIN)
+{
+  RVNGPropertyList &props = pStartText->GetPropertyList();
+
+  m_origXInch = props[PROP_SVG_X] ? SvgUtilsC::GetInchValue(*props[PROP_SVG_X]) : 0.0;
+  m_origYInch = props[PROP_SVG_Y] ? SvgUtilsC::GetInchValue(*props[PROP_SVG_Y]) : 0.0;
+  m_widthInch = props[PROP_SVG_WIDTH] ? SvgUtilsC::GetInchValue(*props[PROP_SVG_WIDTH]) : 0.0;
+  m_heightInch = props[PROP_SVG_HEIGHT] ? SvgUtilsC::GetInchValue(*props[PROP_SVG_HEIGHT]) : 0.0;
+
+  m_padLeftInch = props[PROP_FO_PADDING_LEFT]
+                  ? SvgUtilsC::GetInchValue(*props[PROP_FO_PADDING_LEFT]) : 0.0;
+
+  m_padRightInch = props[PROP_FO_PADDING_RIGHT]
+                   ? SvgUtilsC::GetInchValue(*props[PROP_FO_PADDING_RIGHT]) : 0.0;
+
+  m_padTopInch = props[PROP_FO_PADDING_TOP]
+                 ? SvgUtilsC::GetInchValue(*props[PROP_FO_PADDING_TOP]) : 0.0;
+
+  m_padBottomInch = props[PROP_FO_PADDING_BOTTOM]
+                    ? SvgUtilsC::GetInchValue(*props[PROP_FO_PADDING_BOTTOM]) : 0.0;
+
+  m_verticalAlign = props[PROP_DRAW_TEXTAREA_VERTICAL_ALIGN] ?
+                    props[PROP_DRAW_TEXTAREA_VERTICAL_ALIGN]->getStr().cstr() : PVAL_DRAW_VERTICAL_ALIGN_MIDDLE;
+
+  m_bkgColor = props[PROP_FO_BACKGROUND_COLOR]
+               ? props[PROP_FO_BACKGROUND_COLOR]->getStr().cstr() : "";
+}
+
+SvgDC &SvgTextObjectC::GetDC()
+{
+  return m_dc;
+}
+
+SvgTextObjectC::ProcessingStageE SvgTextObjectC::GetProcessingStage() const
+{
+  return m_stage;
+}
+
+void SvgTextObjectC::StartCalculationStage()
+{
+  if (m_stage == PS_NONE)
+  {
+    m_stage = PS_CALCULATE;
+    m_currentLineYInch = 0.0;
+    m_firstLineOffsetYInch = 0.0;
+  }
+}
+
+void SvgTextObjectC::StartLayoutStage()
+{
+  if (m_stage == PS_CALCULATE)
+  {
+    m_currentLineYInch = GetCurrentTextBoundTopInch() + m_firstLineOffsetYInch;
+    m_stage = PS_LAYOUT;
+  }
+}
+
+double SvgTextObjectC::GetOrigXInch() const
+{
+  return m_origXInch;
+}
+
+double SvgTextObjectC::GetOrigYInch() const
+{
+  return m_origYInch;
+}
+
+double SvgTextObjectC::GetWidthInch() const
+{
+  return m_widthInch;
+}
+
+double SvgTextObjectC::GetHeightInch() const
+{
+  return m_heightInch;
+}
+
+double SvgTextObjectC::GetPaddingLeftInch() const
+{
+  return m_padLeftInch;
+}
+
+double SvgTextObjectC::GetPaddingRightInch() const
+{
+  return m_padRightInch;
+}
+
+double SvgTextObjectC::GetPaddingTopInch() const
+{
+  return m_padTopInch;
+}
+
+double SvgTextObjectC::GetPaddingBottomInch() const
+{
+  return m_padBottomInch;
+}
+
+double SvgTextObjectC::GetCurrentLineYInch() const
+{
+  return m_currentLineYInch;
+}
+
+double SvgTextObjectC::RowOfTextAdded()
+{
+  double fontHeightInch = GetCurrentFont()->GetHeightInch();
+  double lineHeightInch = m_currentParagraph.GetLineHeightInch(fontHeightInch);
+
+  m_currentLineYInch += lineHeightInch;
+
+  if (m_stage == PS_CALCULATE && m_firstLineOffsetYInch == 0.0)
+  {
+    m_firstLineOffsetYInch = fontHeightInch
+                             * (m_dc.GetFontBaseLineHeightRatio(GetCurrentFont()->GetId()))
+                             + (lineHeightInch - fontHeightInch) / 2.0;
+  }
+
+  return m_currentLineYInch;
+}
+
+void SvgTextObjectC::OpenParagraph(const VSDOpenParagraphOutputElement *pOpenParagraph)
+{
+  m_currentParagraph.Reset(pOpenParagraph);
+}
+
+void SvgTextObjectC::OpenUnorderedList(const VSDOpenUnorderedListLevelOutputElement *pList)
+{
+  const RVNGPropertyList &props = pList->GetPropertyList();
+
+  m_currentBulletChar = props[PROP_TEXT_BULLET_CHAR]
+                        ? SvgUtilsC::StrToWstr(props[PROP_TEXT_BULLET_CHAR]->getStr().cstr()) : L"";
+}
+
+void SvgTextObjectC::OpenListElement(const VSDOpenListElementOutputElement *pOpenListElem)
+{
+  m_currentParagraph.Reset(pOpenListElem);
+}
+
+void SvgTextObjectC::OpenSpan(const VSDOpenSpanOutputElement *pOpenSpan)
+{
+  const RVNGPropertyList &props = pOpenSpan->GetPropertyList();
+
+  double fontSizeInch = props[PROP_FO_FONT_SIZE]
+                        ? SvgUtilsC::GetInchValue(*props[PROP_FO_FONT_SIZE]) : 12.0 * POINT_SIZE_INCH;
+
+  unsigned int fontWeight =
+    props[PROP_FO_FONT_WEIGHT]
+    && strcmp(props[PROP_FO_FONT_WEIGHT]->getStr().cstr(), PVAL_FO_FONT_WEIGHT_BOLD) == 0
+    ? FW_BOLD : FW_NORMAL;
+
+  bool isItalic =
+    props[PROP_FO_FONT_STYLE]
+    && strcmp(props[PROP_FO_FONT_STYLE]->getStr().cstr(), PVAL_FO_FONT_STYLE_ITALIC) == 0;
+
+  string fontName = props[PROP_STYLE_FONT_NAME]
+                    ? props[PROP_STYLE_FONT_NAME]->getStr().cstr() : "Arial";
+
+  m_pCurrentFont = m_dc.GetFont(fontSizeInch, fontWeight, isItalic, fontName);
+  m_currentParagraph.AddSpan(pOpenSpan);
+
+  if (props[PROP_FO_COLOR])
+  {
+    // addresses the issue when the text box background is wrongly indicated as 'filled'
+    if (!m_bkgColor.empty() && m_bkgColor == props[PROP_FO_COLOR]->getStr().cstr())
+    {
+      m_bkgColor.clear(); // do not fill the background as (some) text has the same color
+    }
+  }
+}
+
+void SvgTextObjectC::InsertText(const VSDInsertTextOutputElement *pInsertText)
+{
+  string text = pInsertText->GetText().cstr();
+  wstring wtext = SvgUtilsC::StrToWstr(text);
+  double curExtent = m_currentParagraph.GetCurrentTextExtentInch();
+  ParagraphC::TextExtentsTp textExtents;
+
+  m_dc.GetTextPartialExtents(wtext, GetCurrentFont()->GetId(), curExtent, textExtents);
+  m_currentParagraph.AddText(wtext, textExtents);
+}
+
+void SvgTextObjectC::InsertTab(const VSDInsertTabOutputElement *pInsertTab)
+{
+  ParagraphC::TextExtentsTp textExtents;
+  double curExtent = m_currentParagraph.GetCurrentTextExtentInch();
+
+  textExtents.push_back(m_dc.GetTabCharExtent(GetCurrentFont()->GetId(), curExtent));
+  m_currentParagraph.AddText(L"\t", textExtents);
+}
+
+const string &SvgTextObjectC::GetCurrentHorizontalAlignment() const
+{
+  return m_currentParagraph.GetHorizontalAlignment();
+}
+
+const SvgFontC *SvgTextObjectC::GetCurrentFont() const
+{
+  if (m_pCurrentFont == NULL)
+  {
+    m_pCurrentFont = m_dc.GetFont(12.0 * POINT_SIZE_INCH, FW_NORMAL, false, "Arial"); // default font if not yet defined
+  }
+
+  return m_pCurrentFont;
+}
+
+const string &SvgTextObjectC::GetBackgroundColor() const
+{
+  return m_bkgColor;
+}
+
+void SvgTextObjectC::SetCurrentTextLeftBoundInch(double x)
+{
+  if (x < m_textLeftBoundInch)
+  {
+    m_textLeftBoundInch = x;
+  }
+}
+
+void SvgTextObjectC::SetCurrentTextRightBoundInch(double x)
+{
+  if (x > m_textRightBoundInch)
+  {
+    m_textRightBoundInch = x;
+  }
+}
+
+void SvgTextObjectC::GetCurrentTextBoundsInch(
+  double &x1, double &y1, double &x2, double &y2) const
+{
+  x1 = m_textLeftBoundInch;
+  x2 = m_textRightBoundInch;
+  y1 = GetCurrentTextBoundTopInch();
+  y2 = y1 + GetCurrentLineYInch();
+}
+
+double SvgTextObjectC::GetCurrentTextBoundTopInch() const
+{
+  if (m_verticalAlign.compare(PVAL_DRAW_VERTICAL_ALIGN_TOP) == 0)
+  {
+    return GetOrigYInch() + m_padTopInch;
+  }
+
+  if (m_verticalAlign.compare(PVAL_DRAW_VERTICAL_ALIGN_BOTTOM) == 0)
+  {
+    return GetOrigYInch() + (GetHeightInch() - GetCurrentLineYInch()) - m_padBottomInch;
+  }
+
+  return GetOrigYInch() + (GetHeightInch() - GetCurrentLineYInch()) / 2.0;
+}
+
+wstring SvgTextObjectC::GetCurrentBulletCharacter() const
+{
+  return m_currentBulletChar;
+}
+
+/**
+ * Takes the currently stored paragraph text and its partial extents and breaks it down into spans
+ * while adding line breaks when needed, so that each span fits in this text box width.
+ *
+ * @param spans (out)
+ *        a list to be filled (appended) by calculated spans of text
+ */
+void SvgTextObjectC::CalculateCurrentParagraphSpans(SpansTp &spans)
+{
+  const wstring &text = m_currentParagraph.GetText();
+  const ParagraphC::TextExtentsTp &extents = m_currentParagraph.GetTextExtents();
+  const ParagraphC::SpanMarksTp &spanMarks = m_currentParagraph.GetSpanMarks();
+
+  if (text.size() > 0)
+  {
+    assert(text.size() == extents.size());
+    assert(spanMarks.size() > 0);
+    assert(spanMarks.begin()->first == 0);
+
+    ParagraphC::SpanMarksConstItTp spanMarkIt = spanMarks.begin();
+    const VSDOpenSpanOutputElement *pCurOpenSpan = spanMarks.begin()->second;
+    spanMarkIt++;
+
+    double leftMargInch = m_currentParagraph.GetMarginLeftInch();
+    double rightMargInch = m_currentParagraph.GetMarginRightInch();
+
+    double rowWidthInch = m_widthInch - m_padLeftInch - m_padRightInch
+                          - leftMargInch - rightMargInch;// + 0.5 / 72.0; // @TODO Revise: 0.5pt correction - empirical only!
+
+    double curSpanIndentInch = leftMargInch; // relative indent of the current (not-yet-written) span
+    double curRowLeftExtentInch = 0; // left edge of the bounding box mapped to 1-D contiguous coordinate
+    double lastNonTabCharExtentInch = 0; // extent of the last row character that was not tab
+    int lastWhiteIdx = -1; // white-space or hyphen break candidate index
+    bool whiteSpanEnd = false; // the last row span ended with white space
+    wstring curSpanText;
+
+    for (unsigned int i = 0, charCnt = text.size(); i < charCnt; i++)
+    {
+      bool addCurChar = true;
+      int createNewSpan = 0; // <1 - do not create, 1 - without new line, >1 - with new line
+      const VSDOpenSpanOutputElement *pNextOpenSpan = pCurOpenSpan;
+
+      if (spanMarkIt != spanMarks.end() && spanMarkIt->first == i) // new span becomes active
+      {
+        if (curSpanText.size() > 0) // the text is not empty (shall always be true)
+        {
+          createNewSpan = 1;
+        }
+
+        pNextOpenSpan = spanMarkIt->second;
+        spanMarkIt++; // advance to the next span to match in subsequent iterations
+      }
+
+      if (text[i] == L'\t')
+      {
+        addCurChar = false; // do not add tabs in the resulting text
+
+        if (curSpanText.size() == 0) // row-leading tab
+        {
+          curSpanIndentInch = leftMargInch + extents[i] - lastNonTabCharExtentInch;
+        }
+        else // row-interleaved tab
+        {
+          createNewSpan = 1;
+        }
+      }
+      else if (text[i] == 0x2028) // unicode line separator
+      {
+        addCurChar = false;
+        createNewSpan = 2;
+        curRowLeftExtentInch = extents[i];
+        lastNonTabCharExtentInch = extents[i];
+      }
+      else // non-tab character
+      {
+        lastNonTabCharExtentInch = extents[i];
+      }
+
+      if (createNewSpan > 0)
+      {
+        AddSpan(
+          spans, pCurOpenSpan, curSpanText, curSpanIndentInch, i - curSpanText.size(), i - 1,
+          createNewSpan > 1);
+
+        curSpanIndentInch =
+          text[i] == L'\t' ? leftMargInch + extents[i] - extents[i - 1] : leftMargInch;
+
+        wchar_t lastChar = curSpanText[curSpanText.size() - 1];
+        whiteSpanEnd = !!iswspace(lastChar) || lastChar == L'-';
+        lastWhiteIdx = -1;
+        curSpanText.clear();
+      }
+
+      if (curSpanText.size() > 0 // must not be empty, i.e. at least one character per row even it does not fit entirely
+          && extents[i] > curRowLeftExtentInch + rowWidthInch) // available space exceeded by the current character
+      {
+        unsigned int fitCharCnt; // number of characters to fit in the rest of the available space
+
+        if (iswspace(text[i])) // a white space exceeded the boundary
+        {
+          fitCharCnt = curSpanText.size();
+          addCurChar = false; // do not include the white space in the new row
+        }
+        else if (lastWhiteIdx >= 0)
+        {
+          fitCharCnt = lastWhiteIdx + 1; // up to last white-space character including
+        }
+        else if (whiteSpanEnd) // break after white space end of the last span
+        {
+          fitCharCnt = 0;
+        }
+        else // no text-break candidate exist
+        {
+          fitCharCnt = curSpanText.size(); // up to last fitting character
+        }
+
+        if (fitCharCnt == 0) // the row breaks right after the previous span
+        {
+          curRowLeftExtentInch = extents[i - curSpanText.size() - 1]; // update the row start position
+          spans[spans.size() - 1].SetNewLine(); // update the previous span
+        }
+        else
+        {
+          AddSpan(
+            spans, pCurOpenSpan, curSpanText.substr(0, fitCharCnt), curSpanIndentInch,
+            i - curSpanText.size(), i - curSpanText.size() + fitCharCnt - 1, true);
+
+          curRowLeftExtentInch = extents[i - curSpanText.size() + fitCharCnt - 1]; // adjust the new beginning of the current row
+          curSpanText = curSpanText.substr(fitCharCnt); // go on with the remaining part
+
+          if (lastWhiteIdx >= 0)
+          {
+            lastWhiteIdx -= fitCharCnt;
+          }
+        }
+
+        curSpanIndentInch = leftMargInch;
+        lastNonTabCharExtentInch = extents[i];
+      }
+      else // current character still fits in
+      {
+        if (iswspace(text[i]) || text[i] == L'-')
+        {
+          lastWhiteIdx = curSpanText.size();
+        }
+      }
+
+      if (addCurChar)
+      {
+        curSpanText += text[i];
+      }
+
+      pCurOpenSpan = pNextOpenSpan;
+    }
+
+    if (curSpanText.size() > 0) // the rest of the text not yet included
+    {
+      AddSpan(
+        spans, pCurOpenSpan, curSpanText, curSpanIndentInch,
+        text.size() - curSpanText.size(), text.size() - 1, true);
+    }
+
+    if (spans.size() > 0)
+    {
+      spans[spans.size() - 1].SetNewLine(); // assure the line break after the last span
+
+      double totalWidthInch = 0.0; // set sum of spans widths to first spans on each row (used for hor. alignment)
+
+      for (int i = spans.size() - 1; i >= 0; i--)
+      {
+        totalWidthInch += spans[i].GetSpanOffsetInch() + spans[i].GetSpanWidthInch();
+
+        if (i == 0 || spans[i - 1].EndsWithNewLine())
+        {
+          spans[i].SetRowTotalWidthInch(totalWidthInch);
+          totalWidthInch = 0.0;
+        }
+      }
+    }
+  }
+}
+
+void SvgTextObjectC::AddSpan(
+  SpansTp &spans, const VSDOpenSpanOutputElement *pSvgSpan, const wstring &text,
+  double offsetInch, int firstCharIdx, int lastCharIdx, bool endWithNewLine)
+{
+  const ParagraphC::TextExtentsTp &extents = m_currentParagraph.GetTextExtents();
+  double leftExtentInch = firstCharIdx > 0 ? extents[firstCharIdx - 1] : 0;
+  double rightExtentInch = extents[lastCharIdx];
+
+  double lastCharExtentInch =
+    lastCharIdx > 0 ? extents[lastCharIdx] - extents[lastCharIdx - 1] : extents[lastCharIdx];
+
+  spans.push_back(SpanC(
+                    pSvgSpan, text, offsetInch, rightExtentInch - leftExtentInch, lastCharExtentInch,
+                    endWithNewLine));
+}
diff --git a/src/lib/preprocess/SvgTextObject.h b/src/lib/preprocess/SvgTextObject.h
new file mode 100644
index 0000000..3fccfeb
--- /dev/null
+++ b/src/lib/preprocess/SvgTextObject.h
@@ -0,0 +1,109 @@
+#ifndef _SVG_TEXT_OBJECT_H_INCLUDED_
+#define _SVG_TEXT_OBJECT_H_INCLUDED_
+
+
+#include "SvgParagraph.h"
+#include "SvgSpan.h"
+
+#include <string>
+
+
+namespace libvisio
+{
+class VSDStartTextObjectOutputElement;
+class VSDOpenParagraphOutputElement;
+class VSDOpenUnorderedListLevelOutputElement;
+class VSDOpenListElementOutputElement;
+class VSDOpenSpanOutputElement;
+class VSDInsertTextOutputElement;
+class VSDInsertTabOutputElement;
+}
+
+class SvgDC;
+class SvgFontC;
+
+
+/**
+ * Represents an SVG text object and provides methods that reflect its particular structure and
+ * calculate additional properties of contained elements that are required for proper rendering.
+ */
+class SvgTextObjectC
+{
+public:
+  enum ProcessingStageE
+  {
+    PS_NONE,
+    PS_CALCULATE,
+    PS_LAYOUT
+  };
+
+  SvgTextObjectC(libvisio::VSDStartTextObjectOutputElement *pStartText, SvgDC &dc);
+
+  SvgDC &GetDC();
+  ProcessingStageE GetProcessingStage() const;
+
+  double GetOrigXInch() const;
+  double GetOrigYInch() const;
+  double GetWidthInch() const;
+  double GetHeightInch() const;
+  double GetPaddingLeftInch() const;
+  double GetPaddingRightInch() const;
+  double GetPaddingTopInch() const;
+  double GetPaddingBottomInch() const;
+
+  double GetCurrentLineYInch() const;
+  double RowOfTextAdded();
+
+  const std::string &GetCurrentHorizontalAlignment() const;
+  const SvgFontC *GetCurrentFont() const;
+
+  const std::string &GetBackgroundColor() const;
+  void SetCurrentTextLeftBoundInch(double x);
+  void SetCurrentTextRightBoundInch(double x);
+  void GetCurrentTextBoundsInch(double &x1, double &y1, double &x2, double &y2) const;
+  double GetCurrentTextBoundTopInch() const;
+  std::wstring GetCurrentBulletCharacter() const;
+
+  void OpenParagraph(const libvisio::VSDOpenParagraphOutputElement *pOpenParagraph);
+  void OpenUnorderedList(const libvisio::VSDOpenUnorderedListLevelOutputElement *pList);
+  void OpenListElement(const libvisio::VSDOpenListElementOutputElement *pOpenListElem);
+  void OpenSpan(const libvisio::VSDOpenSpanOutputElement *pOpenSpan);
+  void InsertText(const libvisio::VSDInsertTextOutputElement *pInsertText);
+  void InsertTab(const libvisio::VSDInsertTabOutputElement *pInsertTab);
+  void CalculateCurrentParagraphSpans(SpansTp &spans);
+
+  void StartCalculationStage();
+  void StartLayoutStage();
+
+private:
+  void AddSpan(
+    SpansTp &spans, const libvisio::VSDOpenSpanOutputElement *pSvgSpan, const std::wstring &text,
+    double offsetInch, int firstCharIdx, int lastCharIdx, bool endWithNewLine);
+
+  double m_origXInch; ///< X coordinate of the top left corner in inches
+  double m_origYInch; ///< Y coordinate of the top left corner in inches
+  double m_widthInch; ///< box width in inches
+  double m_heightInch; ///< box height in inches
+  double m_padLeftInch; ///< left padding in inches
+  double m_padRightInch; ///< right padding in inches
+  double m_padTopInch; ///<< top padding in inches
+  double m_padBottomInch; ///< bottom padding in inches
+
+  SvgDC &m_dc; ///< device context to use for text size-related calculations
+  mutable const SvgFontC *m_pCurrentFont; ///< font that applies to the currently processed text
+
+  ProcessingStageE m_stage; ///< stage of processing of textbox contents
+  double m_firstLineOffsetYInch; ///< vertical offset of the first (base)line in the text box
+  double m_currentLineYInch; ///< vertical position of the current line of text
+  std::string m_verticalAlign; ///< vertical alignment of the text box
+
+  double m_textLeftBoundInch; ///< the left bound of the left-most line of the text
+  double m_textRightBoundInch; ///< the right bound of the right-most line of the text
+  std::string m_bkgColor; ///< text box background color or empty string for transparent background
+
+  ParagraphC m_currentParagraph; ///< paragraph currently being processed
+  std::wstring m_currentBulletChar; ///< the character to use for the subsequest list item bullet
+};
+
+
+#endif // _SVG_TEXT_OBJECT_H_INCLUDED_
diff --git a/src/lib/preprocess/SvgUtils.cpp b/src/lib/preprocess/SvgUtils.cpp
new file mode 100644
index 0000000..1883903
--- /dev/null
+++ b/src/lib/preprocess/SvgUtils.cpp
@@ -0,0 +1,61 @@
+#include "SvgUtils.h"
+#include <librevenge/librevenge.h>
+
+using namespace librevenge;
+using namespace std;
+
+
+int SvgUtilsC::Round(double x)
+{
+  if (x < 0.0)
+  {
+    return static_cast<int>(x - 0.5);
+  }
+
+  return static_cast<int>(x + 0.5);
+}
+
+double SvgUtilsC::GetInchValue(const RVNGProperty &prop)
+{
+  double value = prop.getDouble();
+
+  switch (prop.getUnit())
+  {
+  case RVNG_GENERIC: // assume inch
+  case RVNG_INCH:
+    return value;
+
+  case RVNG_POINT:
+    return value / 72.0;
+
+  case RVNG_TWIP:
+    return value / 1440.0;
+
+  default :
+    return value; // non-conversible
+  }
+}
+
+wstring SvgUtilsC::StrToWstr(const string &str)
+{
+  int wCharCnt = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, NULL, 0);
+  wchar_t *wstr = new wchar_t[wCharCnt];
+
+  MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, wstr, wCharCnt);
+  wstring outWstr = wstr;
+
+  delete wstr;
+  return outWstr;
+}
+
+string SvgUtilsC::WstrToStr(const wstring &wstr)
+{
+  int charCnt = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL);
+  char *str = new char[charCnt];
+
+  WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &str[0], charCnt, NULL, NULL);
+  string outStr = str;
+
+  delete str;
+  return outStr;
+}
diff --git a/src/lib/preprocess/SvgUtils.h b/src/lib/preprocess/SvgUtils.h
new file mode 100644
index 0000000..cde2096
--- /dev/null
+++ b/src/lib/preprocess/SvgUtils.h
@@ -0,0 +1,28 @@
+#ifndef _SVG_UTILS_H_INCLUDED_
+#define _SVG_UTILS_H_INCLUDED_
+
+
+#include <string>
+#include <Windows.h>
+
+
+namespace librevenge
+{
+class RVNGProperty;
+}
+
+
+class SvgUtilsC
+{
+public:
+  static int Round(double x);
+  static double GetInchValue(const librevenge::RVNGProperty &prop);
+  static std::wstring StrToWstr(const std::string &str);
+  static std::string WstrToStr(const std::wstring &wstr);
+
+private:
+  SvgUtilsC();
+};
+
+
+#endif // _SVG_UTILS_H_INCLUDED_
diff --git a/src/lib/preprocess/VsdElementListPreprocessor.cpp b/src/lib/preprocess/VsdElementListPreprocessor.cpp
new file mode 100644
index 0000000..d40d148
--- /dev/null
+++ b/src/lib/preprocess/VsdElementListPreprocessor.cpp
@@ -0,0 +1,521 @@
+#include "librevenge/SvgConstants.h"
+#include "SvgDC.h"
+#include "SvgUtils.h"
+#include "SvgTextObject.h"
+#include "VsdElementListPreprocessor.h"
+
+#include <assert.h>
+
+using namespace librevenge;
+using namespace libvisio;
+using namespace std;
+using namespace svgconstants;
+
+
+void VsdElementListPreprocessorC::Process(
+  const ElementListTp &elements, ElementListTp &outElements)
+{
+  SvgDC &dc = GetSystemDC();
+
+  for (ElementListConstItTp it = elements.begin(); it != elements.end(); it++)
+  {
+    ElementListConstItTp oldIt = it;
+    it = ProcessTextObject(it, elements.end(), outElements, dc);
+
+    if (it == oldIt)
+    {
+      outElements.push_back((*it)->clone());
+    }
+    else if (it == elements.end())
+    {
+      break;
+    }
+  }
+}
+
+VsdElementListPreprocessorC::ElementListConstItTp VsdElementListPreprocessorC::ProcessTextObject(
+  ElementListConstItTp startIt, ElementListConstItTp endIt, ElementListTp &outElements, SvgDC &dc)
+{
+  ElementListConstItTp it = startIt;
+
+  VSDStartTextObjectOutputElement *pStartText =
+    dynamic_cast<VSDStartTextObjectOutputElement *>(*it);
+
+  if (pStartText == NULL) // not StartTextObject element
+  {
+    return it;
+  }
+
+  pStartText = static_cast<VSDStartTextObjectOutputElement *>((*it)->clone()); // work with the copy!
+  SvgTextObjectC textObjContext(pStartText, dc);
+  it++; // move to the next element
+
+  // 1st pass
+  ElementListTp elementsPass1;
+  textObjContext.StartCalculationStage();
+  it = ProcessTextObjectOnePass(it, endIt, elementsPass1, &textObjContext);
+
+  if (!textObjContext.GetBackgroundColor().empty())
+  {
+    double x1, y1, x2, y2;
+    textObjContext.GetCurrentTextBoundsInch(x1, y1, x2, y2);
+
+    outElements.push_back(CreateStyleOutput(textObjContext.GetBackgroundColor()));
+    outElements.push_back(CreateRectPathOutput(x1, y1, x2, y2));
+  }
+
+  outElements.push_back(pStartText);
+
+  // 2nd pass
+  textObjContext.StartLayoutStage();
+
+  ProcessTextObjectOnePass(
+    elementsPass1.begin(), elementsPass1.end(), outElements, &textObjContext);
+
+  return it;
+}
+
+VsdElementListPreprocessorC::ElementListConstItTp
+VsdElementListPreprocessorC::ProcessTextObjectOnePass(
+  ElementListConstItTp startIt, ElementListConstItTp endIt,
+  ElementListTp &outElements, SvgTextObjectC *pTextObject)
+{
+  assert(pTextObject);
+
+  ElementListConstItTp it;
+
+  for (it = startIt; it != endIt; it++)
+  {
+    VSDEndTextObjectOutputElement *pEndTextObject =
+      dynamic_cast<VSDEndTextObjectOutputElement *>(*it);
+
+    if (pEndTextObject != NULL) // EndTextObject element found
+    {
+      outElements.push_back(pEndTextObject->clone()); // make a copy!
+      break;
+    }
+
+    ElementListConstItTp oldIt = it;
+    it = ProcessParagraph(it, endIt, outElements, pTextObject);
+
+    if (it == oldIt)
+    {
+      it = ProcessUnorderedList(it, endIt, outElements, pTextObject);
+
+      if (it == oldIt)
+      {
+        outElements.push_back((*it)->clone());
+      }
+    }
+
+    if (it == endIt)
+    {
+      break;
+    }
+  }
+
+  return it;
+}
+
+VsdElementListPreprocessorC::ElementListConstItTp VsdElementListPreprocessorC::ProcessParagraph(
+  ElementListConstItTp startIt, ElementListConstItTp endIt, ElementListTp &outElements,
+  SvgTextObjectC *pTextObject)
+{
+  assert(pTextObject);
+
+  ElementListConstItTp it = startIt;
+
+  VSDOpenParagraphOutputElement *pOpenParagraph =
+    dynamic_cast<VSDOpenParagraphOutputElement *>(*it);
+
+  if (pOpenParagraph == NULL)
+  {
+    return it; // not OpenParagraph element
+  }
+
+  pOpenParagraph = static_cast<VSDOpenParagraphOutputElement *>((*it)->clone()); // work with the copy!
+  outElements.push_back(pOpenParagraph);
+
+  SvgTextObjectC::ProcessingStageE stage = pTextObject->GetProcessingStage();
+
+  if (stage == SvgTextObjectC::PS_CALCULATE)
+  {
+    pTextObject->OpenParagraph(pOpenParagraph);
+  }
+
+  it++; // move to the next element
+
+  VSDCloseParagraphOutputElement *pCloseParagraph = NULL;
+
+  for (; it != endIt; it++)
+  {
+    pCloseParagraph = dynamic_cast<VSDCloseParagraphOutputElement *>(*it);
+
+    if (pCloseParagraph != NULL)
+    {
+      break;
+    }
+
+    ElementListConstItTp oldIt = it;
+    it = ProcessSpan(it, endIt, outElements, pTextObject);
+
+    if (it == oldIt)
+    {
+      outElements.push_back((*it)->clone());
+    }
+    else if (it == endIt)
+    {
+      break;
+    }
+  }
+
+  if (stage == SvgTextObjectC::PS_CALCULATE)
+  {
+    SpansTp spans;
+    pTextObject->CalculateCurrentParagraphSpans(spans);
+    GenerateSpans(spans, outElements, pTextObject);
+  }
+
+  if (pCloseParagraph != NULL)
+  {
+    outElements.push_back(pCloseParagraph->clone()); // make a copy!
+  }
+
+  return it;
+}
+
+VsdElementListPreprocessorC::ElementListConstItTp VsdElementListPreprocessorC::ProcessUnorderedList(
+  ElementListConstItTp startIt, ElementListConstItTp endIt,
+  ElementListTp &outElements, SvgTextObjectC *pTextObject)
+{
+  assert(pTextObject);
+
+  ElementListConstItTp it = startIt;
+
+  VSDOpenUnorderedListLevelOutputElement *pOpenList =
+    dynamic_cast<VSDOpenUnorderedListLevelOutputElement *>(*it);
+
+  if (pOpenList == NULL)
+  {
+    return it; // not OpenUnorderedList element
+  }
+
+  pOpenList = static_cast<VSDOpenUnorderedListLevelOutputElement *>((*it)->clone()); // work with the copy!
+  outElements.push_back(pOpenList);
+
+  if (pTextObject->GetProcessingStage() == SvgTextObjectC::PS_CALCULATE)
+  {
+    pTextObject->OpenUnorderedList(pOpenList);
+  }
+
+  it++; // move to the next element
+
+  for (; it != endIt; it++)
+  {
+    VSDCloseUnorderedListLevelOutputElement *pCloseList =
+      dynamic_cast<VSDCloseUnorderedListLevelOutputElement *>(*it);
+
+    if (pCloseList != NULL)
+    {
+      outElements.push_back(pCloseList->clone()); // make a copy!
+      break;
+    }
+
+    ElementListConstItTp oldIt = it;
+    it = ProcessListElement(it, endIt, outElements, pTextObject);
+
+    if (it == oldIt)
+    {
+      outElements.push_back((*it)->clone());
+    }
+    else if (it == endIt)
+    {
+      break;
+    }
+  }
+
+  return it;
+}
+
+VsdElementListPreprocessorC::ElementListConstItTp VsdElementListPreprocessorC::ProcessListElement(
+  ElementListConstItTp startIt, ElementListConstItTp endIt,
+  ElementListTp &outElements, SvgTextObjectC *pTextObject)
+{
+  assert(pTextObject);
+
+  ElementListConstItTp it = startIt;
+
+  VSDOpenListElementOutputElement *pOpenListElem =
+    dynamic_cast<VSDOpenListElementOutputElement *>(*it);
+
+  if (pOpenListElem == NULL)
+  {
+    return it; // not OpenListElement element
+  }
+
+  pOpenListElem = static_cast<VSDOpenListElementOutputElement *>((*it)->clone()); // work with the copy!
+  outElements.push_back(pOpenListElem);
+
+  SvgTextObjectC::ProcessingStageE stage = pTextObject->GetProcessingStage();
+
+  if (stage == SvgTextObjectC::PS_CALCULATE)
+  {
+    pTextObject->OpenListElement(pOpenListElem);
+  }
+
+  it++; // move to the next element
+
+  VSDCloseListElementOutputElement *pCloseListElem = NULL;
+
+  for (; it != endIt; it++)
+  {
+    pCloseListElem = dynamic_cast<VSDCloseListElementOutputElement *>(*it);
+
+    if (pCloseListElem != NULL)
+    {
+      break;
+    }
+
+    ElementListConstItTp oldIt = it;
+    it = ProcessSpan(it, endIt, outElements, pTextObject);
+
+    if (it == oldIt)
+    {
+      outElements.push_back((*it)->clone());
+    }
+    else if (it == endIt)
+    {
+      break;
+    }
+  }
+
+  if (stage == SvgTextObjectC::PS_CALCULATE)
+  {
+    SpansTp spans;
+    pTextObject->CalculateCurrentParagraphSpans(spans);
+
+    if (spans.size() > 0) // add a span for the list item bullet
+    {
+      SpanC span = spans[0];
+      double spanOffsetInch = span.GetSpanOffsetInch();
+      RVNGPropertyList &props = pOpenListElem->GetPropertyList();
+
+      double textIndentInch = props[PROP_FO_TEXT_INDENT]
+                              ? SvgUtilsC::GetInchValue(*props[PROP_FO_TEXT_INDENT]) : -spanOffsetInch;
+
+      if (textIndentInch >= 0)
+      {
+        textIndentInch = -spanOffsetInch;
+      }
+
+      spans.insert(
+        spans.begin(),
+        SpanC(span.GetSpan(), pTextObject->GetCurrentBulletCharacter(),
+              spanOffsetInch + textIndentInch, -textIndentInch, -textIndentInch, false));
+    }
+
+    GenerateSpans(spans, outElements, pTextObject);
+  }
+
+  if (pCloseListElem != NULL)
+  {
+    outElements.push_back(pCloseListElem->clone()); // make a copy!
+  }
+
+  return it;
+}
+
+VsdElementListPreprocessorC::ElementListConstItTp VsdElementListPreprocessorC::ProcessSpan(
+  ElementListConstItTp startIt, ElementListConstItTp endIt,
+  ElementListTp &outElements, SvgTextObjectC *pTextObject)
+{
+  assert(pTextObject);
+
+  ElementListConstItTp it = startIt;
+  VSDOpenSpanOutputElement *pOpenSpan = dynamic_cast<VSDOpenSpanOutputElement *>(*it);
+
+  if (pOpenSpan == NULL)
+  {
+    return it; // not OpenSpan element
+  }
+
+  SvgTextObjectC::ProcessingStageE stage = pTextObject->GetProcessingStage();
+  it++; // move to the next element
+
+  if (stage == SvgTextObjectC::PS_CALCULATE)
+  {
+    pTextObject->OpenSpan(pOpenSpan);
+
+    for (; it != endIt; it++)
+    {
+      VSDCloseSpanOutputElement *pCloseSpan = dynamic_cast<VSDCloseSpanOutputElement *>(*it);
+
+      if (pCloseSpan != NULL)
+      {
+        break;
+      }
+
+      VSDInsertTextOutputElement *pInsertText = dynamic_cast<VSDInsertTextOutputElement *>(*it);
+
+      if (pInsertText != NULL)
+      {
+        pTextObject->InsertText(pInsertText);
+      }
+
+      VSDInsertTabOutputElement *pInsertTab = dynamic_cast<VSDInsertTabOutputElement *>(*it);
+
+      if (pInsertTab != NULL)
+      {
+        pTextObject->InsertTab(pInsertTab);
+      }
+    }
+  }
+  else if (stage == SvgTextObjectC::PS_LAYOUT)
+  {
+    RVNGPropertyList &props = pOpenSpan->GetPropertyList();
+    double posYInch = SvgUtilsC::GetInchValue(*props[PROP_SVG_Y]);
+
+    posYInch += pTextObject->GetCurrentLineYInch();
+    props.insert(PROP_SVG_Y, posYInch, RVNG_INCH);
+
+    outElements.push_back(pOpenSpan->clone());
+
+    for (; it != endIt; it++)
+    {
+      VSDCloseSpanOutputElement *pCloseSpan = dynamic_cast<VSDCloseSpanOutputElement *>(*it);
+
+      if (pCloseSpan != NULL)
+      {
+        outElements.push_back((*it)->clone());
+        break;
+      }
+
+      VSDInsertTextOutputElement *pInsertText = dynamic_cast<VSDInsertTextOutputElement *>(*it);
+
+      if (pInsertText != NULL)
+      {
+        outElements.push_back((*it)->clone());
+      }
+    }
+
+  }
+
+  return it;
+}
+
+void VsdElementListPreprocessorC::GenerateSpans(
+  const SpansTp &spans, ElementListTp &outElements, SvgTextObjectC *pTextObject)
+{
+  if (spans.size() == 0)
+  {
+    // @TODO Revise: More accurate results are sometimes achieved when empty paragraph
+    // is ignorred in the result document, rather than if the a new line is added
+    pTextObject->RowOfTextAdded();
+  }
+  else
+  {
+    double posXInch = 0.0;
+    bool wasNewRow = true;
+
+    for (SpansContItTp spanIt = spans.begin(); spanIt != spans.end(); spanIt++)
+    {
+      VSDOpenSpanOutputElement *pOpenSpan = static_cast<VSDOpenSpanOutputElement *>(
+                                              const_cast<VSDOpenSpanOutputElement *>(spanIt->GetSpan())->clone()); // const_cast needed as clone() is not const (but should be)
+
+      if (wasNewRow)
+      {
+        wasNewRow = false;
+        string horAlign = pTextObject->GetCurrentHorizontalAlignment();
+
+        if (horAlign.compare(PVAL_FO_TEXT_ALIGN_LEFT) == 0)
+        {
+          posXInch = pTextObject->GetOrigXInch() + pTextObject->GetPaddingLeftInch();
+        }
+        else if (horAlign.compare(PVAL_FO_TEXT_ALIGN_RIGHT) == 0)
+        {
+          posXInch = pTextObject->GetOrigXInch() + pTextObject->GetWidthInch()
+                     - pTextObject->GetPaddingRightInch() - spanIt->GetRowTotalWidthInch();
+        }
+        else // PVAL_FO_TEXT_ALIGN_CENTER
+        {
+          posXInch = pTextObject->GetOrigXInch()
+                     + (pTextObject->GetWidthInch() - spanIt->GetRowTotalWidthInch()) / 2.0;
+        }
+
+        posXInch += spanIt->GetSpanOffsetInch();
+      }
+
+      pTextObject->SetCurrentTextLeftBoundInch(posXInch);

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list