72 using SVGAttrib = std::map<std::string, std::string>;
73 using Point = std::pair<double, double>;
75 const static Margins DEFAULT_MARGINS = { 10, 10, 10, 10 };
76 const static Margins NO_MARGINS = { 0, 0, 0, 0 };
78#if __cplusplus < 201402L
80 template<
typename T,
typename... Args>
81 std::unique_ptr<T> make_unique(Args&&... args) {
82 return std::unique_ptr<T>(
new T(std::forward<Args>(args)...));
87 using std::make_unique;
91 inline std::string
to_string(
const double& value);
92 inline std::string
to_string(
const Point& point);
93 inline std::string
to_string(
const std::map<std::string, AttributeMap>& css,
const size_t indent_level=0);
95 std::vector<Point> bounding_polygon(
const std::vector<Shape*>& shapes);
97 SVG merge(
SVG& left,
SVG& right,
const Margins& margins = DEFAULT_MARGINS);
98 SVG merge(std::vector<SVG>& frames,
const double width,
const int max_frame_width);
106 explicit ClassList(std::string& value) : mutable_value_(&value), value_(&value) {}
107 explicit ClassList(
const std::string& value) : value_(&value) {}
111 validate_token(token);
112 const auto values =
tokens();
113 return std::find(values.begin(), values.end(), token) != values.end();
118 validate_token(token);
120 if (std::find(values.begin(), values.end(), token) == values.end()) {
121 values.push_back(token);
129 validate_token(token);
131 const auto original_size = values.size();
132 values.erase(std::remove(values.begin(), values.end(), token), values.end());
133 if (values.size() != original_size) {
152 write(parse(class_names));
168 std::vector<std::string>
tokens()
const {
169 return parse(value());
173 static bool is_space(
char ch) {
174 return std::isspace(
static_cast<unsigned char>(ch)) != 0;
177 static void validate_token(
const std::string& token) {
179 throw std::invalid_argument(
"class token cannot be empty");
181 if (std::find_if(token.begin(), token.end(), is_space) != token.end()) {
182 throw std::invalid_argument(
"class token cannot contain whitespace");
186 static std::vector<std::string> parse(
const std::string& class_names) {
187 std::vector<std::string> result;
189 for (
const auto ch : class_names) {
191 if (!token.empty()) {
192 if (std::find(result.begin(), result.end(), token) == result.end()) {
193 result.push_back(token);
201 if (!token.empty() && std::find(result.begin(), result.end(), token) == result.end()) {
202 result.push_back(token);
207 static std::string join(
const std::vector<std::string>& values) {
209 for (std::size_t i = 0; i < values.size(); ++i) {
218 const std::string& value()
const {
219 static const std::string empty;
220 return value_ ? *value_ : empty;
223 void write(
const std::vector<std::string>& values) {
224 if (!mutable_value_) {
225 throw std::logic_error(
"cannot mutate a const class list");
227 *mutable_value_ = join(values);
228 value_ = mutable_value_;
231 std::string* mutable_value_ =
nullptr;
232 const std::string* value_ =
nullptr;
240 COLINEAR, CLOCKWISE, COUNTERCLOCKWISE
243 inline std::vector<Point> polar_points(
int n,
int a,
int b,
double radius);
246 inline T min_or_not_nan(T first, T second) {
250 if (isnan(first) && isnan(second))
252 else if (isnan(first) || isnan(second))
253 return isnan(first) ? second : first;
255 return std::min(first, second);
259 inline T max_or_not_nan(T first, T second) {
263 if (isnan(first) && isnan(second))
265 else if (isnan(first) || isnan(second))
266 return isnan(first) ? second : first;
268 return std::max(first, second);
271 inline Orientation orientation(Point& p1, Point& p2, Point& p3) {
272 double value = ((p2.second - p1.second) * (p3.first - p2.first) -
273 (p2.first - p1.first) * (p3.second - p2.second));
275 if (value == 0)
return COLINEAR;
276 else if (value > 0)
return CLOCKWISE;
277 else return COUNTERCLOCKWISE;
280 inline std::vector<Point> convex_hull(std::vector<Point>& points) {
287 if (points.size() < 3)
return {};
288 std::vector<Point> hull;
292 for (
size_t i = 0; i < points.size(); i++)
293 if (points[i].first < points[left].first) left = (int)i;
296 int current = left, next;
299 hull.push_back(points[current]);
302 next = (current + 1) % points.size();
303 for (
size_t i = 0; i < points.size(); i++) {
305 if (orientation(points[current], points[next], points[i]) == COUNTERCLOCKWISE)
310 }
while (current != left);
315 inline std::vector<Point> polar_points(
int n,
int a,
int b,
double radius) {
322 std::vector<Point> ret;
323 for (
double degree = 0; degree < 360; degree += 360/n) {
325 a + radius * cos(degree * (PI/180)),
326 b + radius * sin(degree * (PI/180))
336 std::stringstream ss;
337 ss << std::fixed << std::setprecision(1);
353 AttrSetter(SVGAttrib::mapped_type& _attr,
bool _normalize_class =
false) :
354 attr(_attr), normalize_class(_normalize_class) {};
355 SVGAttrib::mapped_type& attr;
356 bool normalize_class =
false;
360 attr += std::to_string(value);
366 if (normalize_class) {
377 AttributeMap& set_attr(
const std::string key, T value) {
378 this->set_attr_value(key, std::to_string(value));
384 for (
const auto& pair : values) {
385 this->set_attr_value(pair.first, pair.second);
391 AttrSetter set_attr(
const std::string key) {
392 if (this->attr.find(key) == this->attr.end()) this->attr[key] =
"";
393 return AttrSetter(this->attr.at(key), key ==
"class");
396 ClassList class_list() {
397 return ClassList(this->attr[
"class"]);
400 ClassList class_list()
const {
401 const auto found = this->attr.find(
"class");
402 return found == this->attr.end() ? ClassList() : ClassList(found->second);
406 void set_attr_value(
const std::string& key,
const std::string& value) {
407 if (key ==
"class") {
408 ClassList(this->attr[key]).set(value);
411 this->attr[key] = value;
416 inline AttributeMap::AttrSetter& AttributeMap::AttrSetter::operator<<(
const char * value) {
423 inline AttributeMap& AttributeMap::set_attr(
const std::string key,
const double value) {
425 this->set_attr_value(key,
to_string(value));
430 inline AttributeMap& AttributeMap::set_attr(
const std::string key,
const char * value) {
432 this->set_attr_value(key, value);
437 inline AttributeMap& AttributeMap::set_attr(
const std::string key,
const std::string value) {
439 this->set_attr_value(key, value);
453 using QuadCoord::QuadCoord;
459 using namespace util;
461 new_box.x1 = min_or_not_nan(this->x1, other.x1);
462 new_box.x2 = max_or_not_nan(this->x2, other.x2);
463 new_box.y1 = min_or_not_nan(this->y1, other.y1);
464 new_box.y2 = max_or_not_nan(this->y2, other.y2);
468 using ChildList = std::vector<Element*>;
469 using ChildMap = std::map<std::string, ChildList>;
479 SVGAttrib({ {
"id",
id } })) {};
480 using AttributeMap::AttributeMap;
483 operator std::string() {
return this->svg_to_string(0); };
485 template<
typename T,
typename... Args>
489 this->children.push_back(detail::make_unique<T>(std::forward<Args>(args)...));
490 return (T*)this->children.back().get();
497 this->children.push_back(detail::make_unique<T>(std::move(node)));
506 auto child_elems = this->get_children_helper();
508 for (
auto& child: child_elems)
509 if (
typeid(*child) ==
typeid(T)) ret.push_back((T*)child);
519 for (
auto& child : this->children) {
521 if (
typeid(t) ==
typeid(T)) ret.push_back((T*)child.get());
527 Element* get_element_by_id(
const std::string&
id);
528 std::vector<Element*> get_elements_by_class(
const std::string& clsname);
529 void autoscale(
const Margins& margins=DEFAULT_MARGINS);
530 void autoscale(
const double margin);
531 virtual BoundingBox get_bbox();
532 ChildMap get_children();
535 std::vector<std::unique_ptr<Element>> children;
536 std::vector<Element*> get_children_helper();
538 virtual std::string svg_to_string(
const size_t indent_level);
539 virtual std::string
tag() = 0;
546 if (attr.find(key) != attr.end())
547 return std::stof(attr[key]);
555 Element::ChildList ret;
556 for (
auto& child : this->children) ret.push_back(child.get());
562 auto child_elems = this->get_children_helper();
563 for (
auto& current: child_elems)
564 if (current->attr.find(
"id") != current->attr.end() &&
565 current->attr.find(
"id")->second == id)
return current;
572 std::vector<Element*> ret;
573 auto child_elems = this->get_children_helper();
575 for (
auto& current: child_elems) {
576 if ((current->attr.find(
"class") != current->attr.end())
577 && current->class_list().contains(clsname))
578 ret.push_back(current);
586 return { NAN, NAN, NAN, NAN };
594 using Element::Element;
598 return std::make_pair(this->x(), this->y());
603 auto bbox = this->get_bbox();
605 Point(bbox.x1, bbox.y1),
606 Point(bbox.x2, bbox.y1),
607 Point(bbox.x1, bbox.y2),
608 Point(bbox.x2, bbox.y2)
612 virtual double x() {
return this->find_numeric(
"x"); }
613 virtual double y() {
return this->find_numeric(
"y"); }
618 return this->find_numeric(
"width");
624 return this->find_numeric(
"height");
633 using Element::Element;
639 std::string svg_to_string(
const size_t)
override;
640 std::string
tag()
override {
return "style"; };
644 SVG(SVGAttrib _attr =
645 { {
"xmlns",
"http://www.w3.org/2000/svg" } }
653 return this->css->media_queries[query][key];
656 std::map<std::string, AttributeMap>&
keyframes(
const std::string& key) {
661 if (!this->css) this->css = this->add_child<Style>();
662 return this->css->keyframes[key];
665 Style* css = this->add_child<Style>();
668 std::string
tag()
override {
return "svg"; }
680 this->attr[
"d"] =
"M " + std::to_string(x) +
" " + std::to_string(y);
692 if (this->attr.find(
"d") == this->attr.end())
695 this->attr[
"d"] +=
" L " + std::to_string(x) +
696 " " + std::to_string(y);
699 inline void line_to(std::pair<double, double> coord) {
700 this->line_to(coord.first, coord.second);
705 this->line_to(x_start, y_start);
709 std::string
tag()
override {
return "path"; }
719 using Element::Element;
721 Text(
double x,
double y, std::string _content) {
727 Text(std::pair<double, double> xy, std::string _content) :
728 Text(xy.first, xy.second, _content) {};
732 std::string svg_to_string(
const size_t)
override;
733 std::string
tag()
override {
return "text"; }
738 using Element::Element;
740 std::string
tag()
override {
return "g"; }
748 Line(
double x1,
double x2,
double y1,
double y2) :
Shape({
755 Line(Point x, Point y) :
Line(x.first, y.first, x.second, y.second) {};
757 virtual double x()
override {
return x1() + (x2() - x1()) / 2; }
758 virtual double y()
override {
return y1() + (y2() - y1()) / 2; }
759 double x1() {
return this->find_numeric(
"x1"); }
760 double x2() {
return this->find_numeric(
"x2"); }
761 double y1() {
return this->find_numeric(
"y1"); }
762 double y2() {
return this->find_numeric(
"y2"); }
764 double width()
override {
return std::abs(x2() - x1()); }
765 double height()
override {
return std::abs(y2() - y1()); }
766 double length() {
return std::sqrt(pow(width(), 2) + pow(height(), 2)); }
767 double slope() {
return (y2() - y1()) / (x2() - x1()); }
768 double angle() {
return atan(this->slope()) * RAD_TO_DEG; }
770 std::pair<double, double> along(
double percent);
774 std::string
tag()
override {
return "line"; }
783 double x,
double y,
double width,
double height) :
793 std::string
tag()
override {
return "rect"; }
801 Circle(
double cx,
double cy,
double radius) :
809 Circle(std::pair<double, double> xy,
double radius) :
Circle(xy.first, xy.second, radius) {};
810 double radius() {
return this->find_numeric(
"r"); }
811 virtual double x()
override {
return this->find_numeric(
"cx"); }
812 virtual double y()
override {
return this->find_numeric(
"cy"); }
813 virtual double width()
override {
return this->radius() * 2; }
814 virtual double height()
override {
return this->width(); }
818 std::string
tag()
override {
return "circle"; }
824 using Element::Element;
826 Polygon(
const std::vector<Point>& points) {
828 std::string& point_str = this->attr[
"points"];
829 for (
auto& pt : points)
834 std::string
tag()
override {
return "polygon"; }
837 inline Element::BoundingBox Line::get_bbox() {
838 return { x1(), x2(), y1(), y2() };
841 inline Element::BoundingBox Rect::get_bbox() {
842 double x = this->x(), y = this->y(),
843 width = this->width(), height = this->height();
844 return { x, x + width, y, y + height };
847 inline Element::BoundingBox Circle::get_bbox() {
848 double x = this->x(), y = this->y(), radius = this->radius();
866 double length = percent * this->length();
867 double discrim = std::sqrt(4 * pow(length, 2) * (1 / (1 + pow(slope(), 2))));
869 double x_a = (2 * x1() + discrim) / 2;
870 double x_b = (2 * x1() - discrim) / 2;
873 if ((x_a > x1() && x_a > x2()) || (x_a < x1() && x_a < x2()))
876 y_pos = slope() * (x_pos - x1()) + y1();
882 y_pos = y1() - percent * this->length();
884 y_pos = y1() + percent * this->length();
887 return std::make_pair(x_pos, y_pos);
896 auto indent = std::string(indent_level,
'\t');
897 std::string ret = indent +
"<" + tag();
900 for (
auto& pair: attr)
901 ret +=
" " + pair.first +
"=" +
"\"" + pair.second +
"\"";
903 if (!this->children.empty()) {
907 for (
auto& child : children) {
909 auto str = child->svg_to_string(indent_level + 1);
910 if (str.size()) ret += str +
"\n";
913 return ret += indent +
"</" + tag() +
">";
919 inline std::string
to_string(
const std::map<std::string, AttributeMap>& css,
const size_t indent_level) {
921 auto indent = std::string(indent_level,
'\t'), ret = std::string();
922 for (
auto& selector : css) {
924 ret += indent +
"\t\t" + selector.first +
" {\n";
925 for (
auto& attr : selector.second.attr)
926 ret += indent +
"\t\t\t" + attr.first +
": " + attr.second +
";\n";
927 ret += indent +
"\t\t" +
"}\n";
934 auto indent = std::string(indent_level,
'\t');
936 if (!this->css.empty() || !this->media_queries.empty() || !this->keyframes.empty()) {
937 std::string ret = indent +
"<style type=\"text/css\">\n" +
938 indent +
"\t<![CDATA[\n";
941 ret +=
to_string(this->css, indent_level);
944 for (
auto& media : this->media_queries) {
945 ret += indent +
"\t\t@media " + media.first +
" {\n" +
946 to_string(media.second, indent_level + 1) +
947 indent +
"\t\t" +
"}\n";
951 for (
auto& anim : this->keyframes) {
952 ret += indent +
"\t\t@keyframes " + anim.first +
" {\n" +
953 to_string(anim.second, indent_level + 1) +
954 indent +
"\t\t" +
"}\n";
957 ret += indent +
"\t]]>\n";
958 return ret + indent +
"</style>";
964 inline std::string Text::svg_to_string(
const size_t indent_level) {
965 auto indent = std::string(indent_level,
'\t');
966 std::string ret = indent +
"<text";
967 for (
auto& pair: attr)
968 ret +=
" " + pair.first +
"=" +
"\"" + pair.second +
"\"";
969 return ret +=
">" + this->content +
"</text>";
975 this->get_bbox(bbox);
976 double width = abs(bbox.x1) + abs(bbox.x2),
977 height = abs(bbox.y1) + abs(bbox.y2);
980 width * margin, width * margin,
981 height * margin, height * margin
994 this->get_bbox(bbox);
995 double width = abs(bbox.x1) + abs(bbox.x2) + margins.x1 + margins.x2,
996 height = abs(bbox.y1) + abs(bbox.y2) + margins.y1 + margins.y2,
997 x1 = bbox.x1 - margins.x1, y1 = bbox.y1 - margins.y1;
999 this->set_attr(
"width", width)
1000 .set_attr(
"height", height);
1002 if (x1 < 0 || y1 < 0) {
1003 std::stringstream viewbox;
1004 viewbox << std::fixed << std::setprecision(1)
1009 this->set_attr(
"viewBox", viewbox.str());
1015 auto this_bbox = this->get_bbox();
1016 box = this_bbox + box;
1017 for (
auto& child: this->children) child->get_bbox(box);
1022 Element::ChildMap child_map;
1023 for (
auto& child : this->get_children_helper())
1024 child_map[child->tag()].push_back(child);
1030 std::deque<Element*> temp;
1031 std::vector<Element*> ret;
1033 for (
auto& child : this->children) { temp.push_back(child.get()); }
1034 while (!temp.empty()) {
1035 ret.push_back(temp.front());
1036 for (
auto& child : temp.front()->children) { temp.push_back(child.get()); }
1048 ret << std::move(left) << std::move(right);
1052 svg_child->autoscale(margins);
1055 double x = 0, height = 0;
1057 svg_child->set_attr(
"x", x).set_attr(
"y", 0);
1058 x += svg_child->width();
1059 height = std::max(height, svg_child->height());
1062 ret.set_attr(
"width", x).set_attr(
"height", height);
1066 inline std::vector<Point> bounding_polygon(std::vector<Shape*>& shapes) {
1070 std::vector<Point> points;
1071 for (
auto& shp : shapes) {
1072 auto temp_points = shp->points();
1073 std::move(temp_points.begin(), temp_points.end(), std::back_inserter(points));
1076 return util::convex_hull(points);
1079 inline SVG merge(std::vector<SVG>& frames,
const double width,
const int max_frame_width) {
1084 double x = 0, y = 0, total_width = 0, total_height = 0;
1085 for (
auto& frame : frames) {
1088 if (frame.width() > max_frame_width) {
1089 const double scale_factor = max_frame_width/frame.width();
1090 frame.set_attr(
"width", max_frame_width);
1091 frame.set_attr(
"height", frame.height() * scale_factor);
1096 double current_height = 0;
1097 for (
auto& frame : frames) {
1099 if ((x + frame.width()) > width) {
1100 total_width = std::max(total_width, x);
1102 y += current_height;
1106 frame.set_attr(
"x", x).set_attr(
"y", y);
1108 current_height = std::max(current_height, frame.height());
1109 root << std::move(frame);
1112 total_height = y + current_height;
1115 root.set_attr(
"viewBox") << 0 <<
" " << 0 <<
" " << total_width <<
" " << total_height;
1116 root.set_attr(
"width", total_width).set_attr(
"height", total_height);
1127 const double duration = (double)frames.size() / fps;
1128 const double frame_step = 1.0 / fps;
1129 int current_frame = 0;
1131 root.
style(
"svg.animated").set_attr(
"animation-iteration-count",
"infinite")
1132 .set_attr(
"animation-timing-function",
"step-end")
1133 .set_attr(
"animation-duration", std::to_string(duration) +
"s")
1134 .set_attr(
"opacity", 0);
1137 for (
auto& frame : frames) {
1138 std::string frame_id =
"frame_" + std::to_string(current_frame);
1139 frame.set_attr(
"id", frame_id).set_attr(
"class",
"animated");
1140 root.
style(
"#" + frame_id).set_attr(
"animation-name",
1141 "anim_" + std::to_string(current_frame));
1143 root << std::move(frame);
1147 for (
size_t i = 0, ilen = frames.size(); i < ilen; i++) {
1148 auto& anim = root.
keyframes(
"anim_" + std::to_string(i));
1149 double begin_pct = (double)i / frames.size(),
1150 end_pct = (double)(i + 1) / frames.size();
1151 anim[
"0%"].set_attr(
"opacity", 0);
1152 anim[std::to_string(begin_pct * 100) +
"%"].set_attr(
"opacity", 1);
1153 anim[std::to_string(end_pct * 100) +
"%"].set_attr(
"opacity", 0);
1157 double width = 0, height = 0;
1161 width = std::max(width, child->width());
1162 height = std::max(height, child->height());
1165 root.set_attr(
"viewBox",
"0 0 " + std::to_string(width) +
" " + std::to_string(height));
1169 child->set_attr(
"x", (width - child->width())/2).set_attr(
"y", (height - child->height())/2);
std::string tag() override
Definition svg.hpp:733