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