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