comparison src/octave-svgconvert.cc @ 26027:98d7a111786a

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