26 #if defined (HAVE_CONFIG_H)
33 #if defined (OCTAVE_USE_WINDOWS_API)
42 #include <QApplication>
43 #include <QFontDatabase>
47 #include <QRegularExpression>
53 class pdfpainter :
public QPainter
57 pdfpainter () =
delete;
59 pdfpainter (QString fname, QRectF sz)
63 m_printer.setOutputFormat (QPrinter::PdfFormat);
64 m_printer.setFontEmbeddingEnabled (
true);
65 m_printer.setOutputFileName (fname);
66 m_printer.setFullPage (
true);
67 m_printer.setPageSize (QPageSize (sz.size (), QPageSize::Point,
69 QPageSize::ExactMatch));
73 setWindow (sz.toRect ());
76 OCTAVE_DISABLE_COPY_MOVE (pdfpainter)
78 ~pdfpainter () { end (); }
90 QStringList coords = str.split (
",");
91 for (
auto& p : coords)
92 pts.append (p.toDouble ());
103 QStringList coords = str.split (
",");
104 for (
auto& p : coords)
105 pts.append (p.toDouble ());
113 QVector<QPointF> pts;
114 str = str.trimmed ();
115 str.replace (
" ",
",");
116 QStringList coords = str.split (
",");
117 for (QStringList::iterator p = coords.begin (); p != coords.end (); p += 2)
119 QPointF pt ((*p).toDouble (), (*(p+1)).toDouble ());
131 str = str.trimmed ();
132 str.replace (
" ",
",");
133 QStringList coords = str.split (
",");
134 for (QStringList::iterator p = coords.begin (); p != coords.end (); p += 2)
136 QPoint pt ((*p).toDouble (), (*(p+1)).toDouble ());
146 QRegularExpression rx (field +
"\\(([^\\)]*)\\)");
147 QRegularExpressionMatch match = rx.match (str);
148 QString retval = match.captured (1);
158 octave_polygon (QPolygonF p)
159 { m_polygons.push_back (p); }
161 OCTAVE_DEFAULT_CONSTRUCT_COPY_MOVE_DELETE (octave_polygon)
164 {
return m_polygons.count (); }
167 { m_polygons.clear (); }
169 QList<QPolygonF> reconstruct (
int reconstruct_level)
171 if (m_polygons.isEmpty ())
172 return QList<QPolygonF> ();
173 else if (reconstruct_level < 2)
177 QVector<bool> unused (m_polygons.count (),
false);
179 bool tryagain = (m_polygons.count () > 1);
184 for (
auto ii = 0; ii < m_polygons.count (); ii++)
188 QPolygonF polygon = m_polygons[ii];
189 for (
auto jj = ii+1; jj < m_polygons.count (); jj++)
193 QPolygonF newpoly = mergepoly (polygon, m_polygons[jj]);
194 if (newpoly.count ())
197 m_polygons[ii] = newpoly;
208 for (
auto ii = 0; ii < m_polygons.count (); ii++)
210 QPolygonF polygon = m_polygons[ii];
211 tryagain = ! unused[ii];
213 while (tryagain && polygon.count () > 4)
218 for (
auto jj = 1; jj < (polygon.count () - 1); jj++)
219 if (polygon[jj-1] == polygon[jj+1])
221 if (! del.contains (jj))
224 del.push_front (jj+1);
228 polygon.remove (idx);
233 m_polygons[ii] = polygon;
248 QList<QPolygonF> retval;
249 for (
int ii = 0; ii < m_polygons.count (); ii++)
251 QPolygonF polygon = m_polygons[ii];
252 if (! unused[ii] && polygon.count () > 2)
253 retval.push_back (polygon);
260 bool eq (QPointF p1, QPointF p2)
262 return ((qAbs (p1.x () - p2.x ())
263 <= 0.00001 * qMin (qAbs (p1.x ()), qAbs (p2.x ())))
264 && (qAbs (p1.y () - p2.y ())
265 <= 0.00001 * qMin (qAbs (p1.y ()), qAbs (p2.y ()))));
269 QPolygonF mergepoly (QPolygonF poly1, QPolygonF poly2)
272 poly1.push_back (poly1[0]);
273 poly2.push_back (poly2[0]);
275 for (
int ii = 0; ii < (poly1.size () - 1); ii++)
277 for (
int jj = 0; jj < (poly2.size () - 1); jj++)
279 bool forward = (eq (poly1[ii], poly2[jj])
280 && eq (poly1[ii+1], poly2[jj+1]));
281 bool backward = ! forward && (eq (poly1[ii], poly2[jj+1])
282 && eq (poly1[ii+1], poly2[jj]));
284 if (forward || backward)
291 for (
int kk = 0; kk < (ii+1); kk++)
292 merged.push_back (poly1[kk]);
295 std::rotate (poly2.begin (), poly2.begin () + jj, poly2.end ());
296 poly2.erase (poly2.begin ());
297 poly2.erase (poly2.begin ());
300 for (
int kk = poly2.size (); kk > 0; kk--)
301 merged.push_back (poly2[kk-1]);
303 for (
int kk = 0; kk < poly2.size (); kk++)
304 merged.push_back (poly2[kk]);
306 for (
int kk = ii+1; kk < poly1.size (); kk++)
307 merged.push_back (poly1[kk]);
310 QPolygonF out (merged.size ());
311 for (
int kk = 0; kk < merged.size (); kk++)
312 out[kk] = merged[kk];
321 void add (QPolygonF p)
323 if (m_polygons.count () == 0)
324 m_polygons.push_back (p);
327 QPolygonF tmp = mergepoly (m_polygons.back (), p);
329 m_polygons.back () = tmp;
331 m_polygons.push_back (p);
336 QList<QPolygonF> m_polygons;
340 draw (QDomElement& parent_elt, pdfpainter& painter)
342 QDomNodeList nodes = parent_elt.childNodes ();
344 static QString clippath_id;
345 static QMap< QString, QVector<QPoint>> clippath;
350 static double dx = 0, dy = 0;
353 static bool in_defs =
false;
354 static QMap< QString, QPainterPath> path_map;
356 for (
int i = 0; i < nodes.count (); i++)
358 QDomNode node = nodes.at (i);
359 if (! node.isElement ())
362 QDomElement elt = node.toElement ();
364 if (elt.tagName () ==
"clipPath")
366 clippath_id =
"#" + elt.attribute (
"id");
368 clippath_id = QString ();
370 else if (elt.tagName () ==
"g")
372 QString str = elt.attribute (
"font-family");
373 if (! str.isEmpty ())
377 font.setFamily (elt.attribute (
"font-family"));
379 str = elt.attribute (
"font-weight");
380 if (! str.isEmpty () && str !=
"normal")
381 font.setWeight (QFont::Bold);
383 str = elt.attribute (
"font-style");
384 if (! str.isEmpty () && str !=
"normal")
385 font.setStyle (QFont::StyleItalic);
387 str = elt.attribute (
"font-size");
388 if (! str.isEmpty ())
389 font.setPixelSize (str.toDouble ());
391 painter.setFont (font);
395 str =
get_field (elt.attribute (
"transform"),
"translate");
396 if (! str.isEmpty ())
398 QStringList trans = str.split (
",");
399 dx = trans[0].toDouble ();
400 dy = trans[1].toDouble ();
402 str =
get_field (elt.attribute (
"transform"),
"rotate");
403 if (! str.isEmpty ())
405 QStringList rot = str.split (
",");
406 painter.translate (dx+rot[1].toDouble (),
407 dy+rot[2].toDouble ());
408 painter.rotate (rot[0].toDouble ());
409 dx = rot[1].toDouble ();
410 dy = rot[2].toDouble ();
414 painter.translate (dx, dy);
425 bool current_clipstate = painter.hasClipping ();
426 QRegion current_clippath = painter.clipRegion ();
428 str = elt.attribute (
"clip-path");
429 if (! str.isEmpty ())
431 QVector<QPoint> pts = clippath[
get_field (str,
"url")];
432 if (! pts.isEmpty ())
434 painter.setClipRegion (QRegion (QPolygon (pts)));
435 painter.setClipping (
true);
440 str =
get_field (elt.attribute (
"fill"),
"rgb");
441 if (! str.isEmpty ())
443 QStringList clist = str.split (
",");
444 painter.setBrush (QColor (clist[0].toInt (),
450 str = elt.attribute (
"transform");
452 if (! str.isEmpty ())
455 QStringView tf {str};
457 QStringRef tf {&str};
460 parseTransformationMatrix (tf) * painter.transform ();
461 painter.setTransform (tform);
468 painter.setClipRegion (current_clippath);
469 painter.setClipping (current_clipstate);
472 else if (elt.tagName () ==
"defs")
478 else if (elt.tagName () ==
"path")
481 QString
id = elt.attribute (
"id");
484 QString
d = elt.attribute (
"d");
489 QStringView data {
d};
491 QStringRef data {&
d};
494 if (! parsePathDataFast (data, path))
496 else if (path.isEmpty ())
497 std::cout <<
"Empty path for data:"
498 <<
d.toStdString () << std::endl;
500 path_map[
"#" + id] = path;
502 painter.drawPath (path);
505 if (path_map[
"#" +
id].isEmpty ())
506 std::cout <<
"Empty path for data:"
507 <<
d.toStdString () << std::endl;
511 else if (elt.tagName () ==
"use")
513 painter.setPen (Qt::NoPen);
515 QString str = elt.attribute (
"xlink:href");
516 if (! str.isEmpty () && str.size () > 2)
518 QPainterPath path = path_map[str];
519 if (! path.isEmpty ())
521 str = elt.attribute (
"x");
522 double x = elt.attribute (
"x").toDouble ();
523 str = elt.attribute (
"y");
524 double y = elt.attribute (
"y").toDouble ();
525 painter.translate (
x, y);
526 painter.drawPath (path);
527 painter.translate (-
x, -y);
531 else if (elt.tagName () ==
"text")
534 QFont saved_font (font);
536 QString str = elt.attribute (
"font-family");
537 if (! str.isEmpty ())
538 font.setFamily (elt.attribute (
"font-family"));
540 str = elt.attribute (
"font-weight");
541 if (! str.isEmpty ())
544 font.setWeight (QFont::Bold);
546 font.setWeight (QFont::Normal);
549 str = elt.attribute (
"font-style");
550 if (! str.isEmpty ())
553 font.setStyle (QFont::StyleItalic);
555 font.setStyle (QFont::StyleNormal);
558 str = elt.attribute (
"font-size");
559 if (! str.isEmpty ())
560 font.setPixelSize (str.toDouble ());
562 painter.setFont (font);
565 str =
get_field (elt.attribute (
"fill"),
"rgb");
566 if (! str.isEmpty ())
568 QStringList clist = str.split (
",");
569 painter.setPen (QColor (clist[0].toInt (), clist[1].toInt (),
573 QStringList xx = elt.attribute (
"x").split (
" ");
574 int y = elt.attribute (
"y").toInt ();
576 if (! str.isEmpty ())
579 foreach (QString s, xx)
580 if (ii < str.size ())
581 painter.drawText (s.toInt ()-dx, y-dy, str.at (ii++));
587 else if (elt.tagName () ==
"polyline")
590 QColor c (elt.attribute (
"stroke"));
591 QString str = elt.attribute (
"stroke-opacity");
592 if (! str.isEmpty () && str.toDouble () != 1.0
593 && str.toDouble () >= 0.0)
594 c.setAlphaF (str.toDouble ());
600 str = elt.attribute (
"stroke-width");
601 if (! str.isEmpty ())
603 double w = str.toDouble ();
608 str = elt.attribute (
"stroke-linecap");
609 pen.setCapStyle (Qt::SquareCap);
611 pen.setCapStyle (Qt::RoundCap);
612 else if (str ==
"butt")
613 pen.setCapStyle (Qt::FlatCap);
615 str = elt.attribute (
"stroke-linejoin");
616 pen.setJoinStyle (Qt::MiterJoin);
618 pen.setJoinStyle (Qt::RoundJoin);
619 else if (str ==
"bevel")
620 pen.setJoinStyle (Qt::BevelJoin);
622 str = elt.attribute (
"stroke-dasharray");
623 pen.setStyle (Qt::SolidLine);
624 if (! str.isEmpty ())
627 if (pat.count () != 2 || pat[1] != 0)
633 pen.setDashPattern (pat);
637 painter.setPen (pen);
640 else if (elt.tagName () ==
"image")
643 QString href_att = elt.attribute (
"xlink:href");
644 QString prefix (
"data:image/png;base64,");
646 = QByteArray::fromBase64 (href_att.mid (prefix.length ()).toLatin1 ());
648 if (img.loadFromData (data,
"PNG"))
650 QRect pos(elt.attribute (
"x").toInt (),
651 elt.attribute (
"y").toInt (),
652 elt.attribute (
"width").toInt (),
653 elt.attribute (
"height").toInt ());
657 QString str =
get_field (elt.attribute (
"transform"),
"matrix");
658 if (! str.isEmpty ())
661 QTransform tform(
m[0],
m[1],
m[2],
663 painter.setTransform (tform);
666 painter.setRenderHint (QPainter::Antialiasing,
false);
667 #if defined (HAVE_QPAINTER_RENDERHINT_LOSSLESS)
668 painter.setRenderHint (QPainter::LosslessImageRendering);
670 painter.drawImage (pos, img);
671 painter.setRenderHint (QPainter::Antialiasing,
true);
675 else if (elt.tagName () ==
"rect")
678 double x = elt.attribute (
"x").toDouble ();
679 double y = elt.attribute (
"y").toDouble ();
682 double wd = elt.attribute (
"width").toDouble ();
683 double hg = elt.attribute (
"height").toDouble ();
686 QColor saved_color = painter.brush ().color ();
688 QString str = elt.attribute (
"fill");
689 if (! str.isEmpty ())
690 painter.setBrush (QColor (str));
692 painter.setPen (Qt::NoPen);
694 painter.drawRect (QRectF (
x, y, wd, hg));
696 if (! str.isEmpty ())
697 painter.setBrush (saved_color);
699 else if (elt.tagName () ==
"polygon")
701 if (! clippath_id.isEmpty ())
705 QString str = elt.attribute (
"fill");
706 if (! str.isEmpty ())
710 str = elt.attribute (
"fill-opacity");
711 if (! str.isEmpty () && str.toDouble () != 1.0
712 && str.toDouble () >= 0.0)
713 color.setAlphaF (str.toDouble ());
719 painter.setBrush (color);
720 painter.setPen (Qt::NoPen);
722 painter.setRenderHint (QPainter::Antialiasing,
false);
723 painter.drawPolygon (p);
724 painter.setRenderHint (QPainter::Antialiasing,
true);
737 QList<QPolygonF> polygons)
739 if (! orig.count () || (orig.count () == polygons.count ()))
742 QDomNode last = orig.last ();
743 for (
int ii = 0; ii < polygons.count (); ii++)
745 QPolygonF polygon = polygons[ii];
747 QDomNode node = last.cloneNode ();
751 for (
int jj = 0; jj < polygon.count (); jj++)
753 pts += QString (
"%1,%2 ").arg (polygon[jj].
x ())
754 .arg (polygon[jj].y ());
757 node.toElement ().setAttribute (
"points", pts.trimmed ());
759 if (! last.isNull ())
760 last = parent_elt.insertAfter (node, last);
763 for (
int ii = 0; ii < orig.count (); ii++)
764 parent_elt.removeChild (orig.at (ii));
770 QDomNodeList nodes = parent_elt.childNodes ();
771 QColor current_color;
772 QList<QDomNode> replaced_nodes;
773 octave_polygon current_polygon;
776 QList< QPair<QList<QDomNode>, QList<QPolygonF>>> collection;
778 for (
int ii = 0; ii < nodes.count (); ii++)
780 QDomNode node = nodes.at (ii);
781 if (! node.isElement ())
784 QDomElement elt = node.toElement ();
786 if (elt.tagName () ==
"polygon")
788 QString str = elt.attribute (
"fill");
789 if (! str.isEmpty ())
792 str = elt.attribute (
"fill-opacity");
793 if (! str.isEmpty ())
795 double alpha = str.toDouble ();
796 if (alpha != 1.0 && alpha >= 0.0)
797 color.setAlphaF (alpha);
800 if (! current_polygon.count ())
801 current_color = color;
803 if (color != current_color)
806 QList<QPolygonF> polygons
807 = current_polygon.reconstruct (reconstruct_level);
808 collection.push_back (QPair<QList<QDomNode>, QList<QPolygonF>>
809 (replaced_nodes, polygons));
811 replaced_nodes.clear ();
812 current_polygon.reset ();
814 current_color = color;
818 current_polygon.add (p);
819 replaced_nodes.push_back (node);
824 if (current_polygon.count ())
826 QList<QPolygonF> polygons = current_polygon.reconstruct (reconstruct_level);
827 collection.push_back (QPair<QList<QDomNode>, QList<QPolygonF>>
828 (replaced_nodes, polygons));
829 replaced_nodes.clear ();
830 current_polygon.reset ();
837 collection.push_back (QPair<QList<QDomNode>, QList<QPolygonF>>
839 current_polygon.reconstruct (reconstruct_level)));
841 for (
int ii = 0; ii < collection.count (); ii++)
848 QDomNodeList nodes = parent_elt.childNodes ();
850 for (
int ii = 0; ii < nodes.count (); ii++)
852 QDomNode node = nodes.at (ii);
853 if (! node.isElement ())
856 QDomElement elt = node.toElement ();
858 if (elt.tagName () ==
"image")
859 elt.setAttribute (
"image-rendering",
"optimizeSpeed");
866 #if defined (OCTAVE_USE_WINDOWS_API) && defined (_UNICODE)
869 wmain (
int argc,
wchar_t **wargv)
871 static char **argv =
new char * [argc + 1];
872 std::vector<std::string> argv_str;
875 std::wstring_convert<std::codecvt_utf8<wchar_t>,
wchar_t> wchar_conv;
876 for (
int i_arg = 0; i_arg < argc; i_arg++)
877 argv_str.push_back (wchar_conv.to_bytes (wargv[i_arg]));
880 for (
int i_arg = 0; i_arg < argc; i_arg++)
881 argv[i_arg] = &argv_str[i_arg][0];
882 argv[argc] =
nullptr;
889 const char *doc =
"See \"octave-svgconvert -h\"";
890 const char *help =
"Usage:\n\
891 octave-svgconvert infile fmt dpi font reconstruct outfile\n\n\
892 Convert svg file to pdf, or svg. All arguments are mandatory:\n\
893 * infile: input svg file or \"-\" to indicate that the input svg file should be \
895 * fmt: format of the output file. May be one of pdf or svg\n\
896 * dpi: device dependent resolution in screen pixel per inch\n\
897 * font: specify a file name for the default FreeSans font\n\
898 * reconstruct: specify whether to reconstruct triangle to polygons\n\
899 0: no reconstruction (merging) of polygons\n\
900 1: merge consecutive triangles if they share an edge\n\
901 2: merge all triangles that share edges (might take a long time)\n\
902 * outfile: output file name\n";
904 if (
strcmp (argv[1],
"-h") == 0)
917 if (
strcmp (argv[1],
"-") != 0)
920 file.setFileName (argv[1]);
921 if (! file.open (QIODevice::ReadOnly | QIODevice::Text))
923 std::cerr <<
"Unable to open file " << argv[1] <<
"\n";
931 if (! file.open (stdin, QIODevice::ReadOnly | QIODevice::Text))
933 std::cerr <<
"Unable to read from stdin\n";
940 QDomDocument document;
942 if (! document.setContent (&file,
false, &msg))
944 std::cerr <<
"Failed to parse XML contents" << std::endl
945 << msg.toStdString () << std::endl;
953 if (
strcmp (argv[2],
"pdf") != 0 &&
strcmp (argv[2],
"svg") != 0)
955 std::cerr <<
"Unhandled output file format " << argv[2] <<
"\n";
965 QDomElement root = document.firstChildElement();
966 double x0, y0, dx, dy;
967 QString s = root.attribute (
"viewBox");
968 QTextStream (&s) >> x0 >> y0 >> dx >> dy;
969 QRectF vp (x0, y0, dx, dy);
972 QApplication a (argc, argv);
975 if (!
strcmp (argv[2],
"pdf"))
977 QFont font (
"FreeSans");
978 if (! font.exactMatch ())
980 QString fontpath (argv[4]);
981 if (! fontpath.isEmpty ())
983 int id = QFontDatabase::addApplicationFont (fontpath);
985 std::cerr <<
"warning: print: "
986 "Unable to add default font to database\n";
989 std::cerr <<
"warning: print: FreeSans font not found\n";
997 std::cerr <<
"Could not open temporary file\n";
1002 int reconstruct_level = QString (argv[5]).toInt ();
1003 if (reconstruct_level)
1010 if (!
strcmp (argv[2],
"pdf"))
1013 pdfpainter painter (fout.fileName (), vp);
1015 draw (root, painter);
1020 QTextStream out (&fout);
1021 #if HAVE_QTEXTSTREAM_SETENCODING
1022 out.setEncoding (QStringConverter::Utf8);
1024 out.setCodec (
"UTF-8");
1026 out << document.toByteArray ();
1030 if (QFile::exists (argv[6]))
1031 if (! QFile::remove (argv[6]))
1033 std::cerr <<
"Unable to replace existing file " << argv[6] <<
"\n";
1037 fout.copy (argv[6]);
F77_RET_T const F77_DBLE const F77_DBLE F77_DBLE * d
F77_RET_T const F77_DBLE * x
std::complex< double > w(std::complex< double > z, double relerr=0)
bool strcmp(const T &str_a, const T &str_b)
Octave string utility functions.
void reconstruct_polygons(QDomElement &parent_elt, int reconstruct_level)
QString get_field(QString str, QString field)
int main(int argc, char **argv)
QVector< double > qstr2vectord(QString str)
QVector< double > qstr2vectorf(QString str)
void replace_polygons(QDomElement &parent_elt, QList< QDomNode > orig, QList< QPolygonF > polygons)
QVector< QPointF > qstr2ptsvector(QString str)
void add_custom_properties(QDomElement &parent_elt)
QVector< QPoint > qstr2ptsvectord(QString str)
void draw(QDomElement &parent_elt, pdfpainter &painter)