changeset 31798:82128f652585 stable

print: Add option to merge only subsequent triangles with SVG toolchain (bug #63646). Trying to merge all triangles that are sharing an edge might take a long time. But that might be necessary to avoid hairlines in figures containing patch or surface graphics objects. Allow selecting if no, all, or only subsequent triangles sharing an edge should be merged into a polygon. * src/octave-svgconvert.cc (main): Distinguish between merging no (0), only consecutive (1), or all (2) triangles into polygons. (octave_polygon::reconstruct): Skip numerically expensive part of trying to merge all polygons that are sharing an edge unless it was selected. * scripts/plot/util/print.m: Document new options "-polymerge", "-nopolymerge", and "-polymerge-all" for polygon merging with the SVG toolchain. (svgconvert): Call "octave-svgconvert" with the selected polygon merge mode. * scripts/plot/private/__print_parge_opts__.m: Select default value and parse input for new options.
author Markus Mützel <markus.muetzel@gmx.de>
date Tue, 31 Jan 2023 20:06:43 +0100
parents 8b869c5d6ce8
children 8825cedf5482 ed47b16ff624
files scripts/plot/util/print.m scripts/plot/util/private/__print_parse_opts__.m src/octave-svgconvert.cc
diffstat 3 files changed, 45 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/plot/util/print.m	Mon Jan 30 18:54:27 2023 +0100
+++ b/scripts/plot/util/print.m	Tue Jan 31 20:06:43 2023 +0100
@@ -157,6 +157,20 @@
 ## Caution: If Octave was built against Qt version earlier than 5.13,
 ## @option{-svgconvert} may lead to inaccurate rendering of image objects.
 ##
+## @item -polymerge
+## @itemx -nopolymerge
+## @itemx -polymerge-all
+##   When using the SVG based backend @option{-svgconvert}, faces are rendered
+## as triangles.  In some cases, some viewers might display fine lines where
+## those triangles share an edge.  These options control whether all triangles
+## that share edges are merged into polygons (@option{-polymerge-all} which
+## might take some time for graphics consisting of many triangles -- including
+## line markers), only consecutive polygons are merged (@option{-polymerge}),
+## or no triangles are merged at all (@option{-no-polymerge}).  By default,
+## only consecutive triangles sharing an edge are merged, unless the printed
+## figure contains patch or surface graphics objects in which case all
+## triangles that are sharing an edge are merged.
+##
 ## @item  -portrait
 ## @itemx -landscape
 ##   Specify the orientation of the plot for printed output.
@@ -1160,7 +1174,8 @@
     cmd = sprintf ('%s - %%s %3.2f "%s" %d "%%s"', ...
                    undo_string_escapes (opts.svgconvert_binary), ...
                    get (0, "screenpixelsperinch"), ...
-                   undo_string_escapes (fullfile (fontdir, "FreeSans.otf")), 1);
+                   undo_string_escapes (fullfile (fontdir, "FreeSans.otf")),
+                   opts.polymerge);
 
     if (opts.debug)
       fprintf ("svgconvert command: '%s'\n", cmd);
--- a/scripts/plot/util/private/__print_parse_opts__.m	Mon Jan 30 18:54:27 2023 +0100
+++ b/scripts/plot/util/private/__print_parse_opts__.m	Tue Jan 31 20:06:43 2023 +0100
@@ -59,6 +59,7 @@
   arg_st.ghostscript.antialiasing_textalphabits = 4;
   arg_st.ghostscript.antialiasing_graphicsalphabits = 1;
   arg_st.lpr_binary = __quote_path__ (__find_binary__ ("lpr"));
+  arg_st.polymerge = 1;
   arg_st.name = "";
   arg_st.orientation = "";
   arg_st.pstoedit_binary = __quote_path__ (__find_binary__ ("pstoedit"));
@@ -88,6 +89,11 @@
     varargin(1) = [];
   endif
 
+  if (! isempty (findall (arg_st.figure, "type", "patch", ...
+                          "-or", "type", "surface")))
+    arg_st.polymerge = 2;
+  endif
+
   for i = 1:numel (varargin)
     if (! ischar (varargin{i}) && ! iscellstr (varargin{i}))
       error ("print: input arguments must be a graphics handle or strings.");
@@ -127,6 +133,12 @@
         arg_st.svgconvert = true;
       elseif (strcmp (arg, "-nosvgconvert"))
         arg_st.svgconvert = false;
+      elseif (strcmp (arg, "-polymerge"))
+        arg_st.polymerge = 1;
+      elseif (strcmp (arg, "-nopolymerge"))
+        arg_st.polymerge = 0;
+      elseif (strcmp (arg, "-polymerge-all"))
+        arg_st.polymerge = 2;
       elseif (strcmp (arg, "-textspecial"))
         arg_st.special_flag = "textspecial";
       elseif (strcmp (arg, "-fillpage"))
--- a/src/octave-svgconvert.cc	Mon Jan 30 18:54:27 2023 +0100
+++ b/src/octave-svgconvert.cc	Tue Jan 31 20:06:43 2023 +0100
@@ -165,10 +165,12 @@
   void reset (void)
   { m_polygons.clear (); }
 
-  QList<QPolygonF> reconstruct (void)
+  QList<QPolygonF> reconstruct (int reconstruct_level)
   {
     if (m_polygons.isEmpty ())
       return QList<QPolygonF> ();
+    else if (reconstruct_level < 2)
+      return m_polygons;
 
     // Once a polygon has been merged to another, it is marked unsuded
     QVector<bool> unused;
@@ -753,7 +755,7 @@
     parent_elt.removeChild (orig.at (ii));
 }
 
-void reconstruct_polygons (QDomElement& parent_elt)
+void reconstruct_polygons (QDomElement& parent_elt, int reconstruct_level)
 {
   QDomNodeList nodes = parent_elt.childNodes ();
   QColor current_color;
@@ -791,7 +793,8 @@
               if (color != current_color)
                 {
                   // Reconstruct the previous series of triangles
-                  QList<QPolygonF> polygons = current_polygon.reconstruct ();
+                  QList<QPolygonF> polygons
+                    = current_polygon.reconstruct (reconstruct_level);
                   collection.push_back (QPair<QList<QDomNode>,QList<QPolygonF> >
                                         (replaced_nodes, polygons));
 
@@ -810,19 +813,20 @@
         {
           if (current_polygon.count ())
             {
-              QList<QPolygonF> polygons = current_polygon.reconstruct ();
+              QList<QPolygonF> polygons = current_polygon.reconstruct (reconstruct_level);
               collection.push_back (QPair<QList<QDomNode>,QList<QPolygonF> >
                                     (replaced_nodes, polygons));
               replaced_nodes.clear ();
               current_polygon.reset ();
             }
-          reconstruct_polygons (elt);
+          reconstruct_polygons (elt, reconstruct_level);
         }
     }
 
   // Finish
   collection.push_back (QPair<QList<QDomNode>,QList<QPolygonF> >
-                        (replaced_nodes, current_polygon.reconstruct ()));
+                          (replaced_nodes,
+                           current_polygon.reconstruct (reconstruct_level)));
 
   for (int ii = 0; ii < collection.count (); ii++)
     replace_polygons (parent_elt, collection[ii].first, collection[ii].second);
@@ -880,7 +884,10 @@
 * fmt: format of the output file. May be one of pdf or svg\n\
 * dpi: device dependent resolution in screen pixel per inch\n\
 * font: specify a file name for the default FreeSans font\n\
-* reconstruct: specify whether to reconstruct triangle to polygons (0 or 1)\n\
+* reconstruct: specify whether to reconstruct triangle to polygons\n\
+  0: no reconstruction (merging) of polygons\n\
+  1: merge consecutive triangles if they share an edge\n\
+  2: merge all triangles that share edges (might take a long time)\n\
 * outfile: output file name\n";
 
   if (strcmp (argv[1], "-h") == 0)
@@ -981,8 +988,9 @@
     }
 
   // Do basic polygons reconstruction
-  if (QString (argv[5]).toInt ())
-    reconstruct_polygons (root);
+  int reconstruct_level = QString (argv[5]).toInt ();
+  if (reconstruct_level)
+    reconstruct_polygons (root, reconstruct_level);
 
   // Add custom properties to SVG
   add_custom_properties (root);