340draw (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 ())
378 str = elt.attribute (
"font-weight");
379 if (! str.isEmpty () && str !=
"normal")
380 font.setWeight (QFont::Bold);
382 str = elt.attribute (
"font-style");
383 if (! str.isEmpty () && str !=
"normal")
384 font.setStyle (QFont::StyleItalic);
386 str = elt.attribute (
"font-size");
387 if (! str.isEmpty ())
388 font.setPixelSize (str.toDouble ());
390 painter.setFont (font);
394 str =
get_field (elt.attribute (
"transform"),
"translate");
395 if (! str.isEmpty ())
397 QStringList trans = str.split (
",");
398 dx = trans[0].toDouble ();
399 dy = trans[1].toDouble ();
401 str =
get_field (elt.attribute (
"transform"),
"rotate");
402 if (! str.isEmpty ())
404 QStringList rot = str.split (
",");
405 painter.translate (dx+rot[1].toDouble (),
406 dy+rot[2].toDouble ());
407 painter.rotate (rot[0].toDouble ());
408 dx = rot[1].toDouble ();
409 dy = rot[2].toDouble ();
413 painter.translate (dx, dy);
424 bool current_clipstate = painter.hasClipping ();
425 QRegion current_clippath = painter.clipRegion ();
427 str = elt.attribute (
"clip-path");
428 if (! str.isEmpty ())
430 QVector<QPoint> pts = clippath[
get_field (str,
"url")];
431 if (! pts.isEmpty ())
433 painter.setClipRegion (QRegion (QPolygon (pts)));
434 painter.setClipping (
true);
439 str =
get_field (elt.attribute (
"fill"),
"rgb");
440 if (! str.isEmpty ())
442 QStringList clist = str.split (
",");
443 painter.setBrush (QColor (clist[0].toInt (),
449 str = elt.attribute (
"transform");
451 if (! str.isEmpty ())
454 QStringView tf {str};
456 QStringRef tf {&str};
459 parseTransformationMatrix (tf) * painter.transform ();
460 painter.setTransform (tform);
467 painter.setClipRegion (current_clippath);
468 painter.setClipping (current_clipstate);
471 else if (elt.tagName () ==
"defs")
477 else if (elt.tagName () ==
"path")
480 QString
id = elt.attribute (
"id");
483 QString
d = elt.attribute (
"d");
488 QStringView data {
d};
490 QStringRef data {&
d};
493 if (! parsePathDataFast (data, path))
495 else if (path.isEmpty ())
496 std::cout <<
"Empty path for data:"
497 <<
d.toStdString () << std::endl;
499 path_map[
"#" + id] = path;
501 painter.drawPath (path);
504 if (path_map[
"#" +
id].isEmpty ())
505 std::cout <<
"Empty path for data:"
506 <<
d.toStdString () << std::endl;
510 else if (elt.tagName () ==
"use")
512 painter.setPen (Qt::NoPen);
514 QString str = elt.attribute (
"xlink:href");
515 if (! str.isEmpty () && str.size () > 2)
517 QPainterPath path = path_map[str];
518 if (! path.isEmpty ())
520 str = elt.attribute (
"x");
521 double x = elt.attribute (
"x").toDouble ();
522 str = elt.attribute (
"y");
523 double y = elt.attribute (
"y").toDouble ();
524 painter.translate (
x, y);
525 painter.drawPath (path);
526 painter.translate (-
x, -y);
530 else if (elt.tagName () ==
"text")
533 QFont saved_font (font);
535 QString str = elt.attribute (
"font-family");
536 if (! str.isEmpty ())
541 font = QFont (str, -1, font.weight (),
542 font.style () == QFont::StyleItalic);
543 font.setPixelSize (saved_font.pixelSize ());
546 str = elt.attribute (
"font-weight");
547 if (! str.isEmpty ())
550 font.setWeight (QFont::Bold);
552 font.setWeight (QFont::Normal);
555 str = elt.attribute (
"font-style");
556 if (! str.isEmpty ())
559 font.setStyle (QFont::StyleItalic);
561 font.setStyle (QFont::StyleNormal);
564 str = elt.attribute (
"font-size");
565 if (! str.isEmpty ())
566 font.setPixelSize (str.toDouble ());
568 painter.setFont (font);
571 str =
get_field (elt.attribute (
"fill"),
"rgb");
572 if (! str.isEmpty ())
574 QStringList clist = str.split (
",");
575 painter.setPen (QColor (clist[0].toInt (), clist[1].toInt (),
579 QStringList xx = elt.attribute (
"x").split (
" ");
580 int y = elt.attribute (
"y").toInt ();
582 if (! str.isEmpty ())
585 for (
const QString& s : xx)
586 if (ii < str.size ())
587 painter.drawText (s.toInt ()-dx, y-dy, str.at (ii++));
593 else if (elt.tagName () ==
"polyline")
596 QColor c (elt.attribute (
"stroke"));
597 QString str = elt.attribute (
"stroke-opacity");
598 if (! str.isEmpty () && str.toDouble () != 1.0
599 && str.toDouble () >= 0.0)
600 c.setAlphaF (str.toDouble ());
606 str = elt.attribute (
"stroke-width");
607 if (! str.isEmpty ())
609 double w = str.toDouble ();
614 str = elt.attribute (
"stroke-linecap");
615 pen.setCapStyle (Qt::SquareCap);
617 pen.setCapStyle (Qt::RoundCap);
618 else if (str ==
"butt")
619 pen.setCapStyle (Qt::FlatCap);
621 str = elt.attribute (
"stroke-linejoin");
622 pen.setJoinStyle (Qt::MiterJoin);
624 pen.setJoinStyle (Qt::RoundJoin);
625 else if (str ==
"bevel")
626 pen.setJoinStyle (Qt::BevelJoin);
628 str = elt.attribute (
"stroke-dasharray");
629 pen.setStyle (Qt::SolidLine);
630 if (! str.isEmpty ())
633 if (pat.count () != 2 || pat[1] != 0)
639 pen.setDashPattern (pat);
643 painter.setPen (pen);
646 else if (elt.tagName () ==
"image")
649 QString href_att = elt.attribute (
"xlink:href");
650 QString prefix (
"data:image/png;base64,");
652 = QByteArray::fromBase64 (href_att.mid (prefix.length ()).toLatin1 ());
654 if (img.loadFromData (data,
"PNG"))
656 QRect pos(elt.attribute (
"x").toInt (),
657 elt.attribute (
"y").toInt (),
658 elt.attribute (
"width").toInt (),
659 elt.attribute (
"height").toInt ());
663 QString str =
get_field (elt.attribute (
"transform"),
"matrix");
664 if (! str.isEmpty ())
667 QTransform tform(m[0], m[1], m[2],
669 painter.setTransform (tform);
672 painter.setRenderHint (QPainter::Antialiasing,
false);
673#if defined (HAVE_QPAINTER_RENDERHINT_LOSSLESS)
674 painter.setRenderHint (QPainter::LosslessImageRendering);
676 painter.drawImage (pos, img);
677 painter.setRenderHint (QPainter::Antialiasing,
true);
681 else if (elt.tagName () ==
"rect")
684 double x = elt.attribute (
"x").toDouble ();
685 double y = elt.attribute (
"y").toDouble ();
688 double wd = elt.attribute (
"width").toDouble ();
689 double hg = elt.attribute (
"height").toDouble ();
692 QColor saved_color = painter.brush ().color ();
694 QString str = elt.attribute (
"fill");
695 if (! str.isEmpty ())
696 painter.setBrush (QColor (str));
698 painter.setPen (Qt::NoPen);
700 painter.drawRect (QRectF (
x, y, wd, hg));
702 if (! str.isEmpty ())
703 painter.setBrush (saved_color);
705 else if (elt.tagName () ==
"polygon")
707 if (! clippath_id.isEmpty ())
711 QString str = elt.attribute (
"fill");
712 if (! str.isEmpty ())
716 str = elt.attribute (
"fill-opacity");
717 if (! str.isEmpty () && str.toDouble () != 1.0
718 && str.toDouble () >= 0.0)
719 color.setAlphaF (str.toDouble ());
725 painter.setBrush (color);
726 painter.setPen (Qt::NoPen);
728 painter.setRenderHint (QPainter::Antialiasing,
false);
729 painter.drawPolygon (p);
730 painter.setRenderHint (QPainter::Antialiasing,
true);
776 QDomNodeList nodes = parent_elt.childNodes ();
777 QColor current_color;
778 QList<QDomNode> replaced_nodes;
779 octave_polygon current_polygon;
782 QList< QPair<QList<QDomNode>, QList<QPolygonF>>> collection;
784 for (
int ii = 0; ii < nodes.count (); ii++)
786 QDomNode node = nodes.at (ii);
787 if (! node.isElement ())
790 QDomElement elt = node.toElement ();
792 if (elt.tagName () ==
"polygon")
794 QString str = elt.attribute (
"fill");
795 if (! str.isEmpty ())
798 str = elt.attribute (
"fill-opacity");
799 if (! str.isEmpty ())
801 double alpha = str.toDouble ();
802 if (alpha != 1.0 && alpha >= 0.0)
803 color.setAlphaF (alpha);
806 if (! current_polygon.count ())
807 current_color = color;
809 if (color != current_color)
812 QList<QPolygonF> polygons
813 = current_polygon.reconstruct (reconstruct_level);
814 collection.push_back (QPair<QList<QDomNode>, QList<QPolygonF>>
815 (replaced_nodes, polygons));
817 replaced_nodes.clear ();
818 current_polygon.reset ();
820 current_color = color;
824 current_polygon.add (p);
825 replaced_nodes.push_back (node);
830 if (current_polygon.count ())
832 QList<QPolygonF> polygons = current_polygon.reconstruct (reconstruct_level);
833 collection.push_back (QPair<QList<QDomNode>, QList<QPolygonF>>
834 (replaced_nodes, polygons));
835 replaced_nodes.clear ();
836 current_polygon.reset ();
843 collection.push_back (QPair<QList<QDomNode>, QList<QPolygonF>>
845 current_polygon.reconstruct (reconstruct_level)));
847 for (
int ii = 0; ii < collection.count (); ii++)
895 const char *doc =
"See \"octave-svgconvert -h\"";
896 const char *help =
"Usage:\n\
897octave-svgconvert infile fmt dpi font reconstruct outfile\n\n\
898Convert svg file to pdf, or svg. All arguments are mandatory:\n\
899* infile: input svg file or \"-\" to indicate that the input svg file should be \
901* fmt: format of the output file. May be one of pdf or svg\n\
902* dpi: device dependent resolution in screen pixel per inch\n\
903* font: specify a file name for the default FreeSans font\n\
904* reconstruct: specify whether to reconstruct triangle to polygons\n\
905 0: no reconstruction (merging) of polygons\n\
906 1: merge consecutive triangles if they share an edge\n\
907 2: merge all triangles that share edges (might take a long time)\n\
908* outfile: output file name\n";
910 if (
strcmp (argv[1],
"-h") == 0)
923 if (
strcmp (argv[1],
"-") != 0)
926 file.setFileName (argv[1]);
927 if (! file.open (QIODevice::ReadOnly | QIODevice::Text))
929 std::cerr <<
"Unable to open file " << argv[1] <<
"\n";
937 if (! file.open (stdin, QIODevice::ReadOnly | QIODevice::Text))
939 std::cerr <<
"Unable to read from stdin\n";
946 QDomDocument document;
947#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
948 QDomDocument::ParseResult parseResult = document.setContent (&file);
951 std::cerr <<
"Failed to parse XML contents" << std::endl
952 <<
"in line " << parseResult.errorLine
953 <<
" column " << parseResult.errorColumn <<
":" << std::endl
954 << parseResult.errorMessage.toStdString () << std::endl;
960 if (! document.setContent (&file,
false, &msg))
962 std::cerr <<
"Failed to parse XML contents" << std::endl
963 << msg.toStdString () << std::endl;
972 if (
strcmp (argv[2],
"pdf") != 0 &&
strcmp (argv[2],
"svg") != 0)
974 std::cerr <<
"Unhandled output file format " << argv[2] <<
"\n";
984 QDomElement root = document.firstChildElement();
985 double x0, y0, dx, dy;
986 QString s = root.attribute (
"viewBox");
987 QTextStream (&s) >> x0 >> y0 >> dx >> dy;
988 QRectF vp (x0, y0, dx, dy);
991 QApplication a (argc, argv);
994 if (!
strcmp (argv[2],
"pdf"))
996 QFont font (
"FreeSans");
997 if (! font.exactMatch ())
999 QString fontpath (argv[4]);
1000 if (! fontpath.isEmpty ())
1002 int id = QFontDatabase::addApplicationFont (fontpath);
1004 std::cerr <<
"warning: print: "
1005 "Unable to add default font to database\n";
1008 std::cerr <<
"warning: print: FreeSans font not found\n";
1013 QTemporaryFile fout;
1016 std::cerr <<
"Could not open temporary file\n";
1021 int reconstruct_level = QString (argv[5]).toInt ();
1022 if (reconstruct_level)
1029 if (!
strcmp (argv[2],
"pdf"))
1032 pdfpainter painter (fout.fileName (), vp);
1034 draw (root, painter);
1039 QTextStream out (&fout);
1040#if HAVE_QTEXTSTREAM_SETENCODING
1041 out.setEncoding (QStringConverter::Utf8);
1043 out.setCodec (
"UTF-8");
1045 out << document.toByteArray ();
1049 if (QFile::exists (argv[6]))
1050 if (! QFile::remove (argv[6]))
1052 std::cerr <<
"Unable to replace existing file " << argv[6] <<
"\n";
1056 fout.copy (argv[6]);