Mercurial > octave
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 } |