# HG changeset patch
# User Pantxo Diribarne <pantxo.diribarne@gmail.com>
# Date 1606341660 -3600
#      Wed Nov 25 23:01:00 2020 +0100
# Node ID cbb162daaadbdda2686b04093a56328bcf7c1026
# Parent  c5ccb3d2be81e70c3cfaf7624f7706cb941defd9
Initial implamentation of a latex interpreter

* contributors.in: Add Andrej Lojdl, a former GSOC student that worked on this
  subject. Even though there is not much left of his original work,
  it served as a very useful starting point.

* base-text-renderer.[h, cc]: New enum to store symbolic
  constants for rotation angles.
  (base_text_renderer::rotate_pixels): New utility method. Code extracted
  from ft_text_renderer::render.
  (base_text_renderer::rotation_to_mode): Moved from ft_text_renderer class.
  (base_text_renderer::fix_bbox_anchor): New utility method. Code extracted
  from ft_text_renderer::text_to_pixels.

* ft_text_renderer.cc (ft_text_renderer::render,
  ft_text_renderer::text_to_pixels): Make use of base class utility functions.

* gl2ps-print.cc: New counter variable m_svg_def_index for safe inclusion of
  defs coming from dvisvgm.
  (gl2ps_renderer::get_svg_elements): New function to manipulate svg elements
  obtained from dvisvgm so as position theim properly on the figure.              (gl2ps_renderer::strlist_to_svg): If the str_list object returned from
  text_to_strlist only contains a single element with an svg element, return
  format the element with get_svg_elements and return it. Otherwise,
  use <g> and <text> elements rather than <text> and <tspan> elements which
  are not supported by Qt's svg-tiny implementation.
  (gl2ps_renderer::strlist_to_ps): If the str_list object returned from
  text_to_strlist only contains a single element with an svg element, raise
  a warning about the necessity of using -svgconvert.
  
* latex-text-renderer.h, latex-text-renderer.cc: New files to hold the
  latex_interpreter class.

* libinterp/corefcn/module.mk: Add new files to the build system.

* text-renderer.h, text-renderer.cc: New data member latex_rep to hold a
  pointer to an instance of a latex_renderer.
  (text_renderer::~text_renderer, get_extent, set_anti_aliasing, set_font,
  set_color, text_to_pixels, text_to_strlist): Duplicate action of the
  latex_rep.
  (text_renderer::latex_ok) New method to test the usability of the
  latex_renderer.

* acinclude.m4: Add QtSvg to the list of imported QT_MODULES.

* octave-svgconvert.cc: Overhaul program to make use of Qt's QSvgRenderer
  when rendering to PDF..

diff -r c5ccb3d2be81 -r cbb162daaadb doc/interpreter/contributors.in
--- a/doc/interpreter/contributors.in	Tue Nov 24 20:34:02 2020 -0500
+++ b/doc/interpreter/contributors.in	Wed Nov 25 23:01:00 2020 +0100
@@ -229,6 +229,7 @@
 David Livings
 Barbara Locsi
 Sebastien Loisel
+Andrej Lojdl
 Erik de Castro Lopo
 Massimo Lorenzin
 Emil Lucretiu
diff -r c5ccb3d2be81 -r cbb162daaadb libinterp/corefcn/base-text-renderer.cc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libinterp/corefcn/base-text-renderer.cc	Wed Nov 25 23:01:00 2020 +0100
@@ -0,0 +1,173 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2020 The Octave Project Developers
+//
+// See the file COPYRIGHT.md in the top-level directory of this
+// distribution or <https://octave.org/copyright/>.
+//
+// This file is part of Octave.
+//
+// Octave is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Octave is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Octave; see the file COPYING.  If not, see
+// <https://www.gnu.org/licenses/>.
+//
+////////////////////////////////////////////////////////////////////////
+
+#if defined (HAVE_CONFIG_H)
+#  include "config.h"
+#endif
+
+#include "base-text-renderer.h"
+
+namespace octave
+{
+  void
+  base_text_renderer::rotate_pixels (uint8NDArray& pixels, int rot_mode) const
+  {
+    switch (rot_mode)
+      {
+      case ROTATION_0:
+        break;
+
+      case ROTATION_90:
+        {
+          Array<octave_idx_type> perm (dim_vector (3, 1));
+          perm(0) = 0;
+          perm(1) = 2;
+          perm(2) = 1;
+          pixels = pixels.permute (perm);
+
+          Array<idx_vector> idx (dim_vector (3, 1));
+          idx(0) = idx_vector (':');
+          idx(1) = idx_vector (pixels.dim2 ()-1, -1, -1);
+          idx(2) = idx_vector (':');
+          pixels = uint8NDArray (pixels.index (idx));
+        }
+        break;
+
+      case ROTATION_180:
+        {
+          Array<idx_vector> idx (dim_vector (3, 1));
+          idx(0) = idx_vector (':');
+          idx(1) = idx_vector (pixels.dim2 ()-1, -1, -1);
+          idx(2) = idx_vector (pixels.dim3 ()-1, -1, -1);
+          pixels = uint8NDArray (pixels.index (idx));
+        }
+        break;
+
+      case ROTATION_270:
+        {
+          Array<octave_idx_type> perm (dim_vector (3, 1));
+          perm(0) = 0;
+          perm(1) = 2;
+          perm(2) = 1;
+          pixels = pixels.permute (perm);
+
+          Array<idx_vector> idx (dim_vector (3, 1));
+          idx(0) = idx_vector (':');
+          idx(1) = idx_vector (':');
+          idx(2) = idx_vector (pixels.dim3 ()-1, -1, -1);
+          pixels = uint8NDArray (pixels.index (idx));
+        }
+        break;
+
+      }
+  }
+
+  int
+  base_text_renderer::rotation_to_mode (double rotation) const
+  {
+    // Clip rotation to range [0, 360]
+    while (rotation < 0)
+      rotation += 360.0;
+    while (rotation > 360.0)
+      rotation -= 360.0;
+
+    if (rotation == 0.0)
+      return ROTATION_0;
+    else if (rotation == 90.0)
+      return ROTATION_90;
+    else if (rotation == 180.0)
+      return ROTATION_180;
+    else if (rotation == 270.0)
+      return ROTATION_270;
+    else
+      return ROTATION_0;
+  }
+
+  void
+  base_text_renderer::fix_bbox_anchor (Matrix& bbox, int halign,
+                                       int valign, int rot_mode,
+                                       bool handle_rotation) const
+  {
+    switch (halign)
+      {
+      case 1:
+        bbox(0) = -bbox(2)/2;
+        break;
+
+      case 2:
+        bbox(0) = -bbox(2);
+        break;
+
+      default:
+        bbox(0) = 0;
+        break;
+      }
+
+    switch (valign)
+      {
+      case 1:
+        bbox(1) = -bbox(3)/2;
+        break;
+
+      case 2:
+        bbox(1) = -bbox(3);
+        break;
+
+      case 3:
+        break;
+
+      case 4:
+        bbox(1) = -bbox(3)-bbox(1);
+        break;
+
+      default:
+        bbox(1) = 0;
+        break;
+      }
+
+    if (handle_rotation)
+      {
+        switch (rot_mode)
+          {
+          case ROTATION_90:
+            std::swap (bbox(0), bbox(1));
+            std::swap (bbox(2), bbox(3));
+            bbox(0) = -bbox(0)-bbox(2);
+            break;
+
+          case ROTATION_180:
+            bbox(0) = -bbox(0)-bbox(2);
+            bbox(1) = -bbox(1)-bbox(3);
+            break;
+
+          case ROTATION_270:
+            std::swap (bbox(0), bbox(1));
+            std::swap (bbox(2), bbox(3));
+            bbox(1) = -bbox(1)-bbox(3);
+            break;
+          }
+      }
+  }
+}
diff -r c5ccb3d2be81 -r cbb162daaadb libinterp/corefcn/base-text-renderer.h
--- a/libinterp/corefcn/base-text-renderer.h	Tue Nov 24 20:34:02 2020 -0500
+++ b/libinterp/corefcn/base-text-renderer.h	Wed Nov 25 23:01:00 2020 +0100
@@ -45,6 +45,14 @@
   {
   public:
 
+    enum
+    {
+      ROTATION_0   = 0,
+      ROTATION_90  = 1,
+      ROTATION_180 = 2,
+      ROTATION_270 = 3
+    };
+
     base_text_renderer (void) : text_processor () { }
 
     // No copying!
@@ -85,6 +93,15 @@
                      std::list<text_renderer::string>& lst,
                      Matrix& box, int halign, int valign, double rotation,
                      const caseless_str& interpreter = "tex") = 0;
+
+    void rotate_pixels (uint8NDArray& pixels, int rot_mode) const;
+
+    int rotation_to_mode (double rotation) const;
+
+    void fix_bbox_anchor (Matrix& bbox, int halign,
+                          int valign, int rot_mode,
+                          bool handle_rotation) const;
+
   };
 }
 
diff -r c5ccb3d2be81 -r cbb162daaadb libinterp/corefcn/ft-text-renderer.cc
--- a/libinterp/corefcn/ft-text-renderer.cc	Tue Nov 24 20:34:02 2020 -0500
+++ b/libinterp/corefcn/ft-text-renderer.cc	Wed Nov 25 23:01:00 2020 +0100
@@ -448,14 +448,6 @@
       MODE_RENDER = 1
     };
 
-    enum
-    {
-      ROTATION_0   = 0,
-      ROTATION_90  = 1,
-      ROTATION_180 = 2,
-      ROTATION_270 = 3
-    };
-
   public:
 
     ft_text_renderer (void)
@@ -526,8 +518,6 @@
 
   private:
 
-    int rotation_to_mode (double rotation) const;
-
     // Class to hold information about fonts and a strong
     // reference to the font objects loaded by FreeType.
 
@@ -1294,53 +1284,7 @@
       {
         elt->accept (*this);
 
-        switch (rotation)
-          {
-          case ROTATION_0:
-            break;
-
-          case ROTATION_90:
-            {
-              Array<octave_idx_type> perm (dim_vector (3, 1));
-              perm(0) = 0;
-              perm(1) = 2;
-              perm(2) = 1;
-              pixels = pixels.permute (perm);
-
-              Array<idx_vector> idx (dim_vector (3, 1));
-              idx(0) = idx_vector (':');
-              idx(1) = idx_vector (pixels.dim2 ()-1, -1, -1);
-              idx(2) = idx_vector (':');
-              pixels = uint8NDArray (pixels.index (idx));
-            }
-            break;
-
-          case ROTATION_180:
-            {
-              Array<idx_vector> idx (dim_vector (3, 1));
-              idx(0) = idx_vector (':');
-              idx(1) = idx_vector (pixels.dim2 ()-1, -1, -1);
-              idx(2) = idx_vector (pixels.dim3 ()-1, -1, -1);
-              pixels = uint8NDArray (pixels.index (idx));
-            }
-            break;
-
-          case ROTATION_270:
-            {
-              Array<octave_idx_type> perm (dim_vector (3, 1));
-              perm(0) = 0;
-              perm(1) = 2;
-              perm(2) = 1;
-              pixels = pixels.permute (perm);
-
-              Array<idx_vector> idx (dim_vector (3, 1));
-              idx(0) = idx_vector (':');
-              idx(1) = idx_vector (':');
-              idx(2) = idx_vector (pixels.dim3 ()-1, -1, -1);
-              pixels = uint8NDArray (pixels.index (idx));
-            }
-            break;
-          }
+        rotate_pixels (pixels, rotation);
       }
 
     return pixels;
@@ -1388,27 +1332,6 @@
     return extent;
   }
 
-  int
-  ft_text_renderer::rotation_to_mode (double rotation) const
-  {
-    // Clip rotation to range [0, 360]
-    while (rotation < 0)
-      rotation += 360.0;
-    while (rotation > 360.0)
-      rotation -= 360.0;
-
-    if (rotation == 0.0)
-      return ROTATION_0;
-    else if (rotation == 90.0)
-      return ROTATION_90;
-    else if (rotation == 180.0)
-      return ROTATION_180;
-    else if (rotation == 270.0)
-      return ROTATION_270;
-    else
-      return ROTATION_0;
-  }
-
   void
   ft_text_renderer::text_to_pixels (const std::string& txt,
                                     uint8NDArray& pxls, Matrix& box,
@@ -1427,65 +1350,9 @@
     if (pxls.isempty ())
       return;  // nothing to render
 
-    switch (halign)
-      {
-      case 1:
-        box(0) = -box(2)/2;
-        break;
-
-      case 2:
-        box(0) = -box(2);
-        break;
-
-      default:
-        box(0) = 0;
-        break;
-      }
-
-    switch (valign)
-      {
-      case 1:
-        box(1) = -box(3)/2;
-        break;
-
-      case 2:
-        box(1) = -box(3);
-        break;
-
-      case 3:
-        break;
-
-      case 4:
-        box(1) = -box(3)-box(1);
-        break;
-
-      default:
-        box(1) = 0;
-        break;
-      }
-
-    if (handle_rotation)
-      {
-        switch (rot_mode)
-          {
-          case ROTATION_90:
-            std::swap (box(0), box(1));
-            std::swap (box(2), box(3));
-            box(0) = -box(0)-box(2);
-            break;
-
-          case ROTATION_180:
-            box(0) = -box(0)-box(2);
-            box(1) = -box(1)-box(3);
-            break;
-
-          case ROTATION_270:
-            std::swap (box(0), box(1));
-            std::swap (box(2), box(3));
-            box(1) = -box(1)-box(3);
-            break;
-          }
-      }
+    // Move X0 and Y0 depending on alignments and eventually swap all values
+    // for text rotated 90° 180° or 270°
+    fix_bbox_anchor (box, halign, valign, rot_mode, handle_rotation);
   }
 
   ft_text_renderer::ft_font::ft_font (const ft_font& ft)
diff -r c5ccb3d2be81 -r cbb162daaadb libinterp/corefcn/gl2ps-print.cc
--- a/libinterp/corefcn/gl2ps-print.cc	Tue Nov 24 20:34:02 2020 -0500
+++ b/libinterp/corefcn/gl2ps-print.cc	Wed Nov 25 23:01:00 2020 +0100
@@ -65,7 +65,7 @@
     gl2ps_renderer (opengl_functions& glfcns, FILE *_fp,
                     const std::string& _term)
       : opengl_renderer (glfcns), fp (_fp), term (_term),
-        fontsize (), fontname (), buffer_overflow (false)
+      fontsize (), fontname (), buffer_overflow (false), m_svg_def_index (0)
     { }
 
     ~gl2ps_renderer (void) = default;
@@ -266,7 +266,10 @@
                                Matrix box, double rotation,
                                std::list<text_renderer::string>& lst);
 
-    // Build an svg text element from a list of parsed strings.
+    // Build an svg text element from a list of parsed strings
+    std::string get_svg_elements (std::string str, Matrix bbox, double rotation,
+                                  ColumnVector coord_pix);
+
     std::string strlist_to_svg (double x, double y, double z, Matrix box,
                                 double rotation,
                                 std::list<text_renderer::string>& lst);
@@ -283,6 +286,7 @@
     double fontsize;
     std::string fontname;
     bool buffer_overflow;
+    std::size_t m_svg_def_index;
   };
 
   static bool
@@ -487,8 +491,7 @@
                         error ("gl2ps_renderer::draw: internal pipe error");
                       }
                   }
-                else if (! header_found
-                         && term.find ("svg") != std::string::npos)
+                else if (term.find ("svg") != std::string::npos)
                   {
                     // FIXME: gl2ps uses pixel units for SVG format.
                     //        Modify resulting svg to use points instead.
@@ -496,7 +499,7 @@
                     //        make header_found true for SVG if gl2ps is fixed.
                     std::string srchstr (str);
                     size_t pos = srchstr.find ("px");
-                    if (pos != std::string::npos)
+                    if (! header_found && pos != std::string::npos)
                       {
                         header_found = true;
                         srchstr[pos+1] = 't';  // "px" -> "pt"
@@ -794,20 +797,127 @@
   }
 
   std::string
+  gl2ps_renderer::get_svg_elements (std::string str, Matrix box,
+                                    double rotation, ColumnVector coord_pix)
+  {
+    // Extract <defs> elements and change their id to avoid conflict with
+    // defs coming from another svg string
+    std::string::size_type n1 = str.find ("<defs>");
+    if (n1 == std::string::npos)
+      return std::string ();
+
+    std::string id, new_id;
+    n1 = str.find ("<path", ++n1);
+    std::string::size_type n2;
+
+    while (n1 != std::string::npos)
+      {
+        // Extract the identifier id='identifier'
+        n1 = str.find ("id='", n1) + 4;
+        n2 = str.find ("'", n1);
+        id = str.substr (n1, n2-n1);
+
+        new_id = std::to_string (m_svg_def_index) + "-" + id ;
+
+        str.replace (n1, n2-n1, new_id);
+
+        std::string::size_type n_ref = str.find ("#" + id);
+
+        while (n_ref != std::string::npos)
+          {
+            str.replace (n_ref + 1, id.length (), new_id);
+            n_ref = str.find ("#" + id);
+          }
+
+        n1 = str.find ("<path", n1);
+      }
+
+    m_svg_def_index++;
+
+    n1 = str.find ("<defs>");
+    n2 = str.find ("</defs>") + 7;
+
+    std::string defs = str.substr (n1, n2-n1);
+
+    // Extract the group containing the <use> elements and transform its
+    // coordinates using the bbox and coordinates info.
+
+    // Extract the original viewBox anchor
+    n1 = str.find ("viewBox='") + 9;
+    if (n1 == std::string::npos)
+      return std::string ();
+
+    n2 = str.find (" ", n1);
+    double original_x0 = std::stod (str.substr (n1, n2-n1));
+
+    n1 = n2+1;
+    n2 = str.find (" ", n1);
+    double original_y0 = std::stod (str.substr (n1, n2-n1));
+
+    // First look for local transform in the original svg
+    std::string orig_trans;
+    n1 = str.find ("<g id='page1' transform='");
+    if (n1 != std::string::npos)
+      {
+        n1 += 25;
+        n2 = str.find ("'", n1);
+        orig_trans = str.substr (n1, n2-n1);
+        n1 = n2 + 1;
+      }
+    else
+      {
+        n1 = str.find ("<g id='page1'");
+        n1 += 13;
+      }
+
+    n2 = str.find ("</g>", n1) + 4;
+
+    // The first applied transformation is the right-most
+    // 1* Apply original transform
+    std::string tform = orig_trans;
+
+    // 2* Move the anchor to the final position
+    tform = std::string ("translate")
+      + "(" + std::to_string (box(0) - original_x0 + coord_pix(0))
+      + "," + std::to_string (-(box(3) + box(1)) - original_y0 + coord_pix(1))
+      + ") " + tform;
+
+    // 3* Rotate around the final position
+    if (rotation != 0)
+      tform = std::string ("rotate")
+        + "(" + std::to_string (-rotation)
+        + "," + std::to_string (coord_pix(0))
+        + "," + std::to_string (coord_pix(1))
+        + ") " + tform;
+
+    std::string use_group = "<g transform='"
+      + tform + "'" + str.substr (n1, n2-n1);
+
+    return defs + "\n" + use_group;
+  }
+
+  std::string
   gl2ps_renderer::strlist_to_svg (double x, double y, double z,
                                   Matrix box, double rotation,
                                   std::list<text_renderer::string>& lst)
   {
-    if (lst.empty ())
-      return "";
-
     //Use pixel coordinates to conform to gl2ps
     ColumnVector coord_pix = get_transform ().transform (x, y, z, false);
 
-    std::ostringstream os;
-    os << R"(<text xml:space="preserve" )";
+    if (lst.empty ())
+      return "";
+    else if (lst.size () == 1)
+      {
+        // This may be a preformated svg image. In that case, extract <def>
+        // elements, store them for later addition, and return <use> elements
+        std::string str = lst.front ().get_string ();
+        if (str.find ("<svg") != std::string::npos)
+          return get_svg_elements (str, box, rotation, coord_pix);
+      }
 
     // Rotation and translation are applied to the whole text element
+    std::ostringstream os;
+    os << R"(<g xml:space="preserve" )";
     os << "transform=\""
        << "translate(" << coord_pix(0) + box(0) << "," << coord_pix(1) - box(1)
        << ") rotate(" << -rotation << "," << -box(0) << "," << box(1)
@@ -829,7 +939,7 @@
     // build a tspan for each element in the strlist
     for (p = lst.begin (); p != lst.end (); p++)
       {
-        os << "<tspan ";
+        os << "<text ";
 
         if (name.compare (p->get_family ()))
           os << "font-family=\"" << p->get_family () << "\" ";
@@ -882,9 +992,9 @@
                   os << chr.str ();
               }
           }
-        os << "</tspan>";
+        os << "</text>";
       }
-    os << "</text>";
+    os << "</g>";
 
     return os.str ();
   }
@@ -894,6 +1004,27 @@
                                  Matrix box, double rotation,
                                  std::list<text_renderer::string>& lst)
   {
+    if (lst.empty ())
+      return "";
+    else if (lst.size () == 1)
+      {
+        static bool warned = false;
+
+        std::string str = lst.front ().get_string ();
+        if (str.find ("<svg") != std::string::npos)
+          {
+            if (! warned)
+              {
+                warned = true;
+                warning_with_id ("Octave:print:unhandled-svg-content",
+                                 "print: unhandled LaTeX strings. "
+                                 "Use -svgconvert option or -d*latex* output "
+                                 "device.");
+              }
+            return "";
+          }
+      }
+
     // Translate and rotate coordinates in order to use bottom-left alignment
     fix_strlist_position (x, y, z, box, rotation, lst);
     Matrix prev_color (1, 3, -1);
diff -r c5ccb3d2be81 -r cbb162daaadb libinterp/corefcn/latex-text-renderer.cc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libinterp/corefcn/latex-text-renderer.cc	Wed Nov 25 23:01:00 2020 +0100
@@ -0,0 +1,427 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2013-2020 The Octave Project Developers
+//
+// See the file COPYRIGHT.md in the top-level directory of this
+// distribution or <https://octave.org/copyright/>.
+//
+// This file is part of Octave.
+//
+// Octave is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Octave is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Octave; see the file COPYING.  If not, see
+// <https://www.gnu.org/licenses/>.
+//
+////////////////////////////////////////////////////////////////////////
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <iostream>
+#include <fstream>
+
+#include "base-text-renderer.h"
+#include "builtin-defun-decls.h"
+#include "dim-vector.h"
+#include "error.h"
+#include "file-ops.h"
+#include "interpreter.h"
+#include "interpreter-private.h"
+#include "oct-env.h"
+#include "oct-process.h"
+
+// Store both the generated pixmap and the svg image
+typedef std::pair<uint8NDArray, std::string> latex_data;
+typedef std::map<std::string, latex_data> latex_cache;
+
+latex_cache global_latex_cache;
+
+namespace octave
+{
+  class
+  OCTINTERP_API
+  latex_renderer : public base_text_renderer
+ {
+
+  public:
+
+    latex_renderer (void)
+      : m_fontsize (10.0), m_fontname ("cmr"), m_tmp_dir (),
+        m_color (dim_vector (1, 3), 0), m_latex_binary ("latex"),
+        m_dvipng_binary ("dvipng"), m_dvisvg_binary ("dvisvgm")
+      {
+        std::string bin = sys::env::getenv ("OCTAVE_LATEX_BINARY");
+        if (! bin.empty ())
+          m_latex_binary = bin;
+
+        bin = sys::env::getenv ("OCTAVE_DVIPNG_BINARY");
+        if (! bin.empty ())
+          m_dvipng_binary = bin;
+
+        bin = sys::env::getenv ("OCTAVE_DVISVG_BINARY");
+        if (! bin.empty ())
+          m_dvisvg_binary = bin;
+      }
+
+    ~latex_renderer (void)
+      {
+        if( ! m_tmp_dir.empty ())
+          octave::sys::recursive_rmdir (m_tmp_dir);
+      }
+
+    void set_font (const std::string& /*name*/, const std::string& /*weight*/,
+                   const std::string& /*angle*/, double size)
+    {
+      m_fontsize = size;
+    }
+
+    void set_color (const Matrix& c)
+    {
+      if (c.numel () == 3)
+        {
+          m_color(0) = static_cast<uint8_t> (c (0) * 255);
+          m_color(1) = static_cast<uint8_t> (c (1) * 255);
+          m_color(2) = static_cast<uint8_t> (c (2) * 255);
+        }
+    }
+
+    Matrix get_extent (text_element* /*elt*/, double /*rotation*/)
+    {
+      return Matrix (1, 2, 0.0);
+    }
+
+    Matrix get_extent (const std::string& txt, double rotation,
+                       const caseless_str& interpreter)
+    {
+      Matrix bbox;
+      uint8NDArray pixels;
+      text_to_pixels (txt, pixels, bbox, 0, 0, rotation, interpreter, false);
+      return bbox.extract_n (0, 2, 1, 2);
+    }
+
+    void text_to_strlist (const std::string& txt,
+                          std::list<text_renderer::string>& lst,
+                          Matrix& bbox, int halign, int valign, double rotation,
+                          const caseless_str& interp)
+    {
+      uint8NDArray pixels;
+      text_to_pixels (txt, pixels, bbox, halign, valign, rotation,
+                      interp, false);
+
+      std::string svgcontent = global_latex_cache[key (txt)].second;
+
+      text_renderer::font fnt;
+      text_renderer::string str (svgcontent, fnt, 0.0, 0.0);
+      lst.push_back (str);
+    }
+
+    void text_to_pixels (const std::string& txt, uint8NDArray& pxls,
+                         Matrix& bbox, int halign, int valign, double rotation,
+                         const caseless_str& interpreter,
+                         bool handle_rotation);
+
+    void set_anti_aliasing (bool /*val*/) { }
+
+    octave_map get_system_fonts (void) { return octave_map (); }
+
+    /* method that cheks if there are all required programs installed */
+    bool is_usable (void);
+
+  private:
+
+    std::string key (const std::string& txt)
+    {
+      return (txt + ":"
+              + std::to_string (m_fontsize) + ":"
+              + std::to_string (m_color(0)) + ":"
+              + std::to_string (m_color(1)) + ":"
+              + std::to_string (m_color(2)));
+    }
+
+    uint8NDArray render (const std::string& txt);
+
+    bool read_image (const std::string& png_file, uint8NDArray& data) const;
+
+    std::string write_tex_file (const std::string& txt);
+
+  private:
+    double m_fontsize;
+    std::string m_fontname;
+    std::string m_tmp_dir;
+    uint8NDArray m_color;
+    std::string m_latex_binary;
+    std::string m_dvipng_binary;
+    std::string m_dvisvg_binary;
+
+  };
+
+  bool
+  latex_renderer::is_usable (void)
+  {
+    static bool tested = false;
+    static bool ok = false;
+
+    if (! tested)
+      {
+        tested = true;
+
+        // For testing, render a questoin mark
+        uint8NDArray pixels = render ("?");
+
+        if (! pixels.isempty ())
+          ok = true;
+      }
+
+    return ok;
+  }
+
+  std::string
+  latex_renderer::write_tex_file (const std::string& txt)
+  {
+    if (m_tmp_dir.empty ())
+      {
+        //Create the temporary directory
+        m_tmp_dir = octave::sys::tempnam ("", "latex");
+
+        if (octave::sys::mkdir (m_tmp_dir, 0700) != 0)
+          {
+            warning_with_id ("Octave:LaTeX:internal-error",
+                             "latex_renderer: unable to create temp directory");
+            return std::string ();
+          }
+      }
+
+    std::string base_file_name
+      = octave::sys::file_ops::concat (m_tmp_dir, "default");
+
+    // Creating and writing to temporary .tex file
+    std::ofstream file;
+    file.open (base_file_name + ".tex");
+    file << "\\documentclass[10pt]{standalone} \n"
+         << "\\usepackage{amsmath} \n"
+         << "\\usepackage[utf8]{inputenc} \n"
+         << "\\begin{document} \n"
+         << txt << "\n"
+         << "\\end{document}";
+    file.close ();
+
+    return base_file_name;
+  }
+
+  bool
+  latex_renderer::read_image (const std::string& png_file,
+                              uint8NDArray& data) const
+  {
+    uint8NDArray alpha;
+    uint8NDArray rgb;
+    int height;
+    int width;
+
+    try
+      {
+        // First get the image size to build the argument to __magick_read__
+        octave_value_list retval = F__magick_ping__ (ovl (png_file), 1);
+
+        octave_scalar_map info
+          = retval(0).xscalar_map_value ("latex_renderer::read_image: "
+                                         "Wrong type for info");
+        height = info.getfield ("rows").int_value ();
+        width = info.getfield ("columns").int_value ();
+        Cell region (dim_vector(1,2));
+        region(0) = range<double> (1.0, height);
+        region(1) = range<double> (1.0, width);
+        info.setfield ("region", region);
+        info.setfield ("index", octave_value (1));
+
+        // Retrieve the alpha map
+        retval = F__magick_read__ (ovl (png_file, info), 3);
+
+        alpha = retval(2).xuint8_array_value ("latex_renderer::read_image: "
+                                              "Wrong type for alpha");
+      }
+    catch (const octave::execution_exception& ee)
+      {
+        warning_with_id ("Octave:LaTeX:internal-error",
+                         "latex_renderer:: failed to read png data. %s",
+                         ee.message ().c_str ());
+
+        octave::interpreter& interp
+          = octave::__get_interpreter__ ("latex_renderer::read_image");
+
+        interp.recover_from_exception ();
+
+        return false;
+      }
+
+    data = uint8NDArray (dim_vector (4, width, height),
+                             static_cast<uint8_t> (0));
+
+    for (int i = 0; i < height; i++)
+      {
+        for (int j = 0; j < width; j++)
+          {
+            data(0, j, i) = m_color(0);
+            data(1, j, i) = m_color(1);
+            data(2, j, i) = m_color(2);
+            data(3, j, i) = alpha(height-i-1,j);
+          }
+      }
+
+    return true;
+  }
+
+  uint8NDArray
+  latex_renderer::render (const std::string& txt)
+  {
+    uint8NDArray data;
+
+    // First write the base .tex file
+    std::string base_file_name = write_tex_file (txt);
+
+    if (base_file_name.empty ())
+      return data;
+
+    // Generate DVI file
+    std::string tex_file = base_file_name + ".tex";
+    std::string dvi_file = base_file_name + ".dvi";
+    std::string log_file = base_file_name + ".log";
+
+    process_execution_result result;
+    std::string cmd = (m_latex_binary + " -interaction=nonstopmode "
+                       + "-output-directory=" + m_tmp_dir + " "
+                       + tex_file + " > " + log_file);
+
+    result = run_command_and_return_output (cmd);
+
+    if (result.exit_status () != 0)
+      {
+        warning_with_id ("Octave:LaTeX:internal-error",
+                         "latex_renderer: unable to compile \"%s\"",
+                         txt.c_str ());
+        return data;
+      }
+
+    double size_factor = m_fontsize / 10.0;
+
+
+    // Convert DVI to SVG, read file and store its content for later use in
+    // gl2ps_print
+    std::string svg_file = base_file_name + ".svg";
+
+    cmd = (m_dvisvg_binary + " -n "
+           + "-TS" + std::to_string (size_factor) + " "
+           + "-o " + svg_file + " "
+           + dvi_file + " >/dev/null 2>/dev/null");
+
+    result = run_command_and_return_output (cmd);
+
+    if (result.exit_status () != 0)
+      {
+        warning_with_id ("Octave:LaTeX:internal-error",
+                         "latex_renderer: unable to run dvisvgm for \"%s\"",
+                         txt.c_str ());
+        return data;
+      }
+
+    std::ifstream svg_stream (svg_file);
+    std::string svg_string;
+    svg_string.assign (std::istreambuf_iterator<char> (svg_stream),
+                       std::istreambuf_iterator<char> ());
+
+    // Convert DVI to PNG, read file and format pixel data for later use in
+    // OpenGL
+    std::string png_file = base_file_name + ".png";
+
+    cmd = (m_dvipng_binary + " " + dvi_file + " "
+           + "-o " + png_file + " "
+           + "-bg Transparent -D "
+           + std::to_string (std::floor (72.0 * size_factor))
+           + " >/dev/null 2>/dev/null");
+
+    result = run_command_and_return_output (cmd);
+
+    if (result.exit_status () != 0)
+      {
+        warning_with_id ("Octave:LaTeX:internal-error",
+                         "latex_renderer: dvipng failed");
+        return data;
+      }
+
+    if (! read_image (png_file, data))
+      return data;
+
+    // Cache pixel and svg data for this string
+    global_latex_cache[key (txt)] = latex_data (data, svg_string);
+    printf ("*Caching %s\n", key (txt).c_str ());
+
+    return data;
+  }
+
+  void
+  latex_renderer::text_to_pixels (const std::string& txt, uint8NDArray& pixels,
+                                  Matrix& bbox, int halign, int valign,
+                                  double rotation,
+                                  const caseless_str& /*interpreter*/,
+                                  bool handle_rotation)
+  {
+    // Return early for empty strings
+    if (txt.empty ())
+      return;
+
+
+    // Render if it was not already done
+    auto it = global_latex_cache.find (key (txt));
+
+    if (it != global_latex_cache.end ())
+      pixels = it->second.first;
+    else if (is_usable ())
+      {
+        pixels = render (txt);
+        if (pixels.isempty ())
+          pixels = render ("?");
+      }
+    else
+      pixels = uint8NDArray (dim_vector (4, 1, 1), static_cast<uint8_t> (0));
+
+    if (pixels.ndims () < 3 || pixels.isempty ())
+      return;  // nothing to render
+
+    // Store unrotated bbox size
+    bbox = Matrix (1, 4, 0.0);
+    bbox (2) = pixels.dim2 ();
+    bbox (3) = pixels.dim3 ();
+
+    // Now rotate pixels if necessary
+    int rot_mode = rotation_to_mode (rotation);
+
+    if (! pixels.isempty ())
+      rotate_pixels (pixels, rot_mode);
+
+    // Move X0 and Y0 depending on alignments and eventually swap values
+    // for text rotated 90° 180° or 270°
+    fix_bbox_anchor (bbox, halign, valign, rot_mode, handle_rotation);
+  }
+
+  base_text_renderer*
+  make_latex_text_renderer (void)
+  {
+    latex_renderer *renderer = new latex_renderer ();
+    if (renderer->is_usable ())
+        return renderer;
+    else
+      {
+        delete renderer;
+        return nullptr;
+      }
+  }
+}
diff -r c5ccb3d2be81 -r cbb162daaadb libinterp/corefcn/latex-text-renderer.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libinterp/corefcn/latex-text-renderer.h	Wed Nov 25 23:01:00 2020 +0100
@@ -0,0 +1,38 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2013-2020 The Octave Project Developers
+//
+// See the file COPYRIGHT.md in the top-level directory of this
+// distribution or <https://octave.org/copyright/>.
+//
+// This file is part of Octave.
+//
+// Octave is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Octave is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Octave; see the file COPYING.  If not, see
+// <https://www.gnu.org/licenses/>.
+//
+////////////////////////////////////////////////////////////////////////
+
+#if ! defined (octave_latex_text_renderer_h)
+#define octave_latex_text_renderer_h 1
+
+#include "octave-config.h"
+
+namespace octave
+{
+  class base_text_renderer;
+
+  extern base_text_renderer * make_latex_text_renderer (void);
+}
+
+#endif
diff -r c5ccb3d2be81 -r cbb162daaadb libinterp/corefcn/module.mk
--- a/libinterp/corefcn/module.mk	Tue Nov 24 20:34:02 2020 -0500
+++ b/libinterp/corefcn/module.mk	Wed Nov 25 23:01:00 2020 +0100
@@ -46,6 +46,7 @@
   %reldir%/hook-fcn.h \
   %reldir%/input.h \
   %reldir%/interpreter.h \
+  %reldir%/latex-text-renderer.h \
   %reldir%/load-path.h \
   %reldir%/load-save.h \
   %reldir%/ls-ascii-helper.h \
@@ -124,6 +125,7 @@
   %reldir%/__qp__.cc \
   %reldir%/amd.cc \
   %reldir%/balance.cc \
+  %reldir%/base-text-renderer.cc \
   %reldir%/besselj.cc \
   %reldir%/bitfcns.cc \
   %reldir%/bsxfun.cc \
@@ -189,6 +191,7 @@
   %reldir%/jsondecode.cc \
   %reldir%/jsonencode.cc \
   %reldir%/kron.cc \
+  %reldir%/latex-text-renderer.cc \
   %reldir%/load-path.cc \
   %reldir%/load-save.cc \
   %reldir%/lookup.cc \
diff -r c5ccb3d2be81 -r cbb162daaadb libinterp/corefcn/text-engine.h
--- a/libinterp/corefcn/text-engine.h	Tue Nov 24 20:34:02 2020 -0500
+++ b/libinterp/corefcn/text-engine.h	Wed Nov 25 23:01:00 2020 +0100
@@ -322,7 +322,7 @@
   text_processor
   {
   public:
-    virtual void visit (text_element_string& e) = 0;
+    virtual void visit (text_element_string&) { }
 
     virtual void visit (text_element_symbol&) { }
 
diff -r c5ccb3d2be81 -r cbb162daaadb libinterp/corefcn/text-renderer.cc
--- a/libinterp/corefcn/text-renderer.cc	Tue Nov 24 20:34:02 2020 -0500
+++ b/libinterp/corefcn/text-renderer.cc	Wed Nov 25 23:01:00 2020 +0100
@@ -28,28 +28,22 @@
 #endif
 
 #include "base-text-renderer.h"
+#include "error.h"
 #include "errwarn.h"
 #include "ft-text-renderer.h"
+#include "latex-text-renderer.h"
 #include "text-renderer.h"
 
 namespace octave
 {
-  static base_text_renderer *
-  make_text_renderer (void)
-  {
-    // Allow the possibility of choosing different text rendering
-    // implementations.
-
-    return make_ft_text_renderer ();
-  }
-
   text_renderer::text_renderer (void)
-    : rep (make_text_renderer ())
+    : rep (make_ft_text_renderer ()), latex_rep (make_latex_text_renderer ())
   { }
 
   text_renderer::~text_renderer (void)
   {
     delete rep;
+    delete latex_rep;
   }
 
   bool
@@ -71,6 +65,24 @@
     return rep != nullptr;
   }
 
+  bool
+  text_renderer::latex_ok (void) const
+  {
+    static bool warned = false;
+
+    if (! latex_rep)
+      {
+        if (! warned)
+          {
+            warning_with_id ("Octave:LaTeX:internal-error",
+                             "latex_renderer: unusable latex tool-chain");
+            warned = true;
+          }
+      }
+
+    return latex_rep != nullptr;
+  }
+
   Matrix
   text_renderer::get_extent (text_element *elt, double rotation)
   {
@@ -83,9 +95,14 @@
   text_renderer::get_extent (const std::string& txt, double rotation,
                              const caseless_str& interpreter)
   {
-    static Matrix empty_extent (1, 4, 0.0);
+    static Matrix retval (1, 4, 0.0);
 
-    return ok () ? rep->get_extent (txt, rotation, interpreter) : empty_extent;
+    if (interpreter == "latex" && latex_ok ())
+      retval = latex_rep->get_extent (txt, rotation, interpreter);
+    else if (ok ())
+      retval = rep->get_extent (txt, rotation, interpreter);
+
+    return retval;
   }
 
   void
@@ -93,6 +110,9 @@
   {
     if (ok ())
       rep->set_anti_aliasing (val);
+
+    if (latex_ok ())
+      latex_rep->set_anti_aliasing (val);
   }
 
   octave_map
@@ -112,6 +132,9 @@
   {
     if (ok ())
       rep->set_font (name, weight, angle, size);
+
+    if (latex_ok ())
+      latex_rep->set_font (name, weight, angle, size);
   }
 
   void
@@ -119,6 +142,9 @@
   {
     if (ok ())
       rep->set_color (c);
+
+    if (latex_ok ())
+      latex_rep->set_color (c);
   }
 
   void
@@ -131,7 +157,10 @@
     static Matrix empty_bbox (1, 4, 0.0);
     static uint8NDArray empty_pxls;
 
-    if (ok ())
+    if (interpreter == "latex" && latex_ok ())
+      latex_rep->text_to_pixels (txt, pxls, bbox, halign, valign, rotation,
+                                 interpreter, handle_rotation);
+    else if (ok ())
       rep->text_to_pixels (txt, pxls, bbox, halign, valign, rotation,
                            interpreter, handle_rotation);
     else
@@ -151,7 +180,10 @@
     static Matrix empty_bbox (1, 4, 0.0);
     static std::list<text_renderer::string> empty_lst;
 
-    if (ok ())
+    if (interpreter == "latex" && latex_ok ())
+      latex_rep->text_to_strlist (txt, lst, bbox, halign, valign, rotation,
+                                  interpreter);
+    else if (ok ())
       rep->text_to_strlist (txt, lst, bbox, halign, valign, rotation,
                             interpreter);
     else
diff -r c5ccb3d2be81 -r cbb162daaadb libinterp/corefcn/text-renderer.h
--- a/libinterp/corefcn/text-renderer.h	Tue Nov 24 20:34:02 2020 -0500
+++ b/libinterp/corefcn/text-renderer.h	Wed Nov 25 23:01:00 2020 +0100
@@ -59,6 +59,8 @@
 
     bool ok (void) const;
 
+    bool latex_ok (void) const;
+
     Matrix get_extent (text_element *elt, double rotation = 0.0);
 
     Matrix get_extent (const std::string& txt, double rotation = 0.0,
@@ -228,6 +230,7 @@
   private:
 
     base_text_renderer *rep;
+    base_text_renderer *latex_rep;
   };
 }
 
diff -r c5ccb3d2be81 -r cbb162daaadb m4/acinclude.m4
--- a/m4/acinclude.m4	Tue Nov 24 20:34:02 2020 -0500
+++ b/m4/acinclude.m4	Wed Nov 25 23:01:00 2020 +0100
@@ -1889,7 +1889,7 @@
   case "$qt_version" in
     5)
       QT_OPENGL_MODULE="Qt5OpenGL"
-      QT_MODULES="Qt5Core Qt5Gui Qt5Network Qt5PrintSupport Qt5Help Qt5Xml"
+      QT_MODULES="Qt5Core Qt5Gui Qt5Help Qt5Network Qt5PrintSupport Qt5Svg Qt5Xml"
     ;;
     *)
       AC_MSG_ERROR([Unrecognized Qt version $qt_version])
diff -r c5ccb3d2be81 -r cbb162daaadb src/octave-svgconvert.cc
--- a/src/octave-svgconvert.cc	Tue Nov 24 20:34:02 2020 -0500
+++ b/src/octave-svgconvert.cc	Wed Nov 25 23:01:00 2020 +0100
@@ -34,7 +34,9 @@
 #include <QPainter>
 #include <QPrinter>
 #include <QRegExp>
+#include <QSvgRenderer>
 
+// Render to pdf
 class pdfpainter : public QPainter
 {
 public:
@@ -51,11 +53,6 @@
     m_printer.setOutputFileName (get_fname ());
     m_printer.setFullPage (true);
     m_printer.setPaperSize (get_rectf ().size (), QPrinter::DevicePixel);
-
-    // Painter settings
-    begin (&m_printer);
-    setViewport (get_rect ());
-    scale (get_scale (), get_scale ());
   }
 
   ~pdfpainter (void) { }
@@ -68,7 +65,15 @@
 
   double get_scale (void) const { return m_dpi / 72.0; }
 
-  void finish (void) { end (); }
+  void render (const QByteArray svg_content)
+  {
+    QSvgRenderer renderer (svg_content);
+    begin (&m_printer);
+    // setViewport (get_rect ());
+    // scale (get_scale (), get_scale ());
+    renderer.render (this);
+    end ();
+  }
 
 private:
   QString m_fname;
@@ -78,30 +83,6 @@
 };
 
 // String conversion functions
-QVector<double> qstr2vectorf (QString str)
-{
-  QVector<double> pts;
-  QStringList coords = str.split (",");
-  for (QStringList::iterator p = coords.begin (); p != coords.end (); p += 1)
-    {
-      double pt = (*p).toDouble ();
-      pts.append (pt);
-    }
-  return pts;
-}
-
-QVector<double> qstr2vectord (QString str)
-{
-  QVector<double> pts;
-  QStringList coords = str.split (",");
-  for (QStringList::iterator p = coords.begin (); p != coords.end (); p += 1)
-    {
-      double pt = (*p).toDouble ();
-      pts.append (pt);
-    }
-
-  return pts;
-}
 
 QVector<QPointF> qstr2ptsvector (QString str)
 {
@@ -117,33 +98,6 @@
   return pts;
 }
 
-QVector<QPoint> qstr2ptsvectord (QString str)
-{
-  QVector<QPoint> pts;
-  str = str.trimmed ();
-  str.replace (" ", ",");
-  QStringList coords = str.split (",");
-  for (QStringList::iterator p = coords.begin (); p != coords.end (); p += 2)
-    {
-      QPoint pt ((*p).toDouble (), (*(p+1)).toDouble ());
-      pts.append (pt);
-    }
-  return pts;
-}
-
-// Extract field arguments in a style-like string, e.g. "bla field(1,34,56) bla"
-QString get_field (QString str, QString field)
-{
-  QString retval;
-  QRegExp rx (field + "\\(([^\\)]*)\\)");
-  int pos = 0;
-  pos = rx.indexIn (str, pos);
-  if (pos > -1)
-    retval = rx.cap (1);
-
-  return retval;
-}
-
 // Polygon reconstruction class
 class octave_polygon
 {
@@ -332,281 +286,6 @@
   QList<QPolygonF> m_polygons;
 };
 
-void draw (QDomElement& parent_elt, pdfpainter& painter)
-{
-  QDomNodeList nodes = parent_elt.childNodes ();
-
-  static QString clippath_id;
-  static QMap< QString, QVector<QPoint> > clippath;
-
-  // tspan elements must have access to the font and position extracted from
-  // their parent text element
-  static QFont font;
-  static double dx = 0, dy = 0;
-
-  for (int i = 0; i < nodes.count (); i++)
-    {
-      QDomNode node = nodes.at (i);
-      if (! node.isElement ())
-        continue;
-
-      QDomElement elt = node.toElement ();
-
-      if (elt.tagName () == "clipPath")
-        {
-          clippath_id = "#" + elt.attribute ("id");
-          draw (elt, painter);
-          clippath_id = QString ();
-        }
-      else if (elt.tagName () == "g")
-        {
-          bool current_clipstate = painter.hasClipping ();
-          QRegion current_clippath = painter.clipRegion ();
-
-          QString str = elt.attribute ("clip-path");
-          if (! str.isEmpty ())
-            {
-              QVector<QPoint> pts = clippath[get_field (str, "url")];
-              if (! pts.isEmpty ())
-                {
-                  painter.setClipRegion (QRegion (QPolygon (pts)));
-                  painter.setClipping (true);
-                }
-            }
-
-          draw (elt, painter);
-
-          // Restore previous clipping settings
-          painter.setClipRegion (current_clippath);
-          painter.setClipping (current_clipstate);
-        }
-      else if (elt.tagName () == "text")
-        {
-          // Font
-          font = QFont ();
-          QString str = elt.attribute ("font-family");
-          if (! str.isEmpty ())
-            font.setFamily (elt.attribute ("font-family"));
-
-          str = elt.attribute ("font-weight");
-          if (! str.isEmpty () && str != "normal")
-            font.setWeight (QFont::Bold);
-
-          str = elt.attribute ("font-style");
-          if (! str.isEmpty () && str != "normal")
-            font.setStyle (QFont::StyleItalic);
-
-          str = elt.attribute ("font-size");
-          if (! str.isEmpty ())
-            font.setPixelSize (str.toDouble ());
-
-          painter.setFont (font);
-
-          // Translation and rotation
-          painter.save ();
-          str = get_field (elt.attribute ("transform"), "translate");
-          if (! str.isEmpty ())
-            {
-              QStringList trans = str.split (",");
-              dx = trans[0].toDouble ();
-              dy = trans[1].toDouble ();
-
-              str = get_field (elt.attribute ("transform"), "rotate");
-              if (! str.isEmpty ())
-                {
-                  QStringList rot = str.split (",");
-                  painter.translate (dx+rot[1].toDouble (),
-                                     dy+rot[2].toDouble ());
-                  painter.rotate (rot[0].toDouble ());
-                  dx = rot[1].toDouble ();
-                  dy = rot[2].toDouble ();
-                }
-              else
-                {
-                  painter.translate (dx, dy);
-                  dx = 0;
-                  dy = 0;
-                }
-            }
-
-          draw (elt, painter);
-          painter.restore ();
-        }
-      else if (elt.tagName () == "tspan")
-        {
-          // Font
-          QFont saved_font (font);
-
-          QString str = elt.attribute ("font-family");
-          if (! str.isEmpty ())
-            font.setFamily (elt.attribute ("font-family"));
-
-          str = elt.attribute ("font-weight");
-          if (! str.isEmpty ())
-            {
-              if (str != "normal")
-                font.setWeight (QFont::Bold);
-              else
-                font.setWeight (QFont::Normal);
-            }
-
-          str = elt.attribute ("font-style");
-          if (! str.isEmpty ())
-            {
-              if (str != "normal")
-                font.setStyle (QFont::StyleItalic);
-              else
-                font.setStyle (QFont::StyleNormal);
-            }
-
-          str = elt.attribute ("font-size");
-          if (! str.isEmpty ())
-            font.setPixelSize (str.toDouble ());
-
-          painter.setFont (font);
-
-          // Color is specified in rgb
-          str = get_field (elt.attribute ("fill"), "rgb");
-          if (! str.isEmpty ())
-            {
-              QStringList clist = str.split (",");
-              painter.setPen (QColor (clist[0].toInt (), clist[1].toInt (),
-                                      clist[2].toInt ()));
-            }
-
-          QStringList xx = elt.attribute ("x").split (" ");
-          int y = elt.attribute ("y").toInt ();
-          str = elt.text ();
-          if (! str.isEmpty ())
-            {
-              int ii = 0;
-              foreach (QString s,  xx)
-                if (ii < str.size ())
-                  painter.drawText (s.toInt ()-dx, y-dy, str.at (ii++));
-            }
-
-          draw (elt, painter);
-          font = saved_font;
-        }
-      else if (elt.tagName () == "polyline")
-        {
-          // Color
-          QColor c (elt.attribute ("stroke"));
-          QString str = elt.attribute ("stroke-opacity");
-          if (! str.isEmpty () && str.toDouble () != 1.0
-              && str.toDouble () >= 0.0)
-            c.setAlphaF (str.toDouble ());
-
-          QPen pen;
-          pen.setColor (c);
-
-          // Line properties
-          str = elt.attribute ("stroke-width");
-          if (! str.isEmpty ())
-            {
-              double w = str.toDouble () * painter.get_scale ();
-              if (w > 0)
-                pen.setWidthF (w / painter.get_scale ());
-            }
-
-          str = elt.attribute ("stroke-linecap");
-          pen.setCapStyle (Qt::SquareCap);
-          if (str == "round")
-            pen.setCapStyle (Qt::RoundCap);
-          else if (str == "butt")
-            pen.setCapStyle (Qt::FlatCap);
-
-          str = elt.attribute ("stroke-linejoin");
-          pen.setJoinStyle (Qt::MiterJoin);
-          if (str == "round")
-            pen.setJoinStyle (Qt::RoundJoin);
-          else if (str == "bevel")
-            pen.setJoinStyle (Qt::BevelJoin);
-
-          str = elt.attribute ("stroke-dasharray");
-          pen.setStyle (Qt::SolidLine);
-          if (! str.isEmpty ())
-            {
-              QVector<double> pat = qstr2vectord (str);
-              if (pat.count () != 2 || pat[1] != 0)
-                {
-                  // Express pattern in linewidth units
-                  for (auto& p : pat)
-                    p /= pen.widthF ();
-
-                  pen.setDashPattern (pat);
-                }
-            }
-
-          painter.setPen (pen);
-          painter.drawPolyline (qstr2ptsvector (elt.attribute ("points")));
-        }
-      else if (elt.tagName () == "image")
-        {
-          // Images are represented as a base64 stream of png formatted data
-          QString href_att = elt.attribute ("xlink:href");
-          QString prefix ("data:image/png;base64,");
-          QByteArray data
-            = QByteArray::fromBase64 (href_att.mid (prefix.length ()).toLatin1 ());
-          QImage img;
-          if (img.loadFromData (data, "PNG"))
-            {
-              QRect pos(elt.attribute ("x").toInt (),
-                        elt.attribute ("y").toInt (),
-                        elt.attribute ("width").toInt (),
-                        elt.attribute ("height").toInt ());
-
-              // Translate
-              painter.save ();
-              QString str = get_field (elt.attribute ("transform"), "matrix");
-              if (! str.isEmpty ())
-                {
-                  QVector<double> m = qstr2vectorf (str);
-                  double scl = painter.get_scale ();
-                  QTransform tform(m[0]*scl, m[1]*scl, m[2]*scl,
-                                   m[3]*scl, m[4]*scl, m[5]*scl);
-                  painter.setTransform (tform);
-                }
-
-              painter.setRenderHint (QPainter::Antialiasing, false);
-              painter.drawImage (pos, img);
-              painter.setRenderHint (QPainter::Antialiasing, true);
-              painter.restore  ();
-            }
-        }
-      else if (elt.tagName () == "polygon")
-        {
-          if (! clippath_id.isEmpty ())
-            clippath[clippath_id] = qstr2ptsvectord (elt.attribute ("points"));
-          else
-            {
-              QString str = elt.attribute ("fill");
-              if (! str.isEmpty ())
-                {
-                  QColor color (str);
-
-                  str = elt.attribute ("fill-opacity");
-                  if (! str.isEmpty () && str.toDouble () != 1.0
-                      && str.toDouble () >= 0.0)
-                    color.setAlphaF (str.toDouble ());
-
-                  QPolygonF p (qstr2ptsvector (elt.attribute ("points")));
-
-                  if (p.count () > 2)
-                    {
-                      painter.setBrush (color);
-                      painter.setPen (Qt::NoPen);
-
-                      painter.setRenderHint (QPainter::Antialiasing, false);
-                      painter.drawPolygon (p);
-                      painter.setRenderHint (QPainter::Antialiasing, true);
-                    }
-                }
-            }
-        }
-    }
-}
-
 // Append a list of reconstructed child polygons to a QDomElement and remove
 // the original nodes
 
@@ -669,7 +348,7 @@
               if (! str.isEmpty ())
                 {
                   double alpha = str.toDouble ();
-                  if (alpha != 1.0 && str.toDouble () >= 0.0)
+                  if (alpha != 1.0 && alpha >= 0.0)
                     color.setAlphaF (alpha);
                 }
 
@@ -678,7 +357,7 @@
 
               if (color != current_color)
                 {
-                  // Reconstruct the previous series of triangle
+                  // Reconstruct the previous series of triangles
                   QList<QPolygonF> polygons = current_polygon.reconstruct ();
                   collection.push_back (QPair<QList<QDomNode>,QList<QPolygonF> >
                                         (replaced_nodes, polygons));
@@ -716,6 +395,61 @@
     replace_polygons (parent_elt, collection[ii].first, collection[ii].second);
 }
 
+// Split "text" elements into single characters elements
+void split_strings (QDomElement& parent_elt)
+{
+  QDomNodeList nodes = parent_elt.childNodes ();
+
+  for (int ii = 0; ii < nodes.count (); ii++)
+    {
+      QDomNode node = nodes.at (ii);
+
+      if (! node.isElement ())
+        continue;
+
+      QDomElement elt = node.toElement ();
+
+      if (elt.tagName () == "text")
+        {
+          QString str = elt.attribute ("x");
+          if (! str.isEmpty ())
+            {
+              QStringList xx = elt.attribute ("x").split (" ");
+              str = elt.text ();
+
+              if (! str.isEmpty () && xx.count () > 1)
+                {
+                  QDomNode last_node = node;
+
+                  for (int jj = 0; jj < (xx.count ()) && (jj < str.size ());
+                       jj++)
+                    {
+                      if (! last_node.isNull ())
+                        {
+                          QDomNode new_node = node.cloneNode ();
+                          new_node.toElement ().setAttribute ("x", xx.at (jj));
+
+                          QDomNodeList subnodes = new_node.childNodes ();
+
+                          // Change the text node of this element
+                          for (int kk = 0; kk < subnodes.count (); kk++)
+                            if (subnodes.at (kk).isText ())
+                              subnodes.at (kk).toText ().setData (str.at (jj));
+
+                          parent_elt.insertAfter (new_node, last_node);
+                          last_node = new_node;
+                        }
+                    }
+
+                  parent_elt.removeChild (node);
+                }
+            }
+        }
+      else
+        split_strings (elt);
+    }
+}
+
 int main(int argc, char *argv[])
 {
   const char *doc = "See \"octave-svgconvert -h\"";
@@ -771,8 +505,7 @@
   if (! document.setContent (&file, false, &msg))
     {
       std::cerr << "Failed to parse XML contents" << std::endl
-                << msg.toStdString ();
-      std::cerr << doc;
+                << msg.toStdString () << std::endl;
       file.close();
       return -1;
     }
@@ -840,11 +573,18 @@
   // Draw
   if (! strcmp (argv[2], "pdf"))
     {
+      // Remove clippath which is not supported in svg-tiny
+      QDomNodeList      lst = root.elementsByTagName ("clipPath");
+      for (int ii = lst.count (); ii > 0; ii--)
+        lst.at (ii-1).parentNode ().removeChild (lst.at (ii-1));
+
+      // Split text strings into single characters with single x coordinates
+      split_strings (root);
+
       // PDF painter
       pdfpainter painter (fout.fileName (), vp, dpi);
 
-      draw (root, painter);
-      painter.finish ();
+      painter.render (document.toByteArray ());
     }
   else
     {
