SVG for C++
Loading...
Searching...
No Matches
svg.hpp
Go to the documentation of this file.
1/*
2 ____ __ ______
3 / ___|\ \ / / ___|
4 \___ \ \ \ / / | _
5 ___) | \ V /| |_| |
6 |____/ \_/ \____|
7
8 SVG for C++ v0.2.0
9
10 Copyright (c) 2018-2026 Vincent La
11 SPDX-License-Identifier: MIT
12
13 Permission is hereby granted, free of charge, to any person obtaining a copy
14 of this software and associated documentation files (the "Software"), to deal
15 in the Software without restriction, including without limitation the rights
16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the Software is
18 furnished to do so, subject to the following conditions:
19
20 The above copyright notice and this permission notice shall be included in all
21 copies or substantial portions of the Software.
22
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 SOFTWARE.
30*/
31
33#pragma once
34#define PI 3.14159265
35#define RAD_TO_DEG (180/PI)
36#define SVG_TYPE_CHECK static_assert(std::is_base_of<Element, T>::value, "Child must be an SVG element.")
37#define APPROX_EQUALS(x, y, tol) bool(abs(x - y) < tol)
38#include <iostream>
39#include <algorithm> // min, max
40#include <cctype>
41#include <fstream> // ofstream
42#include <math.h> // NAN
43#include <map>
44#include <deque>
45#include <vector>
46#include <string>
47#include <sstream> // stringstream
48#include <iomanip> // setprecision
49#include <memory>
50#include <stdexcept>
51#include <type_traits> // is_base_of
52#include <typeinfo>
53#include <utility>
54
55namespace SVG {
59 class AttributeMap;
60 class SVG;
61 class Shape;
62
63 struct QuadCoord {
64 double x1;
65 double x2;
66 double y1;
67 double y2;
68 };
69
71 using SelectorProperties = std::map<std::string, AttributeMap>;
72 using SVGAttrib = std::map<std::string, std::string>;
73 using Point = std::pair<double, double>;
74 using Margins = QuadCoord;
75 const static Margins DEFAULT_MARGINS = { 10, 10, 10, 10 };
76 const static Margins NO_MARGINS = { 0, 0, 0, 0 };
77
78#if __cplusplus < 201402L
79 namespace detail {
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)...));
83 }
84 }
85#else
86 namespace detail {
87 using std::make_unique;
88 }
89#endif
90
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);
94
95 std::vector<Point> bounding_polygon(const std::vector<Shape*>& shapes);
96 SVG frame_animate(std::vector<SVG>& frames, const double fps);
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);
99
103 class ClassList {
104 public:
105 ClassList() = default;
106 explicit ClassList(std::string& value) : mutable_value_(&value), value_(&value) {}
107 explicit ClassList(const std::string& value) : value_(&value) {}
108
110 bool contains(const std::string& token) const {
111 validate_token(token);
112 const auto values = tokens();
113 return std::find(values.begin(), values.end(), token) != values.end();
114 }
115
117 ClassList& add(const std::string& token) {
118 validate_token(token);
119 auto values = tokens();
120 if (std::find(values.begin(), values.end(), token) == values.end()) {
121 values.push_back(token);
122 write(values);
123 }
124 return *this;
125 }
126
128 ClassList& remove(const std::string& token) {
129 validate_token(token);
130 auto values = tokens();
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) {
134 write(values);
135 }
136 return *this;
137 }
138
140 bool toggle(const std::string& token) {
141 if (contains(token)) {
142 remove(token);
143 return false;
144 }
145
146 add(token);
147 return true;
148 }
149
151 ClassList& set(const std::string& class_names) {
152 write(parse(class_names));
153 return *this;
154 }
155
158 write({});
159 return *this;
160 }
161
163 std::string str() const {
164 return join(tokens());
165 }
166
168 std::vector<std::string> tokens() const {
169 return parse(value());
170 }
171
172 private:
173 static bool is_space(char ch) {
174 return std::isspace(static_cast<unsigned char>(ch)) != 0;
175 }
176
177 static void validate_token(const std::string& token) {
178 if (token.empty()) {
179 throw std::invalid_argument("class token cannot be empty");
180 }
181 if (std::find_if(token.begin(), token.end(), is_space) != token.end()) {
182 throw std::invalid_argument("class token cannot contain whitespace");
183 }
184 }
185
186 static std::vector<std::string> parse(const std::string& class_names) {
187 std::vector<std::string> result;
188 std::string token;
189 for (const auto ch : class_names) {
190 if (is_space(ch)) {
191 if (!token.empty()) {
192 if (std::find(result.begin(), result.end(), token) == result.end()) {
193 result.push_back(token);
194 }
195 token.clear();
196 }
197 continue;
198 }
199 token.push_back(ch);
200 }
201 if (!token.empty() && std::find(result.begin(), result.end(), token) == result.end()) {
202 result.push_back(token);
203 }
204 return result;
205 }
206
207 static std::string join(const std::vector<std::string>& values) {
208 std::string result;
209 for (std::size_t i = 0; i < values.size(); ++i) {
210 if (i > 0) {
211 result += ' ';
212 }
213 result += values[i];
214 }
215 return result;
216 }
217
218 const std::string& value() const {
219 static const std::string empty;
220 return value_ ? *value_ : empty;
221 }
222
223 void write(const std::vector<std::string>& values) {
224 if (!mutable_value_) {
225 throw std::logic_error("cannot mutate a const class list");
226 }
227 *mutable_value_ = join(values);
228 value_ = mutable_value_;
229 }
230
231 std::string* mutable_value_ = nullptr;
232 const std::string* value_ = nullptr;
233 };
234
238 namespace util {
239 enum Orientation {
240 COLINEAR, CLOCKWISE, COUNTERCLOCKWISE
241 };
242
243 inline std::vector<Point> polar_points(int n, int a, int b, double radius);
244
245 template<typename T>
246 inline T min_or_not_nan(T first, T second) {
250 if (isnan(first) && isnan(second))
251 return NAN;
252 else if (isnan(first) || isnan(second))
253 return isnan(first) ? second : first;
254 else
255 return std::min(first, second);
256 }
257
258 template<typename T>
259 inline T max_or_not_nan(T first, T second) {
263 if (isnan(first) && isnan(second))
264 return NAN;
265 else if (isnan(first) || isnan(second))
266 return isnan(first) ? second : first;
267 else
268 return std::max(first, second);
269 }
270
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));
274
275 if (value == 0) return COLINEAR;
276 else if (value > 0) return CLOCKWISE;
277 else return COUNTERCLOCKWISE;
278 }
279
280 inline std::vector<Point> convex_hull(std::vector<Point>& points) {
287 if (points.size() < 3) return {}; // Need at least three points
288 std::vector<Point> hull;
289
290 // Find leftmost point (ties don't matter)
291 int left = 0;
292 for (size_t i = 0; i < points.size(); i++)
293 if (points[i].first < points[left].first) left = (int)i;
294
295 // While we don't reach leftmost point
296 int current = left, next;
297 do {
298 // Add to convex hull
299 hull.push_back(points[current]);
300
301 // Keep moving counterclockwise
302 next = (current + 1) % points.size();
303 for (size_t i = 0; i < points.size(); i++) {
304 // We've found a more counterclockwise point --> update next
305 if (orientation(points[current], points[next], points[i]) == COUNTERCLOCKWISE)
306 next = (int)i;
307 }
308
309 current = next;
310 } while (current != left);
311
312 return hull;
313 }
314
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) {
324 ret.push_back(Point(
325 a + radius * cos(degree * (PI/180)), // 1 degree = pi/180 radians
326 b + radius * sin(degree * (PI/180))
327 ));
328 }
329
330 return ret;
331 }
332 }
333
334 inline std::string to_string(const double& value) {
336 std::stringstream ss;
337 ss << std::fixed << std::setprecision(1);
338 ss << value;
339 return ss.str();
340 }
341
342 inline std::string to_string(const Point& point) {
344 return to_string(point.first) + "," + to_string(point.second);
345 }
346
351 public:
352 struct AttrSetter {
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;
357
358 template<typename T>
359 AttrSetter& operator<<(T value) {
360 attr += std::to_string(value);
361 normalize();
362 return *this;
363 }
364
365 void normalize() {
366 if (normalize_class) {
367 ClassList(attr).set(attr);
368 }
369 }
370 };
371
372 AttributeMap() = default;
373 AttributeMap(SVGAttrib _attr) : attr(_attr) {};
374 SVGAttrib attr;
375
376 template<typename T>
377 AttributeMap& set_attr(const std::string key, T value) {
378 this->set_attr_value(key, std::to_string(value));
379 return *this;
380 }
381
383 AttributeMap& set_attrs(std::initializer_list<std::pair<std::string, std::string>> values) {
384 for (const auto& pair : values) {
385 this->set_attr_value(pair.first, pair.second);
386 }
387
388 return *this;
389 }
390
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");
394 };
395
396 ClassList class_list() {
397 return ClassList(this->attr["class"]);
398 }
399
400 ClassList class_list() const {
401 const auto found = this->attr.find("class");
402 return found == this->attr.end() ? ClassList() : ClassList(found->second);
403 }
404
405 private:
406 void set_attr_value(const std::string& key, const std::string& value) {
407 if (key == "class") {
408 ClassList(this->attr[key]).set(value);
409 return;
410 }
411 this->attr[key] = value;
412 }
413 };
414
415 template<>
416 inline AttributeMap::AttrSetter& AttributeMap::AttrSetter::operator<<(const char * value) {
417 attr += value;
418 normalize();
419 return *this;
420 }
421
422 template<>
423 inline AttributeMap& AttributeMap::set_attr(const std::string key, const double value) {
425 this->set_attr_value(key, to_string(value));
426 return *this;
427 }
428
429 template<>
430 inline AttributeMap& AttributeMap::set_attr(const std::string key, const char * value) {
432 this->set_attr_value(key, value);
433 return *this;
434 }
435
436 template<>
437 inline AttributeMap& AttributeMap::set_attr(const std::string key, const std::string value) {
439 this->set_attr_value(key, value);
440 return *this;
441 }
442
446 class Element: public AttributeMap {
447 public:
451 class BoundingBox : public QuadCoord {
452 public:
453 using QuadCoord::QuadCoord;
454 BoundingBox() = default;
455 BoundingBox(double a, double b, double c, double d) : QuadCoord({ a, b, c, d }) {};
456
457 BoundingBox operator+ (const BoundingBox& other) {
459 using namespace util;
460 BoundingBox new_box;
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);
465 return new_box;
466 }
467 };
468 using ChildList = std::vector<Element*>;
469 using ChildMap = std::map<std::string, ChildList>;
470
471 Element() = default;
472 virtual ~Element() = default;
473 Element(const Element& other) = delete; // No copy constructor
474 Element(Element&& other) = default; // Move constructor
475 Element& operator=(const Element&) = delete; // No copy assignment
476 Element& operator=(Element&& other) = default;
477
478 Element(const char* id) : AttributeMap(
479 SVGAttrib({ { "id", id } })) {};
480 using AttributeMap::AttributeMap;
481
482 // Implicit string conversion
483 operator std::string() { return this->svg_to_string(0); };
484
485 template<typename T, typename... Args>
486 T* add_child(Args&&... args) {
488 SVG_TYPE_CHECK;
489 this->children.push_back(detail::make_unique<T>(std::forward<Args>(args)...));
490 return (T*)this->children.back().get();
491 }
492
493 template<typename T>
494 Element& operator<<(T&& node) {
496 SVG_TYPE_CHECK;
497 this->children.push_back(detail::make_unique<T>(std::move(node)));
498 return *this;
499 }
500
501 template<typename T>
502 std::vector<T*> get_children() {
504 SVG_TYPE_CHECK;
505 std::vector<T*> ret;
506 auto child_elems = this->get_children_helper();
507
508 for (auto& child: child_elems)
509 if (typeid(*child) == typeid(T)) ret.push_back((T*)child);
510
511 return ret;
512 }
513
514 template<typename T>
515 std::vector<T*> get_immediate_children() {
517 SVG_TYPE_CHECK;
518 std::vector<T*> ret;
519 for (auto& child : this->children) {
520 auto& t = *child;
521 if (typeid(t) == typeid(T)) ret.push_back((T*)child.get());
522 }
523
524 return ret;
525 }
526
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();
533
534 protected:
535 std::vector<std::unique_ptr<Element>> children;
536 std::vector<Element*> get_children_helper();
537 void get_bbox(Element::BoundingBox&);
538 virtual std::string svg_to_string(const size_t indent_level);
539 virtual std::string tag() = 0;
541 double find_numeric(const std::string& key) {
546 if (attr.find(key) != attr.end())
547 return std::stof(attr[key]);
548 return NAN;
549 }
550 };
551
552 template<>
553 inline Element::ChildList Element::get_immediate_children() {
555 Element::ChildList ret;
556 for (auto& child : this->children) ret.push_back(child.get());
557 return ret;
558 }
559
560 inline Element* Element::get_element_by_id(const std::string &id) {
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;
566
567 return nullptr;
568 }
569
570 inline std::vector<Element*> Element::get_elements_by_class(const std::string &clsname) {
572 std::vector<Element*> ret;
573 auto child_elems = this->get_children_helper();
574
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);
579 }
580
581 return ret;
582 }
583
586 return { NAN, NAN, NAN, NAN };
587 }
588
592 class Shape: public Element {
593 public:
594 using Element::Element;
595
596 operator Point() {
598 return std::make_pair(this->x(), this->y());
599 }
600
601 virtual std::vector<Point> points() {
603 auto bbox = this->get_bbox();
604 return {
605 Point(bbox.x1, bbox.y1), // Top left
606 Point(bbox.x2, bbox.y1), // Top right
607 Point(bbox.x1, bbox.y2), // Bottom left
608 Point(bbox.x2, bbox.y2) // Bottom right
609 };
610 }
611
612 virtual double x() { return this->find_numeric("x"); }
613 virtual double y() { return this->find_numeric("y"); }
614 virtual double width() {
618 return this->find_numeric("width");
619 }
620 virtual double height() {
624 return this->find_numeric("height");
625 }
626 };
627
628 class SVG : public Shape {
629 public:
630 class Style : public Element {
631 public:
632 Style() = default;
633 using Element::Element;
635 std::map<std::string, SelectorProperties> media_queries;
636 std::map<std::string, SelectorProperties> keyframes;
638 protected:
639 std::string svg_to_string(const size_t) override;
640 std::string tag() override { return "style"; };
641 };
642
644 SVG(SVGAttrib _attr =
645 { { "xmlns", "http://www.w3.org/2000/svg" } }
646 ) : Shape(_attr) {};
647
649 AttributeMap& style(const std::string& key) { return this->css->css[key]; }
650
652 AttributeMap& media_style(const std::string& query, const std::string& key) {
653 return this->css->media_queries[query][key];
654 }
655
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];
663 }
664
665 Style* css = this->add_child<Style>();
667 protected:
668 std::string tag() override { return "svg"; }
669 };
670
671 class Path : public Shape {
672 public:
673 using Shape::Shape;
674
675 template<typename T>
676 inline void start(T x, T y) {
680 this->attr["d"] = "M " + std::to_string(x) + " " + std::to_string(y);
681 this->x_start = x;
682 this->y_start = y;
683 }
684
685 template<typename T>
686 inline void line_to(T x, T y) {
692 if (this->attr.find("d") == this->attr.end())
693 start(x, y);
694 else
695 this->attr["d"] += " L " + std::to_string(x) +
696 " " + std::to_string(y);
697 }
698
699 inline void line_to(std::pair<double, double> coord) {
700 this->line_to(coord.first, coord.second);
701 }
702
703 inline void to_origin() {
705 this->line_to(x_start, y_start);
706 }
707
708 protected:
709 std::string tag() override { return "path"; }
710
711 private:
712 double x_start;
713 double y_start;
714 };
715
716 class Text : public Element {
717 public:
718 Text() = default;
719 using Element::Element;
720
721 Text(double x, double y, std::string _content) {
722 set_attr("x", to_string(x));
723 set_attr("y", to_string(y));
724 content = _content;
725 }
726
727 Text(std::pair<double, double> xy, std::string _content) :
728 Text(xy.first, xy.second, _content) {};
729
730 protected:
731 std::string content;
732 std::string svg_to_string(const size_t) override;
733 std::string tag() override { return "text"; }
734 };
735
736 class Group : public Element {
737 public:
738 using Element::Element;
739 protected:
740 std::string tag() override { return "g"; }
741 };
742
743 class Line : public Shape {
744 public:
745 Line() = default;
746 using Shape::Shape;
747
748 Line(double x1, double x2, double y1, double y2) : Shape({
749 { "x1", to_string(x1) },
750 { "x2", to_string(x2) },
751 { "y1", to_string(y1) },
752 { "y2", to_string(y2) }
753 }) {};
754
755 Line(Point x, Point y) : Line(x.first, y.first, x.second, y.second) {};
756
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"); }
763
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; }
769
770 std::pair<double, double> along(double percent);
771
772 protected:
773 Element::BoundingBox get_bbox() override;
774 std::string tag() override { return "line"; }
775 };
776
777 class Rect : public Shape {
778 public:
779 Rect() = default;
780 using Shape::Shape;
781
782 Rect(
783 double x, double y, double width, double height) :
784 Shape({
785 { "x", to_string(x) },
786 { "y", to_string(y) },
787 { "width", to_string(width) },
788 { "height", to_string(height) }
789 }) {};
790
791 Element::BoundingBox get_bbox() override;
792 protected:
793 std::string tag() override { return "rect"; }
794 };
795
796 class Circle : public Shape {
797 public:
798 Circle() = default;
799 using Shape::Shape;
800
801 Circle(double cx, double cy, double radius) :
802 Shape({
803 { "cx", to_string(cx) },
804 { "cy", to_string(cy) },
805 { "r", to_string(radius) }
806 }) {
807 };
808
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(); }
815 Element::BoundingBox get_bbox() override;
816
817 protected:
818 std::string tag() override { return "circle"; }
819 };
820
821 class Polygon : public Element {
822 public:
823 Polygon() = default;
824 using Element::Element;
825
826 Polygon(const std::vector<Point>& points) {
827 // Quick and dirty
828 std::string& point_str = this->attr["points"];
829 for (auto& pt : points)
830 point_str += to_string(pt) + " ";
831 };
832
833 protected:
834 std::string tag() override { return "polygon"; }
835 };
836
837 inline Element::BoundingBox Line::get_bbox() {
838 return { x1(), x2(), y1(), y2() };
839 }
840
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 };
845 }
846
847 inline Element::BoundingBox Circle::get_bbox() {
848 double x = this->x(), y = this->y(), radius = this->radius();
849
850 return {
851 x - radius,
852 x + radius,
853 y - radius,
854 y + radius
855 };
856 }
857
858 inline std::pair<double, double> Line::along(double percent) {
863 double x_pos, y_pos;
864
865 if (x1() != x2()) {
866 double length = percent * this->length();
867 double discrim = std::sqrt(4 * pow(length, 2) * (1 / (1 + pow(slope(), 2))));
868
869 double x_a = (2 * x1() + discrim) / 2;
870 double x_b = (2 * x1() - discrim) / 2;
871 x_pos = x_a;
872
873 if ((x_a > x1() && x_a > x2()) || (x_a < x1() && x_a < x2()))
874 x_pos = x_b;
875
876 y_pos = slope() * (x_pos - x1()) + y1();
877 }
878 else { // Edge case:: Completely vertical lines
879 x_pos = x1();
880
881 if (y1() > y2()) // Downward pointing
882 y_pos = y1() - percent * this->length();
883 else
884 y_pos = y1() + percent * this->length();
885 }
886
887 return std::make_pair(x_pos, y_pos);
888 }
889
890 inline std::string Element::svg_to_string(const size_t indent_level) {
896 auto indent = std::string(indent_level, '\t');
897 std::string ret = indent + "<" + tag();
898
899 // Set attributes
900 for (auto& pair: attr)
901 ret += " " + pair.first + "=" + "\"" + pair.second + "\"";
902
903 if (!this->children.empty()) {
904 ret += ">\n";
905
906 // Recursively get strings for child elements
907 for (auto& child : children) {
908 // Avoid adding empty strings
909 auto str = child->svg_to_string(indent_level + 1);
910 if (str.size()) ret += str +"\n";
911 }
912
913 return ret += indent + "</" + tag() + ">";
914 }
915
916 return ret += " />";
917 }
918
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) {
923 // Loop over each selector's attribute/value pairs
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";
928 }
929 return ret;
930 }
931
932 inline std::string SVG::Style::svg_to_string(const size_t indent_level) {
934 auto indent = std::string(indent_level, '\t');
935
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";
939
940 // Begin CSS stylesheet
941 ret += to_string(this->css, indent_level);
942
943 // Media queries
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";
948 }
949
950 // Animation frames
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";
955 }
956
957 ret += indent + "\t]]>\n";
958 return ret + indent + "</style>";
959 }
960
961 return "";
962 }
963
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>";
970 }
971
972 inline void Element::autoscale(const double margin) {
974 Element::BoundingBox bbox = this->get_bbox();
975 this->get_bbox(bbox);
976 double width = abs(bbox.x1) + abs(bbox.x2),
977 height = abs(bbox.y1) + abs(bbox.y2);
978
979 this->autoscale({
980 width * margin, width * margin,
981 height * margin, height * margin
982 });
983 }
984
985 inline void Element::autoscale(const Margins& margins) {
991 using std::stof;
992
993 Element::BoundingBox bbox = this->get_bbox();
994 this->get_bbox(bbox); // Compute the bounding box (recursive)
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;
998
999 this->set_attr("width", width)
1000 .set_attr("height", height);
1001
1002 if (x1 < 0 || y1 < 0) {
1003 std::stringstream viewbox;
1004 viewbox << std::fixed << std::setprecision(1)
1005 << x1 << " " // min-x
1006 << y1 << " " // min-y
1007 << width << " "
1008 << height;
1009 this->set_attr("viewBox", viewbox.str());
1010 }
1011 }
1012
1015 auto this_bbox = this->get_bbox();
1016 box = this_bbox + box; // Take union of both
1017 for (auto& child: this->children) child->get_bbox(box); // Recursion
1018 }
1019
1020 inline Element::ChildMap Element::get_children() {
1022 Element::ChildMap child_map;
1023 for (auto& child : this->get_children_helper())
1024 child_map[child->tag()].push_back(child);
1025 return child_map;
1026 }
1027
1028 inline std::vector<Element*> Element::get_children_helper() {
1030 std::deque<Element*> temp;
1031 std::vector<Element*> ret;
1032
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()); }
1037 temp.pop_front();
1038 }
1039
1040 return ret;
1041 };
1042
1043 inline SVG merge(SVG& left, SVG& right, const Margins& margins) {
1045 SVG ret;
1046
1047 // Move items
1048 ret << std::move(left) << std::move(right);
1049
1050 // Set bounding box of individual pieces
1051 for (auto& svg_child: ret.get_immediate_children<SVG>())
1052 svg_child->autoscale(margins);
1053
1054 // Set x position for child SVG elements, and compute width/height for this
1055 double x = 0, height = 0;
1056 for (auto& svg_child: ret.get_immediate_children<SVG>()) {
1057 svg_child->set_attr("x", x).set_attr("y", 0);
1058 x += svg_child->width();
1059 height = std::max(height, svg_child->height());
1060 }
1061
1062 ret.set_attr("width", x).set_attr("height", height);
1063 return ret;
1064 }
1065
1066 inline std::vector<Point> bounding_polygon(std::vector<Shape*>& shapes) {
1067 /* Convert shapes into sets of points, aggregate them, and then calculate
1068 * convex hull for aggregate set
1069 */
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));
1074 }
1075
1076 return util::convex_hull(points);
1077 }
1078
1079 inline SVG merge(std::vector<SVG>& frames, const double width, const int max_frame_width) {
1083 SVG root;
1084 double x = 0, y = 0, total_width = 0, total_height = 0;
1085 for (auto& frame : frames) {
1086 // Scale
1087 frame.autoscale();
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); // Scale height proportionally
1092 }
1093 }
1094
1095 // Move
1096 double current_height = 0;
1097 for (auto& frame : frames) {
1098 // Push to next row
1099 if ((x + frame.width()) > width) {
1100 total_width = std::max(total_width, x);
1101 x = 0;
1102 y += current_height;
1103 current_height = 0;
1104 }
1105
1106 frame.set_attr("x", x).set_attr("y", y);
1107 x += frame.width();
1108 current_height = std::max(current_height, frame.height());
1109 root << std::move(frame);
1110 }
1111
1112 total_height = y + current_height;
1113
1114 // Set viewbox
1115 root.set_attr("viewBox") << 0 << " " << 0 << " " << total_width << " " << total_height;
1116 root.set_attr("width", total_width).set_attr("height", total_height);
1117 return root;
1118 }
1119
1120 inline SVG frame_animate(std::vector<SVG>& frames, const double fps) {
1126 SVG root;
1127 const double duration = (double)frames.size() / fps; // [seconds]
1128 const double frame_step = 1.0 / fps; // duration of each frame [seconds]
1129 int current_frame = 0;
1130
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);
1135
1136 // Move frames into new SVG
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));
1142 current_frame++;
1143 root << std::move(frame);
1144 }
1145
1146 // Set animation frames
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);
1154 }
1155
1156 // Scale and center child SVGs
1157 double width = 0, height = 0;
1158
1159 for (auto& child : root.get_immediate_children<SVG>()) {
1160 child->autoscale();
1161 width = std::max(width, child->width());
1162 height = std::max(height, child->height());
1163 }
1164
1165 root.set_attr("viewBox", "0 0 " + std::to_string(width) + " " + std::to_string(height));
1166
1167 // Center child SVGs
1168 for (auto& child : root.get_immediate_children<SVG>())
1169 child->set_attr("x", (width - child->width())/2).set_attr("y", (height - child->height())/2);
1170
1171 return root;
1172 }
1173}
Base class for anything that has attributes (e.g. SVG elements, CSS stylesheets)
Definition svg.hpp:350
AttributeMap & set_attrs(std::initializer_list< std::pair< std::string, std::string > > values)
Definition svg.hpp:383
Definition svg.hpp:796
std::string tag() override
Definition svg.hpp:818
Ordered token list for managing the class attribute.
Definition svg.hpp:103
bool toggle(const std::string &token)
Definition svg.hpp:140
ClassList & remove(const std::string &token)
Definition svg.hpp:128
std::string str() const
Definition svg.hpp:163
std::vector< std::string > tokens() const
Definition svg.hpp:168
ClassList & set(const std::string &class_names)
Definition svg.hpp:151
bool contains(const std::string &token) const
Definition svg.hpp:110
ClassList & clear()
Definition svg.hpp:157
ClassList & add(const std::string &token)
Definition svg.hpp:117
Represents the top left and bottom right corners of a bounding rectangle.
Definition svg.hpp:451
Abstract base class for all SVG elements.
Definition svg.hpp:446
std::vector< T * > get_immediate_children()
Definition svg.hpp:515
std::vector< Element * > get_children_helper()
Definition svg.hpp:1028
Element & operator<<(T &&node)
Definition svg.hpp:494
Element * get_element_by_id(const std::string &id)
Definition svg.hpp:560
virtual BoundingBox get_bbox()
Definition svg.hpp:584
T * add_child(Args &&... args)
Definition svg.hpp:486
virtual std::string tag()=0
std::vector< T * > get_children()
Definition svg.hpp:502
void autoscale(const Margins &margins=DEFAULT_MARGINS)
Definition svg.hpp:985
std::vector< Element * > get_elements_by_class(const std::string &clsname)
Definition svg.hpp:570
virtual std::string svg_to_string(const size_t indent_level)
Definition svg.hpp:890
double find_numeric(const std::string &key)
Definition svg.hpp:541
Definition svg.hpp:736
std::string tag() override
Definition svg.hpp:740
Definition svg.hpp:743
std::string tag() override
Definition svg.hpp:774
std::pair< double, double > along(double percent)
Definition svg.hpp:858
Definition svg.hpp:671
void line_to(T x, T y)
Definition svg.hpp:686
void start(T x, T y)
Definition svg.hpp:676
void to_origin()
Definition svg.hpp:703
std::string tag() override
Definition svg.hpp:709
Definition svg.hpp:821
std::string tag() override
Definition svg.hpp:834
Definition svg.hpp:777
std::string tag() override
Definition svg.hpp:793
Definition svg.hpp:630
SelectorProperties css
Definition svg.hpp:634
std::string svg_to_string(const size_t) override
Definition svg.hpp:932
std::map< std::string, SelectorProperties > media_queries
Definition svg.hpp:635
std::string tag() override
Definition svg.hpp:640
std::map< std::string, SelectorProperties > keyframes
Definition svg.hpp:636
AttributeMap & media_style(const std::string &query, const std::string &key)
Definition svg.hpp:652
AttributeMap & style(const std::string &key)
Definition svg.hpp:649
std::string tag() override
Definition svg.hpp:668
std::map< std::string, AttributeMap > & keyframes(const std::string &key)
Definition svg.hpp:656
Base class for any SVG elements that have a width and height.
Definition svg.hpp:592
virtual std::vector< Point > points()
Definition svg.hpp:601
virtual double width()
Definition svg.hpp:614
virtual double height()
Definition svg.hpp:620
Definition svg.hpp:716
std::string tag() override
Definition svg.hpp:733
Main namespace for SVG for C++.
Definition svg.hpp:55
SVG frame_animate(std::vector< SVG > &frames, const double fps)
Definition svg.hpp:1120
SVG merge(SVG &left, SVG &right, const Margins &margins=DEFAULT_MARGINS)
Definition svg.hpp:1043
std::map< std::string, AttributeMap > SelectorProperties
Definition svg.hpp:71
std::string to_string(const double &value)
Definition svg.hpp:334
Various utility and mathematical functions.
Definition svg.hpp:352
Definition svg.hpp:63