GNU Octave  8.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
octave-svgconvert.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2017-2023 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING. If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 #if defined (HAVE_CONFIG_H)
27 # include "config.h"
28 #endif
29 
30 #include <cstdio>
31 #include <iostream>
32 
33 #if defined (OCTAVE_USE_WINDOWS_API)
34 # include <vector>
35 # include <locale>
36 # include <codecvt>
37 #endif
38 
39 #include <QtCore>
40 #include <QtXml>
41 
42 #include <QApplication>
43 #include <QFontDatabase>
44 #include <QImage>
45 #include <QPainter>
46 #include <QPrinter>
47 #include <QRegExp>
48 
49 // Include a set of path rendering functions extracted from Qt-5.12 source
50 #include "octave-qsvghandler.h"
51 
52 // Render to pdf
53 class pdfpainter : public QPainter
54 {
55 public:
56  pdfpainter (QString fname, QRectF sz)
57  : m_printer ()
58  {
59  // Printer settings
60  m_printer.setOutputFormat (QPrinter::PdfFormat);
61  m_printer.setFontEmbeddingEnabled (true);
62  m_printer.setOutputFileName (fname);
63  m_printer.setFullPage (true);
64 #if defined (HAVE_QPRINTER_SETPAGESIZE)
65  m_printer.setPageSize (QPageSize (sz.size (), QPageSize::Point,
66  QString ("custom"),
67  QPageSize::ExactMatch));
68 #else
69  m_printer.setPaperSize (sz.size (), QPrinter::Point);
70 #endif
71 
72  // Painter settings
73  begin (&m_printer);
74  setWindow (sz.toRect ());
75  }
76 
77  ~pdfpainter (void) { end (); }
78 
79 private:
80  QPrinter m_printer;
81 };
82 
83 // String conversion functions+QVector<double> qstr2vectorf (QString str)
84 QVector<double> qstr2vectorf (QString str)
85 {
86  QVector<double> pts;
87  QStringList coords = str.split (",");
88  for (QStringList::iterator p = coords.begin (); p != coords.end (); p += 1)
89  {
90  double pt = (*p).toDouble ();
91  pts.append (pt);
92  }
93  return pts;
94 }
95 
96 QVector<double> qstr2vectord (QString str)
97 {
98  QVector<double> pts;
99  QStringList coords = str.split (",");
100  for (QStringList::iterator p = coords.begin (); p != coords.end (); p += 1)
101  {
102  double pt = (*p).toDouble ();
103  pts.append (pt);
104  }
105 
106  return pts;
107 }
108 
109 QVector<QPointF> qstr2ptsvector (QString str)
110 {
111  QVector<QPointF> pts;
112  str = str.trimmed ();
113  str.replace (" ", ",");
114  QStringList coords = str.split (",");
115  for (QStringList::iterator p = coords.begin (); p != coords.end (); p += 2)
116  {
117  QPointF pt ((*p).toDouble (), (*(p+1)).toDouble ());
118  pts.append (pt);
119  }
120  return pts;
121 }
122 
123 QVector<QPoint> qstr2ptsvectord (QString str)
124 {
125  QVector<QPoint> pts;
126  str = str.trimmed ();
127  str.replace (" ", ",");
128  QStringList coords = str.split (",");
129  for (QStringList::iterator p = coords.begin (); p != coords.end (); p += 2)
130  {
131  QPoint pt ((*p).toDouble (), (*(p+1)).toDouble ());
132  pts.append (pt);
133  }
134  return pts;
135 }
136 
137 // Extract field arguments in a style-like string, e.g. "bla field(1,34,56) bla"
138 QString get_field (QString str, QString field)
139 {
140  QString retval;
141  QRegExp rx (field + "\\(([^\\)]*)\\)");
142  int pos = 0;
143  pos = rx.indexIn (str, pos);
144  if (pos > -1)
145  retval = rx.cap (1);
146 
147  return retval;
148 }
149 
150 // Polygon reconstruction class
152 {
153 public:
155  { }
156 
157  octave_polygon (QPolygonF p)
158  { m_polygons.push_back (p); }
159 
160  ~octave_polygon (void) { }
161 
162  int count (void) const
163  { return m_polygons.count (); }
164 
165  void reset (void)
166  { m_polygons.clear (); }
167 
168  QList<QPolygonF> reconstruct (int reconstruct_level)
169  {
170  if (m_polygons.isEmpty ())
171  return QList<QPolygonF> ();
172  else if (reconstruct_level < 2)
173  return m_polygons;
174 
175  // Once a polygon has been merged to another, it is marked unsuded
176  QVector<bool> unused;
177  for (auto it = m_polygons.begin (); it != m_polygons.end (); it++)
178  unused.push_back (false);
179 
180  bool tryagain = (m_polygons.count () > 1);
181 
182  while (tryagain)
183  {
184  tryagain = false;
185  for (auto ii = 0; ii < m_polygons.count (); ii++)
186  {
187  if (! unused[ii])
188  {
189  QPolygonF polygon = m_polygons[ii];
190  for (auto jj = ii+1; jj < m_polygons.count (); jj++)
191  {
192  if (! unused[jj])
193  {
194  QPolygonF newpoly = mergepoly (polygon, m_polygons[jj]);
195  if (newpoly.count ())
196  {
197  polygon = newpoly;
198  m_polygons[ii] = newpoly;
199  unused[jj] = true;
200  tryagain = true;
201  }
202  }
203  }
204  }
205  }
206  }
207 
208  // Try to remove cracks in polygons
209  for (auto ii = 0; ii < m_polygons.count (); ii++)
210  {
211  QPolygonF polygon = m_polygons[ii];
212  tryagain = ! unused[ii];
213 
214  while (tryagain && polygon.count () > 4)
215  {
216  tryagain = false;
217  QVector<int> del;
218 
219  for (auto jj = 1; jj < (polygon.count () - 1); jj++)
220  if (polygon[jj-1] == polygon[jj+1])
221  {
222  if (! del.contains (jj))
223  del.push_front (jj);
224 
225  del.push_front (jj+1);
226  }
227 
228  for (auto idx : del)
229  polygon.remove (idx);
230 
231  if (del.count ())
232  tryagain = true;
233  }
234  m_polygons[ii] = polygon;
235  }
236 
237  // FIXME: There may still be residual cracks, we should do something like
238  // resetloop = 2;
239  // while (resetloop)
240  // currface = shift (currface, 1);
241  // if (currface(1) == currface(3))
242  // currface([2 3]) = [];
243  // resetloop = 2;
244  // else
245  // resetloop--;
246  // endif
247  // endwhile
248 
249  QList<QPolygonF> retval;
250  for (int ii = 0; ii < m_polygons.count (); ii++)
251  {
252  QPolygonF polygon = m_polygons[ii];
253  if (! unused[ii] && polygon.count () > 2)
254  retval.push_back (polygon);
255  }
256 
257  return retval;
258  }
259 
260  static inline
261  bool eq (QPointF p1, QPointF p2)
262  {
263  return ((qAbs (p1.x () - p2.x ())
264  <= 0.00001 * qMin (qAbs (p1.x ()), qAbs (p2.x ())))
265  && (qAbs (p1.y () - p2.y ())
266  <= 0.00001 * qMin (qAbs (p1.y ()), qAbs (p2.y ()))));
267  }
268 
269  static
270  QPolygonF mergepoly (QPolygonF poly1, QPolygonF poly2)
271  {
272  // Close polygon contour
273  poly1.push_back (poly1[0]);
274  poly2.push_back (poly2[0]);
275 
276  for (int ii = 0; ii < (poly1.size () - 1); ii++)
277  {
278  for (int jj = 0; jj < (poly2.size () - 1); jj++)
279  {
280  bool forward = (eq (poly1[ii], poly2[jj])
281  && eq (poly1[ii+1], poly2[jj+1]));
282  bool backward = ! forward && (eq (poly1[ii], poly2[jj+1])
283  && eq (poly1[ii+1], poly2[jj]));
284 
285  if (forward || backward)
286  {
287  // Unclose contour
288  poly1.pop_back ();
289  poly2.pop_back ();
290 
291  QPolygonF merged;
292  for (int kk = 0; kk < (ii+1); kk++)
293  merged.push_back (poly1[kk]);
294 
295  // Shift vertices and eliminate the common edge
296  std::rotate (poly2.begin (), poly2.begin () + jj, poly2.end ());
297  poly2.erase (poly2.begin ());
298  poly2.erase (poly2.begin ());
299 
300  if (forward)
301  for (int kk = poly2.size (); kk > 0; kk--)
302  merged.push_back (poly2[kk-1]);
303  else
304  for (int kk = 0; kk < poly2.size (); kk++)
305  merged.push_back (poly2[kk]);
306 
307  for (int kk = ii+1; kk < poly1.size (); kk++)
308  merged.push_back (poly1[kk]);
309 
310  // Return row vector
311  QPolygonF out (merged.size ());
312  for (int kk = 0; kk < merged.size (); kk++)
313  out[kk] = merged[kk];
314 
315  return out;
316  }
317  }
318  }
319  return QPolygonF ();
320  }
321 
322  void add (QPolygonF p)
323  {
324  if (m_polygons.count () == 0)
325  m_polygons.push_back (p);
326  else
327  {
328  QPolygonF tmp = mergepoly (m_polygons.back (), p);
329  if (tmp.count ())
330  m_polygons.back () = tmp;
331  else
332  m_polygons.push_back (p);
333  }
334  }
335 
336 private:
338 };
339 
340 void draw (QDomElement& parent_elt, pdfpainter& painter)
341 {
342  QDomNodeList nodes = parent_elt.childNodes ();
343 
344  static QString clippath_id;
345  static QMap< QString, QVector<QPoint> > clippath;
346 
347  // tspan elements must have access to the font and position extracted from
348  // their parent text element
349  static QFont font;
350  static double dx = 0, dy = 0;
351 
352  // Store path defined in <defs> in a map
353  static bool in_defs = false;
354  static QMap< QString, QPainterPath> path_map;
355 
356  for (int i = 0; i < nodes.count (); i++)
357  {
358  QDomNode node = nodes.at (i);
359  if (! node.isElement ())
360  continue;
361 
362  QDomElement elt = node.toElement ();
363 
364  if (elt.tagName () == "clipPath")
365  {
366  clippath_id = "#" + elt.attribute ("id");
367  draw (elt, painter);
368  clippath_id = QString ();
369  }
370  else if (elt.tagName () == "g")
371  {
372  QString str = elt.attribute ("font-family");
373  if (! str.isEmpty ())
374  {
375  // Font
376  font = QFont ();
377  font.setFamily (elt.attribute ("font-family"));
378 
379  str = elt.attribute ("font-weight");
380  if (! str.isEmpty () && str != "normal")
381  font.setWeight (QFont::Bold);
382 
383  str = elt.attribute ("font-style");
384  if (! str.isEmpty () && str != "normal")
385  font.setStyle (QFont::StyleItalic);
386 
387  str = elt.attribute ("font-size");
388  if (! str.isEmpty ())
389  font.setPixelSize (str.toDouble ());
390 
391  painter.setFont (font);
392 
393  // Translation and rotation
394  painter.save ();
395  str = get_field (elt.attribute ("transform"), "translate");
396  if (! str.isEmpty ())
397  {
398  QStringList trans = str.split (",");
399  dx = trans[0].toDouble ();
400  dy = trans[1].toDouble ();
401 
402  str = get_field (elt.attribute ("transform"), "rotate");
403  if (! str.isEmpty ())
404  {
405  QStringList rot = str.split (",");
406  painter.translate (dx+rot[1].toDouble (),
407  dy+rot[2].toDouble ());
408  painter.rotate (rot[0].toDouble ());
409  dx = rot[1].toDouble ();
410  dy = rot[2].toDouble ();
411  }
412  else
413  {
414  painter.translate (dx, dy);
415  dx = 0;
416  dy = 0;
417  }
418  }
419 
420  draw (elt, painter);
421  painter.restore ();
422  }
423  else
424  {
425  bool current_clipstate = painter.hasClipping ();
426  QRegion current_clippath = painter.clipRegion ();
427 
428  str = elt.attribute ("clip-path");
429  if (! str.isEmpty ())
430  {
431  QVector<QPoint> pts = clippath[get_field (str, "url")];
432  if (! pts.isEmpty ())
433  {
434  painter.setClipRegion (QRegion (QPolygon (pts)));
435  painter.setClipping (true);
436  }
437  }
438 
439  // Fill color
440  str = get_field (elt.attribute ("fill"), "rgb");
441  if (! str.isEmpty ())
442  {
443  QStringList clist = str.split (",");
444  painter.setBrush (QColor (clist[0].toInt (),
445  clist[1].toInt (),
446  clist[2].toInt ()));
447  }
448 
449  // Transform
450  str = elt.attribute ("transform");
451  painter.save ();
452  if (! str.isEmpty ())
453  {
454  QStringRef tf (&str);
455  QTransform tform =
456  parseTransformationMatrix (tf) * painter.transform ();
457  painter.setTransform (tform);
458  }
459 
460  draw (elt, painter);
461 
462  // Restore previous clipping settings
463  painter.restore ();
464  painter.setClipRegion (current_clippath);
465  painter.setClipping (current_clipstate);
466  }
467  }
468  else if (elt.tagName () == "defs")
469  {
470  in_defs = true;
471  draw (elt, painter);
472  in_defs = false;
473  }
474  else if (elt.tagName () == "path")
475  {
476  // Store QPainterPath for latter use
477  QString id = elt.attribute ("id");
478  if (! id.isEmpty ())
479  {
480  QString d = elt.attribute ("d");
481 
482  if (! d.isEmpty ())
483  {
484  QStringRef data (&d);
485  QPainterPath path;
486  if (! parsePathDataFast (data, path))
487  continue; // Something went wrong, pass
488  else if (path.isEmpty ())
489  std::cout << "Empty path for data:"
490  << d.toStdString () << std::endl;
491  else if (in_defs)
492  path_map["#" + id] = path;
493  else
494  painter.drawPath (path);
495 
496 
497  if (path_map["#" + id].isEmpty ())
498  std::cout << "Empty path for data:"
499  << d.toStdString () << std::endl;
500  }
501  }
502  }
503  else if (elt.tagName () == "use")
504  {
505  painter.setPen (Qt::NoPen);
506 
507  QString str = elt.attribute ("xlink:href");
508  if (! str.isEmpty () && str.size () > 2)
509  {
510  QPainterPath path = path_map[str];
511  if (! path.isEmpty ())
512  {
513  str = elt.attribute ("x");
514  double x = elt.attribute ("x").toDouble ();
515  str = elt.attribute ("y");
516  double y = elt.attribute ("y").toDouble ();
517  painter.translate (x, y);
518  painter.drawPath (path);
519  painter.translate (-x, -y);
520  }
521  }
522  }
523  else if (elt.tagName () == "text")
524  {
525  // Font
526  QFont saved_font (font);
527 
528  QString str = elt.attribute ("font-family");
529  if (! str.isEmpty ())
530  font.setFamily (elt.attribute ("font-family"));
531 
532  str = elt.attribute ("font-weight");
533  if (! str.isEmpty ())
534  {
535  if (str != "normal")
536  font.setWeight (QFont::Bold);
537  else
538  font.setWeight (QFont::Normal);
539  }
540 
541  str = elt.attribute ("font-style");
542  if (! str.isEmpty ())
543  {
544  if (str != "normal")
545  font.setStyle (QFont::StyleItalic);
546  else
547  font.setStyle (QFont::StyleNormal);
548  }
549 
550  str = elt.attribute ("font-size");
551  if (! str.isEmpty ())
552  font.setPixelSize (str.toDouble ());
553 
554  painter.setFont (font);
555 
556  // Color is specified in rgb
557  str = get_field (elt.attribute ("fill"), "rgb");
558  if (! str.isEmpty ())
559  {
560  QStringList clist = str.split (",");
561  painter.setPen (QColor (clist[0].toInt (), clist[1].toInt (),
562  clist[2].toInt ()));
563  }
564 
565  QStringList xx = elt.attribute ("x").split (" ");
566  int y = elt.attribute ("y").toInt ();
567  str = elt.text ();
568  if (! str.isEmpty ())
569  {
570  int ii = 0;
571  foreach (QString s, xx)
572  if (ii < str.size ())
573  painter.drawText (s.toInt ()-dx, y-dy, str.at (ii++));
574  }
575 
576  draw (elt, painter);
577  font = saved_font;
578  }
579  else if (elt.tagName () == "polyline")
580  {
581  // Color
582  QColor c (elt.attribute ("stroke"));
583  QString str = elt.attribute ("stroke-opacity");
584  if (! str.isEmpty () && str.toDouble () != 1.0
585  && str.toDouble () >= 0.0)
586  c.setAlphaF (str.toDouble ());
587 
588  QPen pen;
589  pen.setColor (c);
590 
591  // Line properties
592  str = elt.attribute ("stroke-width");
593  if (! str.isEmpty ())
594  {
595  double w = str.toDouble ();
596  if (w > 0)
597  pen.setWidthF (w);
598  }
599 
600  str = elt.attribute ("stroke-linecap");
601  pen.setCapStyle (Qt::SquareCap);
602  if (str == "round")
603  pen.setCapStyle (Qt::RoundCap);
604  else if (str == "butt")
605  pen.setCapStyle (Qt::FlatCap);
606 
607  str = elt.attribute ("stroke-linejoin");
608  pen.setJoinStyle (Qt::MiterJoin);
609  if (str == "round")
610  pen.setJoinStyle (Qt::RoundJoin);
611  else if (str == "bevel")
612  pen.setJoinStyle (Qt::BevelJoin);
613 
614  str = elt.attribute ("stroke-dasharray");
615  pen.setStyle (Qt::SolidLine);
616  if (! str.isEmpty ())
617  {
618  QVector<double> pat = qstr2vectord (str);
619  if (pat.count () != 2 || pat[1] != 0)
620  {
621  // Express pattern in linewidth units
622  for (auto& p : pat)
623  p /= pen.widthF ();
624 
625  pen.setDashPattern (pat);
626  }
627  }
628 
629  painter.setPen (pen);
630  painter.drawPolyline (qstr2ptsvector (elt.attribute ("points")));
631  }
632  else if (elt.tagName () == "image")
633  {
634  // Images are represented as a base64 stream of png formatted data
635  QString href_att = elt.attribute ("xlink:href");
636  QString prefix ("data:image/png;base64,");
637  QByteArray data
638  = QByteArray::fromBase64 (href_att.mid (prefix.length ()).toLatin1 ());
639  QImage img;
640  if (img.loadFromData (data, "PNG"))
641  {
642  QRect pos(elt.attribute ("x").toInt (),
643  elt.attribute ("y").toInt (),
644  elt.attribute ("width").toInt (),
645  elt.attribute ("height").toInt ());
646 
647  // Translate
648  painter.save ();
649  QString str = get_field (elt.attribute ("transform"), "matrix");
650  if (! str.isEmpty ())
651  {
652  QVector<double> m = qstr2vectorf (str);
653  QTransform tform(m[0], m[1], m[2],
654  m[3], m[4], m[5]);
655  painter.setTransform (tform);
656  }
657 
658  painter.setRenderHint (QPainter::Antialiasing, false);
659 #if defined (HAVE_QPAINTER_RENDERHINT_LOSSLESS)
660  painter.setRenderHint (QPainter::LosslessImageRendering);
661 #endif
662  painter.drawImage (pos, img);
663  painter.setRenderHint (QPainter::Antialiasing, true);
664  painter.restore ();
665  }
666  }
667  else if (elt.tagName () == "rect")
668  {
669  // Position
670  double x = elt.attribute ("x").toDouble ();
671  double y = elt.attribute ("y").toDouble ();
672 
673  // Size
674  double wd = elt.attribute ("width").toDouble ();
675  double hg = elt.attribute ("height").toDouble ();
676 
677  // Color
678  QColor saved_color = painter.brush ().color ();
679 
680  QString str = elt.attribute ("fill");
681  if (! str.isEmpty ())
682  painter.setBrush (QColor (str));
683 
684  painter.setPen (Qt::NoPen);
685 
686  painter.drawRect (QRectF (x, y, wd, hg));
687 
688  if (! str.isEmpty ())
689  painter.setBrush (saved_color);
690  }
691  else if (elt.tagName () == "polygon")
692  {
693  if (! clippath_id.isEmpty ())
694  clippath[clippath_id] = qstr2ptsvectord (elt.attribute ("points"));
695  else
696  {
697  QString str = elt.attribute ("fill");
698  if (! str.isEmpty ())
699  {
700  QColor color (str);
701 
702  str = elt.attribute ("fill-opacity");
703  if (! str.isEmpty () && str.toDouble () != 1.0
704  && str.toDouble () >= 0.0)
705  color.setAlphaF (str.toDouble ());
706 
707  QPolygonF p (qstr2ptsvector (elt.attribute ("points")));
708 
709  if (p.count () > 2)
710  {
711  painter.setBrush (color);
712  painter.setPen (Qt::NoPen);
713 
714  painter.setRenderHint (QPainter::Antialiasing, false);
715  painter.drawPolygon (p);
716  painter.setRenderHint (QPainter::Antialiasing, true);
717  }
718  }
719  }
720  }
721  }
722 }
723 
724 // Append a list of reconstructed child polygons to a QDomElement and remove
725 // the original nodes
726 
727 void replace_polygons (QDomElement& parent_elt, QList<QDomNode> orig,
728  QList<QPolygonF> polygons)
729 {
730  if (! orig.count () || (orig.count () == polygons.count ()))
731  return;
732 
733  QDomNode last = orig.last ();
734  for (int ii = 0; ii < polygons.count (); ii++)
735  {
736  QPolygonF polygon = polygons[ii];
737 
738  QDomNode node = last.cloneNode ();
739 
740  QString pts;
741 
742  for (int jj = 0; jj < polygon.count (); jj++)
743  {
744  pts += QString ("%1,%2 ").arg (polygon[jj].x ())
745  .arg (polygon[jj].y ());
746  }
747 
748  node.toElement ().setAttribute ("points", pts.trimmed ());
749 
750  if (! last.isNull ())
751  last = parent_elt.insertAfter (node, last);
752  }
753 
754  for (int ii = 0; ii < orig.count (); ii++)
755  parent_elt.removeChild (orig.at (ii));
756 }
757 
758 void reconstruct_polygons (QDomElement& parent_elt, int reconstruct_level)
759 {
760  QDomNodeList nodes = parent_elt.childNodes ();
761  QColor current_color;
762  QList<QDomNode> replaced_nodes;
763  octave_polygon current_polygon;
764 
765  // Collection of child nodes to be removed and polygons to be added
767 
768  for (int ii = 0; ii < nodes.count (); ii++)
769  {
770  QDomNode node = nodes.at (ii);
771  if (! node.isElement ())
772  continue;
773 
774  QDomElement elt = node.toElement ();
775 
776  if (elt.tagName () == "polygon")
777  {
778  QString str = elt.attribute ("fill");
779  if (! str.isEmpty ())
780  {
781  QColor color (str);
782  str = elt.attribute ("fill-opacity");
783  if (! str.isEmpty ())
784  {
785  double alpha = str.toDouble ();
786  if (alpha != 1.0 && alpha >= 0.0)
787  color.setAlphaF (alpha);
788  }
789 
790  if (! current_polygon.count ())
791  current_color = color;
792 
793  if (color != current_color)
794  {
795  // Reconstruct the previous series of triangles
796  QList<QPolygonF> polygons
797  = current_polygon.reconstruct (reconstruct_level);
798  collection.push_back (QPair<QList<QDomNode>,QList<QPolygonF> >
799  (replaced_nodes, polygons));
800 
801  replaced_nodes.clear ();
802  current_polygon.reset ();
803 
804  current_color = color;
805  }
806 
807  QPolygonF p (qstr2ptsvector (elt.attribute ("points")));
808  current_polygon.add (p);
809  replaced_nodes.push_back (node);
810  }
811  }
812  else
813  {
814  if (current_polygon.count ())
815  {
816  QList<QPolygonF> polygons = current_polygon.reconstruct (reconstruct_level);
817  collection.push_back (QPair<QList<QDomNode>,QList<QPolygonF> >
818  (replaced_nodes, polygons));
819  replaced_nodes.clear ();
820  current_polygon.reset ();
821  }
822  reconstruct_polygons (elt, reconstruct_level);
823  }
824  }
825 
826  // Finish
827  collection.push_back (QPair<QList<QDomNode>,QList<QPolygonF> >
828  (replaced_nodes,
829  current_polygon.reconstruct (reconstruct_level)));
830 
831  for (int ii = 0; ii < collection.count (); ii++)
832  replace_polygons (parent_elt, collection[ii].first, collection[ii].second);
833 }
834 
835 void add_custom_properties (QDomElement& parent_elt)
836 {
837  QDomNodeList nodes = parent_elt.childNodes ();
838 
839  for (int ii = 0; ii < nodes.count (); ii++)
840  {
841  QDomNode node = nodes.at (ii);
842  if (! node.isElement ())
843  continue;
844 
845  QDomElement elt = node.toElement ();
846 
847  if (elt.tagName () == "image")
848  elt.setAttribute ("image-rendering", "optimizeSpeed");
849  else
850  add_custom_properties (elt);
851  }
852 
853 }
854 
855 #if defined (OCTAVE_USE_WINDOWS_API) && defined (_UNICODE)
856 extern "C"
857 int
858 wmain (int argc, wchar_t **wargv)
859 {
860  static char **argv = new char * [argc + 1];
861  std::vector<std::string> argv_str;
862 
863  // convert wide character strings to multibyte UTF-8 strings
864  std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> wchar_conv;
865  for (int i_arg = 0; i_arg < argc; i_arg++)
866  argv_str.push_back (wchar_conv.to_bytes (wargv[i_arg]));
867 
868  // Get pointers to C strings not before vector is stable.
869  for (int i_arg = 0; i_arg < argc; i_arg++)
870  argv[i_arg] = &argv_str[i_arg][0];
871  argv[argc] = nullptr;
872 
873 #else
874 int
875 main (int argc, char **argv)
876 {
877 #endif
878  const char *doc = "See \"octave-svgconvert -h\"";
879  const char *help = "Usage:\n\
880 octave-svgconvert infile fmt dpi font reconstruct outfile\n\n\
881 Convert svg file to pdf, or svg. All arguments are mandatory:\n\
882 * infile: input svg file or \"-\" to indicate that the input svg file should be \
883 read from stdin\n\
884 * fmt: format of the output file. May be one of pdf or svg\n\
885 * dpi: device dependent resolution in screen pixel per inch\n\
886 * font: specify a file name for the default FreeSans font\n\
887 * reconstruct: specify whether to reconstruct triangle to polygons\n\
888  0: no reconstruction (merging) of polygons\n\
889  1: merge consecutive triangles if they share an edge\n\
890  2: merge all triangles that share edges (might take a long time)\n\
891 * outfile: output file name\n";
892 
893  if (strcmp (argv[1], "-h") == 0)
894  {
895  std::cout << help;
896  return 0;
897  }
898  else if (argc != 7)
899  {
900  std::cerr << help;
901  return -1;
902  }
903 
904  // Open svg file
905  QFile file;
906  if (strcmp (argv[1], "-") != 0)
907  {
908  // Read from file
909  file.setFileName (argv[1]);
910  if (! file.open (QIODevice::ReadOnly | QIODevice::Text))
911  {
912  std::cerr << "Unable to open file " << argv[1] << "\n";
913  std::cerr << help;
914  return -1;
915  }
916  }
917  else
918  {
919  // Read from stdin
920  if (! file.open (stdin, QIODevice::ReadOnly | QIODevice::Text))
921  {
922  std::cerr << "Unable to read from stdin\n";
923  std::cerr << doc;
924  return -1;
925  }
926  }
927 
928  // Create a DOM document and load the svg file
929  QDomDocument document;
930  QString msg;
931  if (! document.setContent (&file, false, &msg))
932  {
933  std::cerr << "Failed to parse XML contents" << std::endl
934  << msg.toStdString () << std::endl;
935  file.close();
936  return -1;
937  }
938 
939  file.close ();
940 
941  // Format
942  if (strcmp (argv[2], "pdf") != 0 && strcmp (argv[2], "svg") != 0)
943  {
944  std::cerr << "Unhandled output file format " << argv[2] << "\n";
945  std::cerr << doc;
946  return -1;
947  }
948 
949  // Resolution (Currently unused). Keep the DPI argument in case
950  // we implement raster outputs.
951  // double dpi = QString (argv[3]).toDouble ();
952 
953  // Get the viewport from the root element
954  QDomElement root = document.firstChildElement();
955  double x0, y0, dx, dy;
956  QString s = root.attribute ("viewBox");
957  QTextStream (&s) >> x0 >> y0 >> dx >> dy;
958  QRectF vp (x0, y0, dx, dy);
959 
960  // Setup application and add default FreeSans font if needed
961  QApplication a (argc, argv);
962 
963  // When printing to PDF we may need the default FreeSans font
964  if (! strcmp (argv[2], "pdf"))
965  {
966  QFont font ("FreeSans");
967  if (! font.exactMatch ())
968  {
969  QString fontpath (argv[4]);
970  if (! fontpath.isEmpty ())
971  {
972  int id = QFontDatabase::addApplicationFont (fontpath);
973  if (id < 0)
974  std::cerr << "warning: print: "
975  "Unable to add default font to database\n";
976  }
977  else
978  std::cerr << "warning: print: FreeSans font not found\n";
979  }
980  }
981 
982  // First render in a temporary file
983  QTemporaryFile fout;
984  if (! fout.open ())
985  {
986  std::cerr << "Could not open temporary file\n";
987  return -1;
988  }
989 
990  // Do basic polygons reconstruction
991  int reconstruct_level = QString (argv[5]).toInt ();
992  if (reconstruct_level)
993  reconstruct_polygons (root, reconstruct_level);
994 
995  // Add custom properties to SVG
996  add_custom_properties (root);
997 
998  // Draw
999  if (! strcmp (argv[2], "pdf"))
1000  {
1001  // PDF painter
1002  pdfpainter painter (fout.fileName (), vp);
1003 
1004  draw (root, painter);
1005  }
1006  else
1007  {
1008  // Return modified svg document
1009  QTextStream out (&fout);
1010  out.setCodec ("UTF-8");
1011  out << document.toByteArray ();
1012  }
1013 
1014  // Delete output file before writing with new data
1015  if (QFile::exists (argv[6]))
1016  if (! QFile::remove (argv[6]))
1017  {
1018  std::cerr << "Unable to replace existing file " << argv[6] << "\n";
1019  return -1;
1020  }
1021 
1022  fout.copy (argv[6]);
1023 
1024  return 0;
1025 }
int count(void) const
static bool eq(QPointF p1, QPointF p2)
void add(QPolygonF p)
QList< QPolygonF > reconstruct(int reconstruct_level)
octave_polygon(QPolygonF p)
static QPolygonF mergepoly(QPolygonF poly1, QPolygonF poly2)
QList< QPolygonF > m_polygons
pdfpainter(QString fname, QRectF sz)
QPrinter m_printer
F77_RET_T const F77_DBLE const F77_DBLE F77_DBLE * d
F77_RET_T const F77_DBLE * x
T octave_idx_type m
Definition: mx-inlines.cc:773
std::complex< double > w(std::complex< double > z, double relerr=0)
OCTAVE_API bool strcmp(const T &str_a, const T &str_b)
Octave string utility functions.
static qreal toDouble(const QChar *&str)
static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path)
static QTransform parseTransformationMatrix(const QStringRef &value)
void reconstruct_polygons(QDomElement &parent_elt, int reconstruct_level)
QString get_field(QString str, QString field)
int main(int argc, char **argv)
QVector< double > qstr2vectord(QString str)
QVector< double > qstr2vectorf(QString str)
void replace_polygons(QDomElement &parent_elt, QList< QDomNode > orig, QList< QPolygonF > polygons)
QVector< QPointF > qstr2ptsvector(QString str)
void add_custom_properties(QDomElement &parent_elt)
QVector< QPoint > qstr2ptsvectord(QString str)
void draw(QDomElement &parent_elt, pdfpainter &painter)