view src/octave-svgconvert.cc @ 31771:21f9b34eb893

maint: Eliminate "(void)" in C++ function prototypes/declarations. * mk-opts.pl, external.txi, embedded.cc, make_int.cc, standalone.cc, standalonebuiltin.cc, BaseControl.cc, BaseControl.h, ButtonControl.cc, ButtonControl.h, ButtonGroup.cc, ButtonGroup.h, Canvas.cc, Canvas.h, CheckBoxControl.cc, CheckBoxControl.h, Container.cc, Container.h, ContextMenu.cc, ContextMenu.h, EditControl.cc, EditControl.h, Figure.cc, Figure.h, FigureWindow.cc, FigureWindow.h, GLCanvas.cc, GLCanvas.h, GenericEventNotify.h, KeyMap.cc, ListBoxControl.cc, ListBoxControl.h, Logger.cc, Logger.h, Menu.cc, Menu.h, MenuContainer.h, Object.cc, Object.h, ObjectProxy.cc, ObjectProxy.h, Panel.cc, Panel.h, PopupMenuControl.cc, PopupMenuControl.h, PushButtonControl.cc, PushButtonControl.h, PushTool.cc, PushTool.h, RadioButtonControl.cc, RadioButtonControl.h, SliderControl.cc, SliderControl.h, Table.cc, Table.h, TextControl.cc, TextControl.h, TextEdit.h, ToggleButtonControl.cc, ToggleButtonControl.h, ToggleTool.cc, ToggleTool.h, ToolBar.cc, ToolBar.h, ToolBarButton.cc, ToolBarButton.h, gl-select.cc, gl-select.h, qopengl-functions.h, qt-graphics-toolkit.h, qdialog.cpp, qfontdialog.cpp, qprintdialog_win.cpp, liboctgui-build-info.h, liboctgui-build-info.in.cc, color-picker.cc, color-picker.h, command-widget.cc, command-widget.h, community-news.cc, community-news.h, dialog.cc, dialog.h, documentation-bookmarks.cc, documentation-bookmarks.h, documentation-dock-widget.cc, documentation-dock-widget.h, documentation.cc, documentation.h, dw-main-window.cc, dw-main-window.h, external-editor-interface.cc, external-editor-interface.h, files-dock-widget.cc, files-dock-widget.h, find-files-dialog.cc, find-files-dialog.h, find-files-model.cc, find-files-model.h, gui-preferences.cc, gui-preferences.h, gui-settings.cc, gui-settings.h, history-dock-widget.cc, history-dock-widget.h, interpreter-qobject.cc, interpreter-qobject.h, file-editor-interface.h, file-editor-tab.cc, file-editor-tab.h, file-editor.cc, file-editor.h, find-dialog.cc, find-dialog.h, marker.cc, marker.h, octave-qscintilla.cc, octave-qscintilla.h, octave-txt-lexer.cc, octave-txt-lexer.h, main-window.cc, main-window.h, news-reader.cc, news-reader.h, octave-dock-widget.cc, octave-dock-widget.h, octave-qobject.cc, octave-qobject.h, qt-application.cc, qt-application.h, qt-interpreter-events.cc, qt-interpreter-events.h, release-notes.cc, release-notes.h, set-path-dialog.cc, set-path-dialog.h, set-path-model.cc, set-path-model.h, settings-dialog.cc, settings-dialog.h, shortcuts-tree-widget.cc, shortcuts-tree-widget.h, tab-bar.cc, tab-bar.h, terminal-dock-widget.cc, terminal-dock-widget.h, variable-editor-model.cc, variable-editor-model.h, variable-editor.cc, variable-editor.h, welcome-wizard.cc, welcome-wizard.h, workspace-model.cc, workspace-model.h, workspace-view.cc, workspace-view.h, build-env.h, Cell.cc, Cell.h, __contourc__.cc, __magick_read__.cc, auto-shlib.cc, auto-shlib.h, base-text-renderer.h, bsxfun.cc, c-file-ptr-stream.cc, c-file-ptr-stream.h, call-stack.cc, call-stack.h, debug.cc, defaults.cc, defaults.h, defun.cc, display.cc, display.h, dynamic-ld.cc, dynamic-ld.h, environment.cc, environment.h, error.cc, error.h, errwarn.cc, errwarn.h, event-manager.cc, event-manager.h, event-queue.cc, event-queue.h, fcn-info.cc, fcn-info.h, ft-text-renderer.cc, ft-text-renderer.h, genprops.awk, gh-manager.cc, gh-manager.h, gl-render.cc, gl-render.h, gl2ps-print.cc, graphics-toolkit.h, graphics.cc, graphics.in.h, gtk-manager.cc, gtk-manager.h, help.cc, help.h, hook-fcn.h, input.cc, input.h, interpreter-private.cc, interpreter-private.h, interpreter.cc, interpreter.h, jsondecode.cc, latex-text-renderer.cc, latex-text-renderer.h, load-path.cc, load-path.h, load-save.cc, load-save.h, ls-hdf5.cc, ls-hdf5.h, mxarray.h, oct-errno.h, oct-errno.in.cc, oct-fstrm.cc, oct-fstrm.h, oct-handle.h, oct-hist.cc, oct-hist.h, oct-iostrm.cc, oct-iostrm.h, oct-map.cc, oct-map.h, oct-opengl.h, oct-prcstrm.cc, oct-prcstrm.h, oct-procbuf.cc, oct-procbuf.h, oct-process.h, oct-stdstrm.h, oct-stream.cc, oct-stream.h, oct-strstrm.cc, oct-strstrm.h, oct-tex-lexer.in.ll, pager.cc, pager.h, pr-flt-fmt.cc, pr-flt-fmt.h, pr-output.cc, pr-output.h, procstream.cc, procstream.h, settings.cc, settings.h, sighandlers.cc, sighandlers.h, stack-frame.cc, stack-frame.h, svd.cc, syminfo.cc, syminfo.h, symrec.cc, symrec.h, symscope.cc, symscope.h, symtab.cc, symtab.h, sysdep.cc, sysdep.h, text-engine.cc, text-engine.h, text-renderer.cc, text-renderer.h, toplev.cc, url-handle-manager.cc, url-handle-manager.h, variables.cc, xpow.cc, __init_fltk__.cc, __init_gnuplot__.cc, __ode15__.cc, audiodevinfo.cc, gzip.cc, liboctinterp-build-info.h, liboctinterp-build-info.in.cc, mk-build-env-features.sh, mk-builtins.pl, cdef-class.cc, cdef-class.h, cdef-manager.h, cdef-method.cc, cdef-method.h, cdef-object.cc, cdef-object.h, cdef-package.cc, cdef-package.h, cdef-property.cc, cdef-property.h, cdef-utils.cc, cdef-utils.h, ov-base-diag.cc, ov-base-diag.h, ov-base-int.cc, ov-base-int.h, ov-base-mat.cc, ov-base-mat.h, ov-base-scalar.cc, ov-base-scalar.h, ov-base-sparse.cc, ov-base-sparse.h, ov-base.cc, ov-base.h, ov-bool-mat.cc, ov-bool-mat.h, ov-bool-sparse.cc, ov-bool-sparse.h, ov-bool.cc, ov-bool.h, ov-builtin.cc, ov-builtin.h, ov-cell.cc, ov-cell.h, ov-ch-mat.cc, ov-ch-mat.h, ov-class.cc, ov-class.h, ov-classdef.cc, ov-classdef.h, ov-colon.h, ov-complex.cc, ov-complex.h, ov-cs-list.h, ov-cx-diag.cc, ov-cx-diag.h, ov-cx-mat.cc, ov-cx-mat.h, ov-cx-sparse.cc, ov-cx-sparse.h, ov-dld-fcn.cc, ov-dld-fcn.h, ov-fcn-handle.cc, ov-fcn-handle.h, ov-fcn.cc, ov-fcn.h, ov-float.cc, ov-float.h, ov-flt-complex.cc, ov-flt-complex.h, ov-flt-cx-diag.cc, ov-flt-cx-diag.h, ov-flt-cx-mat.cc, ov-flt-cx-mat.h, ov-flt-re-diag.cc, ov-flt-re-diag.h, ov-flt-re-mat.cc, ov-flt-re-mat.h, ov-intx.h, ov-java.cc, ov-java.h, ov-lazy-idx.cc, ov-lazy-idx.h, ov-legacy-range.cc, ov-legacy-range.h, ov-magic-int.cc, ov-magic-int.h, ov-mex-fcn.cc, ov-mex-fcn.h, ov-null-mat.cc, ov-null-mat.h, ov-oncleanup.cc, ov-oncleanup.h, ov-perm.cc, ov-perm.h, ov-range.cc, ov-range.h, ov-re-diag.cc, ov-re-diag.h, ov-re-mat.cc, ov-re-mat.h, ov-re-sparse.cc, ov-re-sparse.h, ov-scalar.cc, ov-scalar.h, ov-str-mat.cc, ov-str-mat.h, ov-struct.cc, ov-struct.h, ov-typeinfo.cc, ov-typeinfo.h, ov-usr-fcn.cc, ov-usr-fcn.h, ov.cc, ov.h, ovl.cc, ovl.h, octave.cc, octave.h, anon-fcn-validator.h, bp-table.cc, bp-table.h, comment-list.cc, comment-list.h, filepos.h, lex.h, lex.ll, oct-lvalue.cc, oct-lvalue.h, oct-parse.yy, parse.h, profiler.cc, profiler.h, pt-anon-scopes.h, pt-arg-list.cc, pt-arg-list.h, pt-args-block.cc, pt-args-block.h, pt-array-list.cc, pt-array-list.h, pt-assign.cc, pt-assign.h, pt-binop.cc, pt-binop.h, pt-bp.h, pt-cbinop.h, pt-cell.h, pt-check.h, pt-classdef.cc, pt-classdef.h, pt-cmd.h, pt-colon.h, pt-const.h, pt-decl.cc, pt-decl.h, pt-eval.cc, pt-eval.h, pt-except.cc, pt-except.h, pt-exp.cc, pt-exp.h, pt-fcn-handle.cc, pt-fcn-handle.h, pt-id.cc, pt-id.h, pt-idx.cc, pt-idx.h, pt-jump.h, pt-loop.cc, pt-loop.h, pt-mat.h, pt-misc.cc, pt-misc.h, pt-pr-code.cc, pt-pr-code.h, pt-select.cc, pt-select.h, pt-spmd.cc, pt-spmd.h, pt-stmt.cc, pt-stmt.h, pt-tm-const.cc, pt-tm-const.h, pt-unop.cc, pt-unop.h, pt-walk.h, pt.cc, pt.h, token.cc, token.h, usage.h, Array-base.cc, Array.h, CColVector.cc, CColVector.h, CDiagMatrix.cc, CDiagMatrix.h, CMatrix.cc, CMatrix.h, CNDArray.cc, CNDArray.h, CRowVector.cc, CRowVector.h, CSparse.cc, CSparse.h, DiagArray2.cc, DiagArray2.h, MArray.cc, MArray.h, MDiagArray2.h, MSparse.h, MatrixType.cc, MatrixType.h, PermMatrix.cc, PermMatrix.h, Range.cc, Range.h, Sparse-b.cc, Sparse.cc, Sparse.h, boolMatrix.cc, boolMatrix.h, boolNDArray.cc, boolNDArray.h, boolSparse.cc, boolSparse.h, chMatrix.h, chNDArray.h, dColVector.cc, dColVector.h, dDiagMatrix.cc, dDiagMatrix.h, dMatrix.cc, dMatrix.h, dNDArray.cc, dNDArray.h, dRowVector.cc, dRowVector.h, dSparse.cc, dSparse.h, dim-vector.cc, dim-vector.h, fCColVector.cc, fCColVector.h, fCDiagMatrix.cc, fCDiagMatrix.h, fCMatrix.cc, fCMatrix.h, fCNDArray.cc, fCNDArray.h, fCRowVector.cc, fCRowVector.h, fColVector.cc, fColVector.h, fDiagMatrix.cc, fDiagMatrix.h, fMatrix.cc, fMatrix.h, fNDArray.cc, fNDArray.h, fRowVector.cc, fRowVector.h, idx-vector.cc, idx-vector.h, intNDArray.cc, intNDArray.h, liboctave-build-info.h, liboctave-build-info.in.cc, CollocWt.cc, CollocWt.h, DAE.h, DAEFunc.h, DAERT.h, DAERTFunc.h, DASPK.cc, DASPK.h, DASRT.cc, DASRT.h, DASSL.cc, DASSL.h, DET.h, EIG.h, LSODE.cc, LSODE.h, ODE.h, ODEFunc.h, ODES.h, ODESFunc.h, Quad.h, aepbalance.cc, aepbalance.h, base-dae.h, base-de.h, chol.cc, chol.h, eigs-base.cc, fEIG.h, gepbalance.h, gsvd.cc, gsvd.h, hess.h, lu.cc, lu.h, oct-fftw.cc, oct-fftw.h, oct-rand.cc, oct-rand.h, oct-spparms.cc, oct-spparms.h, qr.cc, qr.h, qrp.cc, qrp.h, randmtzig.cc, randmtzig.h, schur.h, sparse-chol.cc, sparse-chol.h, sparse-lu.cc, sparse-lu.h, sparse-qr.cc, sparse-qr.h, svd.cc, svd.h, child-list.cc, child-list.h, dir-ops.cc, dir-ops.h, file-ops.cc, file-ops.h, file-stat.cc, file-stat.h, lo-sysdep.cc, lo-sysdep.h, lo-sysinfo.cc, lo-sysinfo.h, mach-info.cc, mach-info.h, oct-env.cc, oct-env.h, oct-group.cc, oct-group.h, oct-password.cc, oct-password.h, oct-syscalls.cc, oct-syscalls.h, oct-time.cc, oct-time.h, oct-uname.cc, oct-uname.h, action-container.h, base-list.h, caseless-str.h, cmd-edit.cc, cmd-edit.h, cmd-hist.cc, cmd-hist.h, data-conv.cc, file-info.h, glob-match.cc, glob-match.h, kpse.cc, kpse.h, lo-array-errwarn.cc, lo-array-errwarn.h, lo-hash.cc, lo-hash.h, lo-ieee.cc, lo-ieee.h, lo-regexp.cc, lo-regexp.h, oct-inttypes.cc, oct-inttypes.h, oct-mutex.cc, oct-mutex.h, oct-refcount.h, oct-shlib.cc, oct-shlib.h, oct-sort.cc, oct-sort.h, oct-sparse.cc, octave-preserve-stream-state.h, pathsearch.cc, pathsearch.h, quit.cc, singleton-cleanup.cc, singleton-cleanup.h, str-vec.cc, str-vec.h, unwind-prot.cc, unwind-prot.h, url-transfer.cc, url-transfer.h, version.cc, version.in.h, cxx-signal-helpers.cc, acinclude.m4, main-cli.cc, main-gui.cc, main.in.cc, mkoctfile.in.cc, octave-build-info.h, octave-build-info.in.cc, octave-config.in.cc, octave-svgconvert.cc, shared-fcns.h: maint: Eliminate "(void)" in C++ function prototypes/declarations.
author Rik <rik@octave.org>
date Tue, 24 Jan 2023 17:19:44 -0800
parents 9cc509a00b54
children 8825cedf5482
line wrap: on
line source

////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2017-2023 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 <cstdio>
#include <iostream>

#if defined (OCTAVE_USE_WINDOWS_API)
#  include <vector>
#  include <locale>
#  include <codecvt>
#endif

#include <QtCore>
#include <QtXml>

#include <QApplication>
#include <QFontDatabase>
#include <QImage>
#include <QPainter>
#include <QPrinter>
#include <QRegExp>

// Include a set of path rendering functions extracted from Qt-5.12 source
#include "octave-qsvghandler.h"

// Render to pdf
class pdfpainter : public QPainter
{
public:
  pdfpainter (QString fname, QRectF sz)
      :  m_printer ()
  {
    // Printer settings
    m_printer.setOutputFormat (QPrinter::PdfFormat);
    m_printer.setFontEmbeddingEnabled (true);
    m_printer.setOutputFileName (fname);
    m_printer.setFullPage (true);
    m_printer.setPageSize (QPageSize (sz.size (), QPageSize::Point,
                                      QString ("custom"),
                                      QPageSize::ExactMatch));

    // Painter settings
    begin (&m_printer);
    setWindow (sz.toRect ());
  }

  ~pdfpainter () { end (); }

private:
  QPrinter m_printer;
};

// String conversion functions+QVector<double> qstr2vectorf (QString str)
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)
{
  QVector<QPointF> pts;
  str = str.trimmed ();
  str.replace (" ", ",");
  QStringList coords = str.split (",");
  for (QStringList::iterator p = coords.begin (); p != coords.end (); p += 2)
    {
      QPointF pt ((*p).toDouble (), (*(p+1)).toDouble ());
      pts.append (pt);
    }
  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
{
public:
  octave_polygon ()
  { }

  octave_polygon (QPolygonF p)
  { m_polygons.push_back (p); }

  ~octave_polygon () { }

  int count () const
  { return m_polygons.count (); }

  void reset ()
  { m_polygons.clear (); }

  QList<QPolygonF> reconstruct ()
  {
    if (m_polygons.isEmpty ())
      return QList<QPolygonF> ();

    // Once a polygon has been merged to another, it is marked unsuded
    QVector<bool> unused;
    for (auto it = m_polygons.begin (); it != m_polygons.end (); it++)
      unused.push_back (false);

    bool tryagain = (m_polygons.count () > 1);

    while (tryagain)
      {
        tryagain = false;
        for (auto ii = 0; ii < m_polygons.count (); ii++)
          {
            if (! unused[ii])
              {
                QPolygonF polygon = m_polygons[ii];
                for (auto jj = ii+1; jj < m_polygons.count (); jj++)
                  {
                    if (! unused[jj])
                      {
                        QPolygonF newpoly = mergepoly (polygon, m_polygons[jj]);
                        if (newpoly.count ())
                          {
                            polygon = newpoly;
                            m_polygons[ii] = newpoly;
                            unused[jj] = true;
                            tryagain = true;
                          }
                      }
                  }
              }
          }
      }

    // Try to remove cracks in polygons
    for (auto ii = 0; ii < m_polygons.count (); ii++)
      {
        QPolygonF polygon = m_polygons[ii];
        tryagain = ! unused[ii];

        while (tryagain && polygon.count () > 4)
          {
            tryagain = false;
            QVector<int> del;

            for (auto jj = 1; jj < (polygon.count () - 1); jj++)
              if (polygon[jj-1] == polygon[jj+1])
                {
                  if (! del.contains (jj))
                    del.push_front (jj);

                  del.push_front (jj+1);
                }

            for (auto idx : del)
              polygon.remove (idx);

            if (del.count ())
              tryagain = true;
          }
        m_polygons[ii] = polygon;
      }

    // FIXME: There may still be residual cracks, we should do something like
    //   resetloop = 2;
    //   while (resetloop)
    //     currface = shift (currface, 1);
    //     if (currface(1) == currface(3))
    //       currface([2 3]) = [];
    //       resetloop = 2;
    //     else
    //       resetloop--;
    //     endif
    //   endwhile

    QList<QPolygonF> retval;
    for (int ii = 0; ii < m_polygons.count (); ii++)
      {
        QPolygonF polygon = m_polygons[ii];
        if (! unused[ii] && polygon.count () > 2)
          retval.push_back (polygon);
      }

    return retval;
  }

  static inline
  bool eq (QPointF p1, QPointF p2)
  {
    return ((qAbs (p1.x () - p2.x ())
             <= 0.00001 * qMin (qAbs (p1.x ()), qAbs (p2.x ())))
            && (qAbs (p1.y () - p2.y ())
                <= 0.00001 * qMin (qAbs (p1.y ()), qAbs (p2.y ()))));
  }

  static
  QPolygonF mergepoly (QPolygonF poly1, QPolygonF poly2)
  {
    // Close polygon contour
    poly1.push_back (poly1[0]);
    poly2.push_back (poly2[0]);

    for (int ii = 0; ii < (poly1.size () - 1); ii++)
      {
        for (int jj = 0; jj < (poly2.size () - 1); jj++)
          {
            bool forward = (eq (poly1[ii], poly2[jj])
                            && eq (poly1[ii+1], poly2[jj+1]));
            bool backward = ! forward && (eq (poly1[ii], poly2[jj+1])
                                          && eq (poly1[ii+1], poly2[jj]));

            if (forward || backward)
              {
                // Unclose contour
                poly1.pop_back ();
                poly2.pop_back ();

                QPolygonF merged;
                for (int kk = 0; kk < (ii+1); kk++)
                  merged.push_back (poly1[kk]);

                // Shift vertices and eliminate the common edge
                std::rotate (poly2.begin (), poly2.begin () + jj, poly2.end ());
                poly2.erase (poly2.begin ());
                poly2.erase (poly2.begin ());

                if (forward)
                  for (int kk = poly2.size (); kk > 0; kk--)
                    merged.push_back (poly2[kk-1]);
                else
                  for (int kk = 0; kk < poly2.size (); kk++)
                    merged.push_back (poly2[kk]);

                for (int kk = ii+1; kk < poly1.size (); kk++)
                  merged.push_back (poly1[kk]);

                // Return row vector
                QPolygonF out (merged.size ());
                for (int kk = 0; kk < merged.size (); kk++)
                  out[kk] = merged[kk];

                return out;
              }
          }
      }
    return QPolygonF ();
  }

  void add (QPolygonF p)
  {
    if (m_polygons.count () == 0)
      m_polygons.push_back (p);
    else
      {
        QPolygonF tmp = mergepoly (m_polygons.back (), p);
        if (tmp.count ())
          m_polygons.back () = tmp;
        else
          m_polygons.push_back (p);
      }
  }

private:
  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;

  // Store path defined in <defs> in a map
  static bool in_defs = false;
  static QMap< QString, QPainterPath> path_map;

  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")
        {
          QString str = elt.attribute ("font-family");
          if (! str.isEmpty ())
            {
              // Font
              font = QFont ();
              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
            {
              bool current_clipstate = painter.hasClipping ();
              QRegion current_clippath = painter.clipRegion ();

              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);
                    }
                }

              // Fill color
              str = get_field (elt.attribute ("fill"), "rgb");
              if (! str.isEmpty ())
                {
                  QStringList clist = str.split (",");
                  painter.setBrush (QColor (clist[0].toInt (),
                                            clist[1].toInt (),
                                            clist[2].toInt ()));
                }

              // Transform
              str = elt.attribute ("transform");
              painter.save ();
              if (! str.isEmpty ())
                {
                  QStringRef tf (&str);
                  QTransform  tform =
                    parseTransformationMatrix (tf) * painter.transform ();
                  painter.setTransform (tform);
                }

              draw (elt, painter);

              // Restore previous clipping settings
              painter.restore  ();
              painter.setClipRegion (current_clippath);
              painter.setClipping (current_clipstate);
            }
        }
      else if (elt.tagName () == "defs")
        {
          in_defs = true;
          draw (elt, painter);
          in_defs = false;
        }
      else if (elt.tagName () == "path")
        {
          // Store QPainterPath for latter use
          QString id = elt.attribute ("id");
          if (! id.isEmpty ())
            {
              QString d = elt.attribute ("d");

              if (! d.isEmpty ())
                {
                  QStringRef data (&d);
                  QPainterPath path;
                  if (! parsePathDataFast (data, path))
                    continue; // Something went wrong, pass
                  else if (path.isEmpty ())
                    std::cout << "Empty path for data:"
                              << d.toStdString () << std::endl;
                  else if (in_defs)
                    path_map["#" + id] = path;
                  else
                    painter.drawPath (path);


                  if (path_map["#" + id].isEmpty ())
                    std::cout << "Empty path for data:"
                              << d.toStdString () << std::endl;
                }
            }
        }
      else if (elt.tagName () == "use")
        {
          painter.setPen (Qt::NoPen);

          QString str = elt.attribute ("xlink:href");
          if (! str.isEmpty () && str.size () > 2)
            {
              QPainterPath path = path_map[str];
              if (! path.isEmpty ())
                {
                  str = elt.attribute ("x");
                  double x = elt.attribute ("x").toDouble ();
                  str = elt.attribute ("y");
                  double y = elt.attribute ("y").toDouble ();
                  painter.translate (x, y);
                  painter.drawPath (path);
                  painter.translate (-x, -y);
                }
            }
        }
      else if (elt.tagName () == "text")
        {
          // 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 ();
              if (w > 0)
                pen.setWidthF (w);
            }

          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);
                  QTransform tform(m[0], m[1], m[2],
                                   m[3], m[4], m[5]);
                  painter.setTransform (tform);
                }

              painter.setRenderHint (QPainter::Antialiasing, false);
#if defined (HAVE_QPAINTER_RENDERHINT_LOSSLESS)
              painter.setRenderHint (QPainter::LosslessImageRendering);
#endif
              painter.drawImage (pos, img);
              painter.setRenderHint (QPainter::Antialiasing, true);
              painter.restore  ();
            }
        }
      else if (elt.tagName () == "rect")
        {
          // Position
          double x = elt.attribute ("x").toDouble ();
          double y = elt.attribute ("y").toDouble ();

          // Size
          double wd = elt.attribute ("width").toDouble ();
          double hg = elt.attribute ("height").toDouble ();

          // Color
          QColor saved_color = painter.brush ().color ();

          QString str = elt.attribute ("fill");
          if (! str.isEmpty ())
            painter.setBrush (QColor (str));

          painter.setPen (Qt::NoPen);

          painter.drawRect (QRectF (x, y, wd, hg));

          if (! str.isEmpty ())
            painter.setBrush (saved_color);
        }
      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

void replace_polygons (QDomElement& parent_elt, QList<QDomNode> orig,
                       QList<QPolygonF> polygons)
{
  if (! orig.count () || (orig.count () == polygons.count ()))
    return;

  QDomNode last = orig.last ();
  for (int ii = 0; ii < polygons.count (); ii++)
    {
      QPolygonF polygon = polygons[ii];

      QDomNode node = last.cloneNode ();

      QString pts;

      for (int jj = 0; jj < polygon.count (); jj++)
        {
          pts += QString ("%1,%2 ").arg (polygon[jj].x ())
                 .arg (polygon[jj].y ());
        }

      node.toElement ().setAttribute ("points", pts.trimmed ());

      if (! last.isNull ())
        last = parent_elt.insertAfter (node, last);
    }

  for (int ii = 0; ii < orig.count (); ii++)
    parent_elt.removeChild (orig.at (ii));
}

void reconstruct_polygons (QDomElement& parent_elt)
{
  QDomNodeList nodes = parent_elt.childNodes ();
  QColor current_color;
  QList<QDomNode> replaced_nodes;
  octave_polygon current_polygon;

  // Collection of child nodes to be removed and polygons to be added
  QList< QPair<QList<QDomNode>,QList<QPolygonF> > > collection;

  for (int ii = 0; ii < nodes.count (); ii++)
    {
      QDomNode node = nodes.at (ii);
      if (! node.isElement ())
        continue;

      QDomElement elt = node.toElement ();

      if (elt.tagName () == "polygon")
        {
          QString str = elt.attribute ("fill");
          if (! str.isEmpty ())
            {
              QColor color (str);
              str = elt.attribute ("fill-opacity");
              if (! str.isEmpty ())
                {
                  double alpha = str.toDouble ();
                  if (alpha != 1.0 && alpha >= 0.0)
                    color.setAlphaF (alpha);
                }

              if (! current_polygon.count ())
                current_color = color;

              if (color != current_color)
                {
                  // Reconstruct the previous series of triangles
                  QList<QPolygonF> polygons = current_polygon.reconstruct ();
                  collection.push_back (QPair<QList<QDomNode>,QList<QPolygonF> >
                                        (replaced_nodes, polygons));

                  replaced_nodes.clear ();
                  current_polygon.reset ();

                  current_color = color;
                }

              QPolygonF p (qstr2ptsvector (elt.attribute ("points")));
              current_polygon.add (p);
              replaced_nodes.push_back (node);
            }
        }
      else
        {
          if (current_polygon.count ())
            {
              QList<QPolygonF> polygons = current_polygon.reconstruct ();
              collection.push_back (QPair<QList<QDomNode>,QList<QPolygonF> >
                                    (replaced_nodes, polygons));
              replaced_nodes.clear ();
              current_polygon.reset ();
            }
          reconstruct_polygons (elt);
        }
    }

  // Finish
  collection.push_back (QPair<QList<QDomNode>,QList<QPolygonF> >
                        (replaced_nodes, current_polygon.reconstruct ()));

  for (int ii = 0; ii < collection.count (); ii++)
    replace_polygons (parent_elt, collection[ii].first, collection[ii].second);
}

void add_custom_properties (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 () == "image")
        elt.setAttribute ("image-rendering", "optimizeSpeed");
      else
        add_custom_properties (elt);
    }

}

#if defined (OCTAVE_USE_WINDOWS_API) && defined (_UNICODE)
extern "C"
int
wmain (int argc, wchar_t **wargv)
{
  static char **argv = new char * [argc + 1];
  std::vector<std::string> argv_str;

  // convert wide character strings to multibyte UTF-8 strings
  std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> wchar_conv;
  for (int i_arg = 0; i_arg < argc; i_arg++)
    argv_str.push_back (wchar_conv.to_bytes (wargv[i_arg]));

  // Get pointers to C strings not before vector is stable.
  for (int i_arg = 0; i_arg < argc; i_arg++)
    argv[i_arg] = &argv_str[i_arg][0];
  argv[argc] = nullptr;

#else
int
main (int argc, char **argv)
{
#endif
  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\
* 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\
* 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\
* outfile: output file name\n";

  if (strcmp (argv[1], "-h") == 0)
    {
      std::cout << help;
      return 0;
    }
  else if (argc != 7)
    {
      std::cerr << help;
      return -1;
    }

  // Open svg file
  QFile file;
  if (strcmp (argv[1], "-") != 0)
    {
      // Read from file
      file.setFileName (argv[1]);
      if (! file.open (QIODevice::ReadOnly | QIODevice::Text))
        {
          std::cerr << "Unable to open file " << argv[1] << "\n";
          std::cerr << help;
          return -1;
        }
    }
  else
    {
      // Read from stdin
      if (! file.open (stdin, QIODevice::ReadOnly | QIODevice::Text))
        {
          std::cerr << "Unable to read from stdin\n";
          std::cerr << doc;
          return -1;
        }
    }

  // Create a DOM document and load the svg file
  QDomDocument document;
  QString msg;
  if (! document.setContent (&file, false, &msg))
    {
      std::cerr << "Failed to parse XML contents" << std::endl
                << msg.toStdString () << std::endl;
      file.close();
      return -1;
    }

  file.close ();

  // Format
  if (strcmp (argv[2], "pdf") != 0 && strcmp (argv[2], "svg") != 0)
    {
      std::cerr << "Unhandled output file format " << argv[2] << "\n";
      std::cerr << doc;
      return -1;
    }

  // Resolution (Currently unused). Keep the DPI argument in case
  // we implement raster outputs.
  // double dpi = QString (argv[3]).toDouble ();

  // Get the viewport from the root element
  QDomElement root = document.firstChildElement();
  double x0, y0, dx, dy;
  QString s = root.attribute ("viewBox");
  QTextStream (&s) >> x0 >> y0 >> dx >> dy;
  QRectF vp (x0, y0, dx, dy);

  // Setup application and add default FreeSans font if needed
  QApplication a (argc, argv);

  // When printing to PDF we may need the default FreeSans font
  if (! strcmp (argv[2], "pdf"))
    {
      QFont font ("FreeSans");
      if (! font.exactMatch ())
        {
          QString fontpath (argv[4]);
          if (! fontpath.isEmpty ())
            {
              int id = QFontDatabase::addApplicationFont (fontpath);
              if (id < 0)
                std::cerr << "warning: print: "
                             "Unable to add default font to database\n";
            }
          else
            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
  if (QString (argv[5]).toInt ())
    reconstruct_polygons (root);

  // Add custom properties to SVG
  add_custom_properties (root);

  // Draw
  if (! strcmp (argv[2], "pdf"))
    {
      // PDF painter
      pdfpainter painter (fout.fileName (), vp);

      draw (root, painter);
    }
  else
    {
      // Return modified svg document
      QTextStream out (&fout);
      out.setCodec ("UTF-8");
      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;
      }

  fout.copy (argv[6]);

  return 0;
}