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