diff libinterp/corefcn/gl2ps-print.cc @ 29470:2ae4764180c6

Initial implementation of a LaTeX interpreter (bug #59546). * NEWS: Announce basic support for "latex" interpreter. * plot.txi: Restructure the "Use of the interpreter Property" section to include 3 subsections, one for each interpreter type. In "Printing and Saving Plots" describe the new behavior. * print.m: Document new behavior. * 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, base-text-renderer.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::format_svg_element): New function to manipulate svg elements obtained from dvisvgm so as to position them properly on the figure and change their color. (gl2ps_renderer::strlist_to_svg): If the str_list object returned from text_to_strlist contains an svg element, use format_svg_element to handle position and color and then return it. Otherwise, use <g> and <text> elements rather than <text> and <tspan> elements which are not supported by Qt's svg renderer (svg-tiny implementation). (gl2ps_renderer::strlist_to_ps): If the str_list object returned from text_to_strlist contains 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 (text_renderer): 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. (text_renderer::string::svg_element, text_renderer::string::set_svg_element, text_renderer::string::get_svg_element): New string data member to hold preformated svg element. Provide accessor methods. * 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.
author Pantxo Diribarne <pantxo.diribarne@gmail.com>
date Mon, 22 Mar 2021 21:32:54 +0100
parents 7854d5752dd2
children aef11bb4e6d1
line wrap: on
line diff
--- a/libinterp/corefcn/gl2ps-print.cc	Mon Mar 29 07:54:26 2021 +0200
+++ b/libinterp/corefcn/gl2ps-print.cc	Mon Mar 22 21:32:54 2021 +0100
@@ -64,8 +64,8 @@
 
     gl2ps_renderer (opengl_functions& glfcns, FILE *_fp,
                     const std::string& _term)
-      : opengl_renderer (glfcns), fp (_fp), term (_term),
-        fontsize (), fontname (), buffer_overflow (false)
+      : opengl_renderer (glfcns), fp (_fp), term (_term), fontsize (),
+        fontname (), buffer_overflow (false), m_svg_def_index (0)
     { }
 
     ~gl2ps_renderer (void) = default;
@@ -266,7 +266,11 @@
                                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 format_svg_element (std::string str, Matrix bbox,
+                                    double rotation, ColumnVector coord_pix,
+                                    Matrix color);
+
     std::string strlist_to_svg (double x, double y, double z, Matrix box,
                                 double rotation,
                                 std::list<text_renderer::string>& lst);
@@ -283,6 +287,7 @@
     double fontsize;
     std::string fontname;
     bool buffer_overflow;
+    std::size_t m_svg_def_index;
   };
 
   static bool
@@ -487,8 +492,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 +500,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 +798,134 @@
   }
 
   std::string
+  gl2ps_renderer::format_svg_element (std::string str, Matrix box,
+                                      double rotation, ColumnVector coord_pix,
+                                      Matrix color)
+  {
+    // 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;
+
+    // Fill color
+    std::string fill = "fill='rgb("
+      + std::to_string (static_cast<uint8_t> (color(0) * 255.0)) + ","
+      + std::to_string (static_cast<uint8_t> (color(1) * 255.0)) + ","
+      + std::to_string (static_cast<uint8_t> (color(2) * 255.0)) + ")' ";
+
+    std::string use_group = "<g "
+      + fill
+      + "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)
   {
+    //Use pixel coordinates to conform to gl2ps
+    ColumnVector coord_pix = get_transform ().transform (x, y, z, false);
+
     if (lst.empty ())
       return "";
 
-    //Use pixel coordinates to conform to gl2ps
-    ColumnVector coord_pix = get_transform ().transform (x, y, z, false);
+    // This may already be an svg image.
+    std::string svg = lst.front ().get_svg_element ();
+    if (! svg.empty ())
+      return format_svg_element (svg, box, rotation, coord_pix,
+                                 lst.front ().get_color ());
 
+    // Rotation and translation are applied to the whole group
     std::ostringstream os;
-    os << R"(<text xml:space="preserve" )";
-
-    // Rotation and translation are applied to the whole text element
+    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)
@@ -826,10 +944,10 @@
        << "font-size=\"" << size << "\">";
 
 
-    // build a tspan for each element in the strlist
+    // Build a text element 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 +1000,9 @@
                   os << chr.str ();
               }
           }
-        os << "</tspan>";
+        os << "</text>";
       }
-    os << "</text>";
+    os << "</g>";
 
     return os.str ();
   }
@@ -894,6 +1012,26 @@
                                  Matrix box, double rotation,
                                  std::list<text_renderer::string>& lst)
   {
+    if (lst.empty ())
+      return "";
+    else if (lst.size () == 1)
+      {
+        static bool warned = false;
+        // This may be an svg image, not handled in native eps format.
+        if (! lst.front ().get_svg_element ().empty ())
+          {
+            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);