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