# HG changeset patch
# User Pantxo Diribarne <pantxo.diribarne@gmail.com>
# Date 1677102462 -3600
#      Wed Feb 22 22:47:42 2023 +0100
# Node ID 525d052ba1b82c80139f5605a21be05fa93f14b4
# Parent  79abf4cdcd95886e7a10d2ff145f988d3dc15544
Allow sending figures to printer without resorting to lpr (bug #63813)

* octave-svgconvert (main): Allow a second calling form with two additionnal
  page formating arguments "paper_size" and "bottom_left". Allow "printer" for
  the "fmt" argument.
  (pdfpainter::pdfpainter): Change arguments to include the page_layout and
  eventually send to printer rather than file.

* print.m (svgconvert): Use the new octave-svgconvert calling form for printing.

* __opengl_print__.m: Avoid calling lpr when using the svgconvert toolchain and
  sending to printer.

diff -r 79abf4cdcd95 -r 525d052ba1b8 scripts/plot/util/print.m
--- a/scripts/plot/util/print.m	Sun Feb 19 17:19:24 2023 +0100
+++ b/scripts/plot/util/print.m	Wed Feb 22 22:47:42 2023 +0100
@@ -1135,7 +1135,7 @@
       cmd = sprintf ("%s -f %s 2> /dev/null", opts.pstoedit_binary, devopt);
     endif
   else
-    cmd = svgconvert (opts, devopt);
+    cmd = svgconvert (opts);
     if (dos_shell)
       cmd = sprintf ('%s & %s -ssp -f %s "%%s" 2> NUL', cmd, ...
                      undo_string_escapes (opts.pstoedit_binary), ...
@@ -1156,10 +1156,6 @@
 
   cmd = "";
 
-  if (nargin < 2)
-    devopt = opts.devopt;
-  endif
-
   if (isempty (opts.svgconvert_binary))
     warning ("Octave:print:nosvgconvert", ...
              ["print: unable to find octave-svgconvert, ", ...
@@ -1171,11 +1167,25 @@
       fontdir = __octave_config_info__ ("octfontsdir");
     endif
 
-    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")),
-                   opts.polymerge);
+    if (! opts.send_to_printer)
+      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")),
+                     opts.polymerge);
+    else
+      saved_units = get (opts.figure, "paperunits");
+      set (opts.figure, "paperunits", "points");
+      papersize_points = get (opts.figure, "papersize");
+      set (opts.figure, "paperunits", saved_units);
+
+      cmd = sprintf ('%s - %%s %3.2f "%s" %d "%%s" %f,%f %f,%f', ...
+                     undo_string_escapes (opts.svgconvert_binary), ...
+                     get (0, "screenpixelsperinch"), ...
+                     undo_string_escapes (fullfile (fontdir, "FreeSans.otf")),
+                     opts.polymerge, papersize_points, ...
+                     opts.ghostscript.pageoffset);
+    endif
 
     if (opts.debug)
       fprintf ("svgconvert command: '%s'\n", cmd);
diff -r 79abf4cdcd95 -r 525d052ba1b8 scripts/plot/util/private/__opengl_print__.m
--- a/scripts/plot/util/private/__opengl_print__.m	Sun Feb 19 17:19:24 2023 +0100
+++ b/scripts/plot/util/private/__opengl_print__.m	Wed Feb 22 22:47:42 2023 +0100
@@ -86,7 +86,7 @@
       gl2ps_device = {"svg"};
       svgcmd = "";
       if (opts.svgconvert)
-        svgcmd = opts.svgconvert_cmd (opts, opts.ghostscript.device);
+        svgcmd = opts.svgconvert_cmd (opts);
       endif
       if (! isempty (svgcmd))
         pipeline = {sprintf(svgcmd, "svg", opts.name)};
@@ -140,16 +140,26 @@
     case opts.ghostscript.device
       svgcmd = "";
       if (opts.svgconvert)
-        svgcmd = opts.svgconvert_cmd (opts, opts.ghostscript.device);
+        svgcmd = opts.svgconvert_cmd (opts);
       endif
       dosvg = ! isempty (svgcmd);
       if (! dosvg)
         opts.ghostscript.source = "-";
       else
-        tmp = tempname ();
-        opts.ghostscript.source = tmp;
-        opts.unlink = [opts.unlink tmp];
-        svgcmd = sprintf (svgcmd, "pdf", tmp);
+        if (! opts.send_to_printer)
+          if (strcmp (opts.devopt, "pdfwrite") && ! opts.formatted_for_printing)
+            svgcmd = sprintf (svgcmd, "pdf", opts.name);
+          else
+            tmp = tempname ();
+            opts.ghostscript.source = tmp;
+            opts.unlink = [opts.unlink tmp];
+            svgcmd = sprintf (svgcmd, "pdf", tmp);
+          endif
+        elseif (isempty (opts.printer))
+          svgcmd = sprintf (svgcmd, "printer", "-");
+        else
+          svgcmd = sprintf (svgcmd, "printer", opts.printer(3:end));
+        endif
       endif
 
       opts.ghostscript.output = opts.name;
@@ -159,15 +169,20 @@
       endif
 
       [cmd_gs, cmd_cleanup] = __ghostscript__ (opts.ghostscript);
-      if (opts.send_to_printer || isempty (opts.name))
-        cmd_lpr = opts.lpr_cmd (opts);
-        cmd = sprintf ("%s | %s", cmd_gs, cmd_lpr);
-      elseif (dosvg)
-        if (dos_shell)
+
+      if (dosvg)
+        if (opts.send_to_printer ||
+            (strcmp (opts.devopt, "pdfwrite") && ! opts.formatted_for_printing))
+          cmd = svgcmd;
+          cmd_cleanup = [];
+        elseif (dos_shell)
           cmd = sprintf ("%s & %s", svgcmd, cmd_gs);
         else
           cmd = sprintf ("%s ; %s", svgcmd, cmd_gs);
         endif
+      elseif (opts.send_to_printer || isempty (opts.name))
+        cmd_lpr = opts.lpr_cmd (opts);
+        cmd = sprintf ("%s | %s", cmd_gs, cmd_lpr);
       else
         cmd = sprintf ("%s", cmd_gs);
       endif
diff -r 79abf4cdcd95 -r 525d052ba1b8 src/octave-svgconvert.cc
--- a/src/octave-svgconvert.cc	Sun Feb 19 17:19:24 2023 +0100
+++ b/src/octave-svgconvert.cc	Wed Feb 22 22:47:42 2023 +0100
@@ -53,27 +53,65 @@
 class pdfpainter : public QPainter
 {
 public:
-  pdfpainter (QString fname, QRectF sz)
-      :  m_printer ()
+
+  pdfpainter (QString name, QRectF page_layout, bool send_to_printer)
+    :  m_printer (), m_printer_found (true)
   {
-    // Printer settings
-    m_printer.setOutputFormat (QPrinter::PdfFormat);
-    m_printer.setFontEmbeddingEnabled (true);
-    m_printer.setOutputFileName (fname);
+    if (send_to_printer)
+      {
+        m_printer.setOutputFormat (QPrinter::NativeFormat);
+
+        if (name != QString ("-"))
+          m_printer.setPrinterName (name);
+
+        QString actual_name = m_printer.printerName ();
+
+        if (name == QString ("-") && actual_name.isEmpty ())
+          {
+            std::cerr << "error: svgconvert: unable to find a default printer"
+                      << std::endl;
+            m_printer_found = false;
+            return;
+          }
+        else if (name != QString ("-") && name != actual_name)
+          {
+            std::cerr << "error: svgconvert: unknown printer "
+                      << name.toStdString () << std::endl;
+            m_printer_found = false;
+            return;
+          }
+      }
+    else
+      {
+        m_printer.setOutputFormat (QPrinter::PdfFormat);
+        m_printer.setOutputFileName (name);
+        m_printer.setFontEmbeddingEnabled (true);
+      }
+
+
+    // Let Qt guess the appropriate paper type from size
+    m_printer.setPageLayout (QPageLayout (QPageSize (page_layout.size (),
+                                                     QPageSize::Point),
+                                          QPageLayout::Portrait,
+                                          QMargins (0.0, 0.0, 0.0, 0.0)));
     m_printer.setFullPage (true);
-    m_printer.setPageSize (QPageSize (sz.size (), QPageSize::Point,
-                                      QString ("custom"),
-                                      QPageSize::ExactMatch));
 
     // Painter settings
     begin (&m_printer);
-    setWindow (sz.toRect ());
+    setWindow (page_layout.toRect ());
   }
 
-  ~pdfpainter () { end (); }
+  ~pdfpainter ()
+  {
+    if (m_printer_found)
+      end ();
+  }
+
+  bool ok () { return m_printer_found; }
 
 private:
   QPrinter m_printer;
+  bool m_printer_found;
 };
 
 // String conversion functions+QVector<double> qstr2vectorf (QString str)
@@ -845,6 +883,20 @@
 
 }
 
+QSizeF qsize_from_qstring (QString str)
+{
+  QSizeF sz;
+  QStringList coords = str.split (",");
+
+  if (coords.count () != 2)
+    // default to "letter"
+    sz = QSizeF (612, 792);
+  else
+    sz = QSizeF (coords.at (0).toDouble (), coords.at (1).toDouble ());
+
+  return sz;
+}
+
 #if defined (OCTAVE_USE_WINDOWS_API) && defined (_UNICODE)
 extern "C"
 int
@@ -871,24 +923,43 @@
   const char *doc = "See \"octave-svgconvert -h\"";
   const char *help = "Usage:\n\
 octave-svgconvert infile fmt dpi font reconstruct outfile\n\n\
-Convert svg file to pdf, or svg. All arguments are mandatory:\n\
+octave-svgconvert infile fmt dpi font reconstruct printer \
+paper_type bottom_left \n\n\
+\n\
+Convert an octave svg file to a cropped PDF or SVG  file(first calling form), \
+or prepare it for printing with a surounding page and output to a PDF file \
+or a printer (second calling form):\n\
 * infile: input svg file or \"-\" to indicate that the input svg file should be \
 read from stdin\n\
-* fmt: format of the output file. May be one of pdf or svg\n\
+* fmt: format of the output file. May be one of 'pdf', 'svg' or 'printer'\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\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";
+* outfile: output file name\n\
+* printer: name of the printer ('-', for the default printer)\n\
+* paper_size: size of the output page in points specified as \
+a comma separated pair 'width,height'\n\
+* bottom_left: position of the anchor of the bottom left corner of the figure \
+(in points) specified as a comma separated pair 'x0,y0'\n\n";
 
   if (strcmp (argv[1], "-h") == 0)
     {
       std::cout << help;
       return 0;
     }
-  else if (argc != 7)
+
+  if (argc != 7 && argc != 9)
+    {
+      std::cerr << help;
+      return -1;
+    }
+
+  bool send_to_printer = ! strcmp (argv[2], "printer");
+
+  if (argc != 9 && send_to_printer)
     {
       std::cerr << help;
       return -1;
@@ -932,7 +1003,8 @@
   file.close ();
 
   // Format
-  if (strcmp (argv[2], "pdf") != 0 && strcmp (argv[2], "svg") != 0)
+  if (strcmp (argv[2], "pdf") != 0 && strcmp (argv[2], "svg") != 0
+      && strcmp (argv[2], "printer") != 0)
     {
       std::cerr << "Unhandled output file format " << argv[2] << "\n";
       std::cerr << doc;
@@ -971,15 +1043,6 @@
             std::cerr << "warning: print: FreeSans font not found\n";
         }
     }
-
-  // First render in a temporary file
-  QTemporaryFile fout;
-  if (! fout.open ())
-    {
-      std::cerr << "Could not open temporary file\n";
-      return -1;
-    }
-
   // Do basic polygons reconstruction
   int reconstruct_level = QString (argv[5]).toInt ();
   if (reconstruct_level)
@@ -988,13 +1051,45 @@
   // Add custom properties to SVG
   add_custom_properties (root);
 
+
+  // First render in a temporary file
+  QTemporaryFile fout;
+  if (! send_to_printer && ! fout.open ())
+    {
+      std::cerr << "Could not open temporary file\n";
+      return -1;
+    }
+
   // Draw
-  if (! strcmp (argv[2], "pdf"))
+  if (! strcmp (argv[2], "pdf") || send_to_printer)
     {
-      // PDF painter
-      pdfpainter painter (fout.fileName (), vp);
+      QRectF page_layout = vp;
+
+      if (argc == 9)
+        {
+          QSizeF sz (qsize_from_qstring (QString (argv[7])));
+          QSizeF anchor (qsize_from_qstring (QString (argv[8])));
+
+          page_layout.setX (- anchor.width ());
+          page_layout.setY (- (sz.height () - anchor.height () - vp.height ()));
+
+          page_layout.setWidth (sz.width ());
+          page_layout.setHeight (sz.height ());
+        }
 
-      draw (root, painter);
+      QString name;
+
+      if (send_to_printer)
+        name = QString (argv[6]).trimmed ();
+      else
+        name = fout.fileName ();
+
+      pdfpainter painter (name, page_layout, send_to_printer);
+
+      if (painter.ok ())
+        draw (root, painter);
+      else
+        return -1;
     }
   else
     {
@@ -1004,15 +1099,18 @@
       out << document.toByteArray ();
     }
 
-  // Delete output file before writing with new data
-  if (QFile::exists (argv[6]))
-    if (! QFile::remove (argv[6]))
-      {
-        std::cerr << "Unable to replace existing file " << argv[6] << "\n";
-        return -1;
-      }
+  if (! send_to_printer)
+    {
+      // Delete output file before writing with new data
+      if (QFile::exists (argv[6]))
+        if (! QFile::remove (argv[6]))
+          {
+            std::cerr << "Unable to replace existing file " << argv[6] << "\n";
+            return -1;
+          }
 
-  fout.copy (argv[6]);
+      fout.copy (argv[6]);
+    }
 
   return 0;
 }
