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.5.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 <cmath>
42#include <cstdint>
43#include <fstream> // ofstream
44#include <functional>
45#include <math.h> // NAN
46#include <map>
47#include <deque>
48#include <set>
49#include <vector>
50#include <string>
51#include <sstream> // stringstream
52#include <iomanip> // setprecision
53#include <iterator>
54#include <memory>
55#include <stdexcept>
56#include <type_traits> // is_base_of
57#include <tuple>
58#include <utility>
59
60namespace SVG {
64 class AttributeMap;
65 class Element;
66 class LinearGradient;
67 class RadialGradient;
68 class SVG;
69 class Shape;
70 class Symbol;
71 class Use;
72
74 enum class ElementKind {
75 Custom,
76 Defs,
79 Stop,
80 Symbol,
81 Use,
82 SVG,
83 Style,
84 Path,
85 Text,
86 Title,
87 Group,
88 Line,
89 Rect,
90 Circle,
92 };
93
95 inline std::string tag_name(ElementKind kind) {
96 switch (kind) {
97 case ElementKind::Defs: return "defs";
98 case ElementKind::LinearGradient: return "linearGradient";
99 case ElementKind::RadialGradient: return "radialGradient";
100 case ElementKind::Stop: return "stop";
101 case ElementKind::Symbol: return "symbol";
102 case ElementKind::Use: return "use";
103 case ElementKind::SVG: return "svg";
104 case ElementKind::Style: return "style";
105 case ElementKind::Path: return "path";
106 case ElementKind::Text: return "text";
107 case ElementKind::Title: return "title";
108 case ElementKind::Group: return "g";
109 case ElementKind::Line: return "line";
110 case ElementKind::Rect: return "rect";
111 case ElementKind::Circle: return "circle";
112 case ElementKind::Polygon: return "polygon";
113 case ElementKind::Custom: return "";
114 }
115 return "";
116 }
117
118 struct QuadCoord {
119 double x1;
120 double x2;
121 double y1;
122 double y2;
123 };
124
126 using SelectorProperties = std::map<std::string, AttributeMap>;
127 using SVGAttrib = std::map<std::string, std::string>;
129 struct Attrs : SVGAttrib {
130 using SVGAttrib::SVGAttrib;
131 };
132 using Point = std::pair<double, double>;
133 using Margins = QuadCoord;
134 const static Margins DEFAULT_MARGINS = { 10, 10, 10, 10 };
135 const static Margins NO_MARGINS = { 0, 0, 0, 0 };
138 AutoscaleOptions(const Margins& _margins = DEFAULT_MARGINS,
139 bool _autoscale_nested_svgs = true) :
140 margins(_margins),
141 autoscale_nested_svgs(_autoscale_nested_svgs) {}
142
143 Margins margins;
146 };
147
149 enum class RelativeAlignment : unsigned {
150 Left = 1u << 0,
151 Top = 1u << 1,
152 Right = 1u << 2,
153 Bottom = 1u << 3
154 };
155
157 enum class Anchor : unsigned {
158 Start = 1u << 4,
159 Center = 1u << 5,
160 End = 1u << 6
161 };
162
164 enum class Axis {
165 X,
166 Y
167 };
168
170 using Alignment = unsigned;
171
172 inline Alignment operator|(RelativeAlignment relative, Anchor anchor) {
173 return static_cast<Alignment>(relative) | static_cast<Alignment>(anchor);
174 }
175
176 inline Alignment operator|(Anchor anchor, RelativeAlignment relative) {
177 return relative | anchor;
178 }
179
181 class Color {
182 public:
184 static Color rgb(int red, int green, int blue) {
185 return Color(channel(red), channel(green), channel(blue));
186 }
187
189 static Color hex(std::string value) {
190 if (!value.empty() && value[0] == '#') {
191 value.erase(value.begin());
192 }
193 if (value.size() != 3 && value.size() != 6) {
194 throw std::invalid_argument("Hex colors must use 3 or 6 digits");
195 }
196
197 for (auto& ch : value) {
198 if (!std::isxdigit(static_cast<unsigned char>(ch))) {
199 throw std::invalid_argument("Hex colors may only contain hexadecimal digits");
200 }
201 ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
202 }
203
204 if (value.size() == 3) {
205 value = std::string{ value[0], value[0],
206 value[1], value[1],
207 value[2], value[2] };
208 }
209
210 return Color(from_hex_pair(value, 0),
211 from_hex_pair(value, 2),
212 from_hex_pair(value, 4));
213 }
214
216 static Color hsl(double hue, double saturation, double lightness) {
217 validate_percentage(saturation, "Saturation");
218 validate_percentage(lightness, "Lightness");
219
220 hue = std::fmod(hue, 360.0);
221 if (hue < 0) {
222 hue += 360.0;
223 }
224
225 const auto s = saturation / 100.0;
226 const auto l = lightness / 100.0;
227 const auto c = (1.0 - std::fabs(2.0 * l - 1.0)) * s;
228 const auto x = c * (1.0 - std::fabs(std::fmod(hue / 60.0, 2.0) - 1.0));
229 const auto m = l - c / 2.0;
230 double red = 0;
231 double green = 0;
232 double blue = 0;
233
234 if (hue < 60) {
235 red = c;
236 green = x;
237 } else if (hue < 120) {
238 red = x;
239 green = c;
240 } else if (hue < 180) {
241 green = c;
242 blue = x;
243 } else if (hue < 240) {
244 green = x;
245 blue = c;
246 } else if (hue < 300) {
247 red = x;
248 blue = c;
249 } else {
250 red = c;
251 blue = x;
252 }
253
254 return Color(channel_from_unit(red + m),
255 channel_from_unit(green + m),
256 channel_from_unit(blue + m));
257 }
258
260 Color mix(const Color& other, double amount) const {
261 validate_unit(amount, "Mix amount");
262 return Color(mix_channel(this->red_, other.red_, amount),
263 mix_channel(this->green_, other.green_, amount),
264 mix_channel(this->blue_, other.blue_, amount));
265 }
266
268 Color tint(double amount) const {
269 return mix(white(), amount);
270 }
271
273 Color shade(double amount) const {
274 return mix(black(), amount);
275 }
276
278 static Color white() {
279 return rgb(255, 255, 255);
280 }
281
283 static Color black() {
284 return rgb(0, 0, 0);
285 }
286
288 operator std::string() const {
289 return serialize();
290 }
291
292 private:
293 Color(uint8_t red, uint8_t green, uint8_t blue) : red_(red), green_(green), blue_(blue) {}
294
295 std::string serialize() const {
296 std::ostringstream ss;
297 ss << '#'
298 << std::hex << std::setfill('0') << std::nouppercase
299 << std::setw(2) << static_cast<int>(red_)
300 << std::setw(2) << static_cast<int>(green_)
301 << std::setw(2) << static_cast<int>(blue_);
302 return ss.str();
303 }
304
305 static uint8_t channel(int value) {
306 if (value < 0 || value > 255) {
307 throw std::invalid_argument("RGB channels must be between 0 and 255");
308 }
309 return static_cast<uint8_t>(value);
310 }
311
312 static uint8_t channel_from_unit(double value) {
313 value = std::max(0.0, std::min(1.0, value));
314 return channel(static_cast<int>(std::round(value * 255.0)));
315 }
316
317 static uint8_t from_hex_pair(const std::string& value, size_t offset) {
318 return static_cast<uint8_t>(std::stoi(value.substr(offset, 2), nullptr, 16));
319 }
320
321 static uint8_t mix_channel(uint8_t from, uint8_t to, double amount) {
322 return channel(static_cast<int>(std::round(from + (to - from) * amount)));
323 }
324
325 static void validate_percentage(double value, const std::string& label) {
326 if (value < 0 || value > 100) {
327 throw std::invalid_argument(label + " must be between 0 and 100");
328 }
329 }
330
331 static void validate_unit(double value, const std::string& label) {
332 if (value < 0 || value > 1) {
333 throw std::invalid_argument(label + " must be between 0 and 1");
334 }
335 }
336
337 uint8_t red_;
338 uint8_t green_;
339 uint8_t blue_;
340 };
341
342#if __cplusplus < 201402L
343 namespace detail {
344 template<typename T, typename... Args>
345 std::unique_ptr<T> make_unique(Args&&... args) {
346 return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
347 }
348 }
349#else
350 namespace detail {
351 using std::make_unique;
352 }
353#endif
354
356 namespace detail {
357 template<size_t... I>
358 struct index_sequence {};
359
360 template<size_t N, size_t... I>
361 struct make_index_sequence : make_index_sequence<N - 1, N - 1, I...> {};
362
363 template<size_t... I>
364 struct make_index_sequence<0, I...> {
365 using type = index_sequence<I...>;
366 };
367
368 template<typename T>
369 struct is_attrs : std::is_same<typename std::decay<T>::type, Attrs> {};
370
371 template<typename... Args>
372 struct last_is_attrs : std::false_type {};
373
374 template<typename T>
375 struct last_is_attrs<T> : is_attrs<T> {};
376
377 template<typename T, typename... Rest>
378 struct last_is_attrs<T, Rest...> : last_is_attrs<Rest...> {};
379
380 template<typename T, typename Tuple, size_t... I>
381 std::unique_ptr<T> make_child_with_attrs(Tuple&& tuple, index_sequence<I...>) {
382 const auto attrs = std::get<sizeof...(I)>(tuple);
383 auto child = detail::make_unique<T>(std::get<I>(std::move(tuple))...);
384 for (const auto& attr : attrs) {
385 child->set_attr(attr.first, attr.second);
386 }
387 return child;
388 }
389
390 template<typename T, typename... Args>
391 std::unique_ptr<T> make_child_impl(std::false_type, Args&&... args) {
392 return detail::make_unique<T>(std::forward<Args>(args)...);
393 }
394
395 template<typename T, typename... Args>
396 std::unique_ptr<T> make_child_impl(std::true_type, Args&&... args) {
397 auto tuple = std::forward_as_tuple(std::forward<Args>(args)...);
398 return make_child_with_attrs<T>(
399 std::move(tuple),
400 typename make_index_sequence<sizeof...(Args) - 1>::type{});
401 }
402
403 template<typename T, typename... Args>
404 std::unique_ptr<T> make_child(Args&&... args) {
405 return make_child_impl<T>(last_is_attrs<Args...>{}, std::forward<Args>(args)...);
406 }
407
408 struct AffineTransform {
409 AffineTransform() = default;
410 AffineTransform(double a_, double b_, double c_, double d_, double e_, double f_) :
411 a(a_), b(b_), c(c_), d(d_), e(e_), f(f_) {}
412
413 double a = 1;
414 double b = 0;
415 double c = 0;
416 double d = 1;
417 double e = 0;
418 double f = 0;
419
420 Point apply(Point point) const {
421 return {
422 a * point.first + c * point.second + e,
423 b * point.first + d * point.second + f
424 };
425 }
426 };
427
428 inline AffineTransform multiply(const AffineTransform& left, const AffineTransform& right) {
429 return {
430 left.a * right.a + left.c * right.b,
431 left.b * right.a + left.d * right.b,
432 left.a * right.c + left.c * right.d,
433 left.b * right.c + left.d * right.d,
434 left.a * right.e + left.c * right.f + left.e,
435 left.b * right.e + left.d * right.f + left.f
436 };
437 }
438
439 inline AffineTransform rotate_transform(double degrees, double cx = 0, double cy = 0) {
440 const auto radians = degrees * PI / 180.0;
441 const auto cos_value = std::cos(radians);
442 const auto sin_value = std::sin(radians);
443 return {
444 cos_value,
445 sin_value,
446 -sin_value,
447 cos_value,
448 cx - cos_value * cx + sin_value * cy,
449 cy - sin_value * cx - cos_value * cy
450 };
451 }
452
453 inline AffineTransform translate_transform(double x, double y = 0) {
454 return { 1, 0, 0, 1, x, y };
455 }
456
457 inline double parse_number_or(const std::string& value, double fallback) {
458 if (value.empty()) return fallback;
459 try {
460 return std::stof(value);
461 } catch (const std::invalid_argument&) {
462 return fallback;
463 } catch (const std::out_of_range&) {
464 return fallback;
465 }
466 }
467
468 template<typename T>
469 struct is_numeric_attr_type {
470 static const bool value =
471 std::is_arithmetic<T>::value &&
472 !std::is_same<typename std::remove_cv<T>::type, bool>::value &&
473 !std::is_same<typename std::remove_cv<T>::type, char>::value &&
474 !std::is_same<typename std::remove_cv<T>::type, signed char>::value &&
475 !std::is_same<typename std::remove_cv<T>::type, unsigned char>::value;
476 };
477
478 inline std::vector<double> parse_transform_args(std::string args) {
479 for (auto& ch : args) {
480 if (ch == ',') {
481 ch = ' ';
482 }
483 }
484
485 std::istringstream stream(args);
486 std::vector<double> values;
487 double value;
488 while (stream >> value) {
489 values.push_back(value);
490 }
491 return values;
492 }
493
494 class TextEstimator {
495 public:
496 struct Metrics {
497 double width = 0;
498 double height = 0;
499 double ascent = 0;
500 double descent = 0;
501 };
502
503 TextEstimator(std::string text_, double font_size_, std::string font_weight_) :
504 text(std::move(text_)),
505 font_size(font_size_ > 0 ? font_size_ : 16),
506 font_weight(std::move(font_weight_)) {}
507
509 Metrics estimate() const {
510 const auto bold = is_bold();
511 const double weight_multiplier = bold ? 1.06 : 1.0;
512 const double side_padding = bold ? font_size * 0.05 : font_size * 0.02;
513 const double vertical_padding = bold ? font_size * 0.10 : font_size * 0.06;
514 const double line_height = font_size * (bold ? 1.38 : 1.32);
515 const double ascent = font_size * (bold ? 1.00 : 0.96) + vertical_padding;
516 const double descent = font_size * (bold ? 0.38 : 0.34) + vertical_padding;
517
518 double current = 0;
519 double longest = 0;
520 std::size_t lines = text.empty() ? 0 : 1;
521 bool previous_was_joiner = false;
522 bool regional_pair_open = false;
523 std::size_t index = 0;
524
525 while (index < text.size()) {
526 const auto codepoint = next_codepoint(index);
527 if (codepoint == '\r') {
528 if (index < text.size() && text[index] == '\n') ++index;
529 longest = std::max(longest, current);
530 current = 0;
531 ++lines;
532 previous_was_joiner = false;
533 regional_pair_open = false;
534 continue;
535 }
536 if (codepoint == '\n') {
537 longest = std::max(longest, current);
538 current = 0;
539 ++lines;
540 previous_was_joiner = false;
541 regional_pair_open = false;
542 continue;
543 }
544
545 current += glyph_advance(codepoint, previous_was_joiner, regional_pair_open);
546 previous_was_joiner = codepoint == 0x200d;
547 }
548
549 longest = std::max(longest, current);
550 Metrics metrics;
551 metrics.width = longest == 0 ? 0 : longest * font_size * weight_multiplier + side_padding * 2;
552 metrics.ascent = ascent;
553 metrics.descent = descent;
554 metrics.height = lines == 0 ? 0 : ascent + descent + static_cast<double>(lines - 1) * line_height;
555 return metrics;
556 }
557
558 private:
559 std::string text;
560 double font_size;
561 std::string font_weight;
562
563 bool is_bold() const {
564 auto value = font_weight;
565 for (auto& ch : value) ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
566 if (value == "bold" || value == "bolder") return true;
567 return parse_number_or(value, 400) >= 600;
568 }
569
570 uint32_t next_codepoint(std::size_t& index) const {
571 const auto first = static_cast<unsigned char>(text[index++]);
572 if (first < 0x80) return first;
573
574 uint32_t codepoint = 0xfffd;
575 std::size_t continuation_count = 0;
576 if ((first & 0xe0) == 0xc0) {
577 codepoint = first & 0x1f;
578 continuation_count = 1;
579 } else if ((first & 0xf0) == 0xe0) {
580 codepoint = first & 0x0f;
581 continuation_count = 2;
582 } else if ((first & 0xf8) == 0xf0) {
583 codepoint = first & 0x07;
584 continuation_count = 3;
585 } else {
586 return 0xfffd;
587 }
588
589 for (std::size_t i = 0; i < continuation_count; ++i) {
590 if (index >= text.size()) return 0xfffd;
591 const auto next = static_cast<unsigned char>(text[index]);
592 if ((next & 0xc0) != 0x80) return 0xfffd;
593 ++index;
594 codepoint = (codepoint << 6) | (next & 0x3f);
595 }
596
597 if ((continuation_count == 1 && codepoint < 0x80) ||
598 (continuation_count == 2 && codepoint < 0x800) ||
599 (continuation_count == 3 && codepoint < 0x10000) ||
600 codepoint > 0x10ffff ||
601 (codepoint >= 0xd800 && codepoint <= 0xdfff)) {
602 return 0xfffd;
603 }
604 return codepoint;
605 }
606
607 static bool in_range(uint32_t value, uint32_t first, uint32_t last) {
608 return value >= first && value <= last;
609 }
610
611 static bool is_combining_mark(uint32_t codepoint) {
612 return in_range(codepoint, 0x0300, 0x036f) ||
613 in_range(codepoint, 0x0483, 0x0489) ||
614 in_range(codepoint, 0x0591, 0x05bd) ||
615 codepoint == 0x05bf ||
616 in_range(codepoint, 0x05c1, 0x05c2) ||
617 in_range(codepoint, 0x05c4, 0x05c5) ||
618 codepoint == 0x05c7 ||
619 in_range(codepoint, 0x0610, 0x061a) ||
620 in_range(codepoint, 0x064b, 0x065f) ||
621 codepoint == 0x0670 ||
622 in_range(codepoint, 0x06d6, 0x06dc) ||
623 in_range(codepoint, 0x06df, 0x06e4) ||
624 in_range(codepoint, 0x06e7, 0x06e8) ||
625 in_range(codepoint, 0x06ea, 0x06ed) ||
626 in_range(codepoint, 0x1ab0, 0x1aff) ||
627 in_range(codepoint, 0x1dc0, 0x1dff) ||
628 in_range(codepoint, 0x20d0, 0x20ff) ||
629 in_range(codepoint, 0xfe20, 0xfe2f);
630 }
631
632 static bool is_variation_selector(uint32_t codepoint) {
633 return in_range(codepoint, 0xfe00, 0xfe0f) ||
634 in_range(codepoint, 0xe0100, 0xe01ef);
635 }
636
637 static bool is_full_width(uint32_t codepoint) {
638 return in_range(codepoint, 0x1100, 0x115f) ||
639 in_range(codepoint, 0x2329, 0x232a) ||
640 in_range(codepoint, 0x2e80, 0xa4cf) ||
641 in_range(codepoint, 0xac00, 0xd7a3) ||
642 in_range(codepoint, 0xf900, 0xfaff) ||
643 in_range(codepoint, 0xfe10, 0xfe19) ||
644 in_range(codepoint, 0xfe30, 0xfe6f) ||
645 in_range(codepoint, 0xff00, 0xff60) ||
646 in_range(codepoint, 0xffe0, 0xffe6);
647 }
648
649 static bool is_emoji_or_symbol(uint32_t codepoint) {
650 return in_range(codepoint, 0x1f000, 0x1faff) ||
651 in_range(codepoint, 0x2600, 0x27bf) ||
652 in_range(codepoint, 0x2190, 0x21ff) ||
653 in_range(codepoint, 0x2300, 0x23ff);
654 }
655
656 static bool is_regional_indicator(uint32_t codepoint) {
657 return in_range(codepoint, 0x1f1e6, 0x1f1ff);
658 }
659
660 static bool is_latin_letter(uint32_t codepoint) {
661 return in_range(codepoint, 'A', 'Z') ||
662 in_range(codepoint, 'a', 'z') ||
663 in_range(codepoint, 0x00c0, 0x024f);
664 }
665
666 static double ascii_advance(uint32_t codepoint) {
667 switch (codepoint) {
668 case ' ':
669 case '\t':
670 case '.':
671 case ',':
672 case ':':
673 case ';':
674 case '\'':
675 case '"':
676 case '`':
677 case '!':
678 case '|':
679 return 0.34;
680 case 'i':
681 case 'j':
682 case 'l':
683 case 'I':
684 case '[':
685 case ']':
686 case '(':
687 case ')':
688 case '{':
689 case '}':
690 return 0.40;
691 case 'm':
692 case 'w':
693 case 'M':
694 case 'W':
695 case '@':
696 case '%':
697 case '&':
698 return 0.92;
699 case '-':
700 case '_':
701 case '/':
702 case '\\':
703 return 0.52;
704 default:
705 break;
706 }
707
708 if (in_range(codepoint, '0', '9')) return 0.64;
709 if (in_range(codepoint, 'A', 'Z')) return 0.72;
710 if (in_range(codepoint, 'a', 'z')) return 0.62;
711 return 0.58;
712 }
713
714 static double glyph_advance(uint32_t codepoint,
715 bool previous_was_joiner,
716 bool& regional_pair_open) {
717 if (is_combining_mark(codepoint) ||
718 is_variation_selector(codepoint) ||
719 codepoint == 0x200d ||
720 in_range(codepoint, 0x1f3fb, 0x1f3ff)) {
721 return 0;
722 }
723
724 if (previous_was_joiner) return 0;
725
726 if (is_regional_indicator(codepoint)) {
727 regional_pair_open = !regional_pair_open;
728 return regional_pair_open ? 1.10 : 0;
729 }
730 regional_pair_open = false;
731
732 if (codepoint < 0x80) return ascii_advance(codepoint);
733 if (is_full_width(codepoint)) return 1.05;
734 if (is_emoji_or_symbol(codepoint)) return 1.10;
735 if (is_latin_letter(codepoint)) return 0.66;
736 return 0.85;
737 }
738 };
739
740 inline AffineTransform parse_supported_transform(const std::string& transform) {
741 AffineTransform current;
742 size_t pos = 0;
743 while (pos < transform.size()) {
744 while (pos < transform.size() && std::isspace(static_cast<unsigned char>(transform[pos]))) {
745 ++pos;
746 }
747 const auto name_start = pos;
748 while (pos < transform.size() && std::isalpha(static_cast<unsigned char>(transform[pos]))) {
749 ++pos;
750 }
751 if (name_start == pos) {
752 break;
753 }
754
755 const auto name = transform.substr(name_start, pos - name_start);
756 while (pos < transform.size() && std::isspace(static_cast<unsigned char>(transform[pos]))) {
757 ++pos;
758 }
759 if (pos >= transform.size() || transform[pos] != '(') {
760 break;
761 }
762 const auto args_start = ++pos;
763 const auto args_end = transform.find(')', args_start);
764 if (args_end == std::string::npos) {
765 break;
766 }
767 pos = args_end + 1;
768
769 const auto args = parse_transform_args(transform.substr(args_start, args_end - args_start));
770 if (name == "rotate" && (args.size() == 1 || args.size() == 3)) {
771 current = multiply(
772 current,
773 args.size() == 1 ? rotate_transform(args[0])
774 : rotate_transform(args[0], args[1], args[2]));
775 } else if (name == "translate" && (args.size() == 1 || args.size() == 2)) {
776 current = multiply(
777 current,
778 args.size() == 1 ? translate_transform(args[0])
779 : translate_transform(args[0], args[1]));
780 }
781 }
782 return current;
783 }
784 }
787 inline std::string to_string(const double& value);
788 inline std::string to_string(const Point& point);
790 inline std::string to_string(const Color& color);
791 inline std::string to_string(const std::map<std::string, AttributeMap>& css, const size_t indent_level=0);
793 inline std::string escape_xml(const std::string& text);
794
795 std::vector<Point> bounding_polygon(const std::vector<Shape*>& shapes);
796 SVG frame_animate(std::vector<SVG>& frames, const double fps);
797 SVG merge(SVG& left, SVG& right, const Margins& margins = DEFAULT_MARGINS);
798 SVG merge(std::vector<SVG>& frames, const double width, const int max_frame_width);
799
803 class ClassList {
804 public:
805 ClassList() = default;
806 explicit ClassList(std::string& value) : mutable_value_(&value), value_(&value) {}
807 explicit ClassList(const std::string& value) : value_(&value) {}
808
810 bool contains(const std::string& token) const {
811 validate_token(token);
812 const auto values = tokens();
813 return std::find(values.begin(), values.end(), token) != values.end();
814 }
815
817 ClassList& add(const std::string& token) {
818 validate_token(token);
819 auto values = tokens();
820 if (std::find(values.begin(), values.end(), token) == values.end()) {
821 values.push_back(token);
822 write(values);
823 }
824 return *this;
825 }
826
828 ClassList& remove(const std::string& token) {
829 validate_token(token);
830 auto values = tokens();
831 const auto original_size = values.size();
832 values.erase(std::remove(values.begin(), values.end(), token), values.end());
833 if (values.size() != original_size) {
834 write(values);
835 }
836 return *this;
837 }
838
840 bool toggle(const std::string& token) {
841 if (contains(token)) {
842 remove(token);
843 return false;
844 }
845
846 add(token);
847 return true;
848 }
849
851 ClassList& set(const std::string& class_names) {
852 write(parse(class_names));
853 return *this;
854 }
855
858 write({});
859 return *this;
860 }
861
863 std::string str() const {
864 return join(tokens());
865 }
866
868 std::vector<std::string> tokens() const {
869 return parse(value());
870 }
871
872 private:
873 static bool is_space(char ch) {
874 return std::isspace(static_cast<unsigned char>(ch)) != 0;
875 }
876
877 static void validate_token(const std::string& token) {
878 if (token.empty()) {
879 throw std::invalid_argument("class token cannot be empty");
880 }
881 if (std::find_if(token.begin(), token.end(), is_space) != token.end()) {
882 throw std::invalid_argument("class token cannot contain whitespace");
883 }
884 }
885
886 static std::vector<std::string> parse(const std::string& class_names) {
887 std::vector<std::string> result;
888 std::string token;
889 for (const auto ch : class_names) {
890 if (is_space(ch)) {
891 if (!token.empty()) {
892 if (std::find(result.begin(), result.end(), token) == result.end()) {
893 result.push_back(token);
894 }
895 token.clear();
896 }
897 continue;
898 }
899 token.push_back(ch);
900 }
901 if (!token.empty() && std::find(result.begin(), result.end(), token) == result.end()) {
902 result.push_back(token);
903 }
904 return result;
905 }
906
907 static std::string join(const std::vector<std::string>& values) {
908 std::string result;
909 for (std::size_t i = 0; i < values.size(); ++i) {
910 if (i > 0) {
911 result += ' ';
912 }
913 result += values[i];
914 }
915 return result;
916 }
917
918 const std::string& value() const {
919 static const std::string empty;
920 return value_ ? *value_ : empty;
921 }
922
923 void write(const std::vector<std::string>& values) {
924 if (!mutable_value_) {
925 throw std::logic_error("cannot mutate a const class list");
926 }
927 *mutable_value_ = join(values);
928 value_ = mutable_value_;
929 }
930
931 std::string* mutable_value_ = nullptr;
932 const std::string* value_ = nullptr;
933 };
934
939 public:
940 TransformList() = default;
941 explicit TransformList(std::string& value, const Element* owner = nullptr) :
942 mutable_value_(&value), value_(&value), owner_(owner) {}
943 explicit TransformList(const std::string& value, const Element* owner = nullptr) :
944 value_(&value), owner_(owner) {}
945
947 TransformList& append(const std::string& transform) {
948 validate_appendable();
949 if (transform.empty()) {
950 throw std::invalid_argument("transform cannot be empty");
951 }
952
953 if (str().empty() || str() == "none") {
954 write(transform);
955 } else {
956 write(str() + " " + transform);
957 }
958 return *this;
959 }
960
962 TransformList& set(const std::string& transform) {
963 write(transform);
964 return *this;
965 }
966
969 write("");
970 return *this;
971 }
972
973 TransformList& matrix(double a, double b, double c, double d, double e, double f) {
974 std::stringstream ss;
975 ss << "matrix(" << to_string(a) << " " << to_string(b) << " " << to_string(c)
976 << " " << to_string(d) << " " << to_string(e) << " " << to_string(f) << ")";
977 return append(ss.str());
978 }
979
980 TransformList& translate(double x) {
981 return append("translate(" + to_string(x) + ")");
982 }
983
984 TransformList& translate(double x, double y) {
985 return append("translate(" + to_string(x) + " " + to_string(y) + ")");
986 }
987
988 TransformList& scale(double factor) {
989 return append("scale(" + to_string(factor) + ")");
990 }
991
992 TransformList& scale(double x, double y) {
993 return append("scale(" + to_string(x) + " " + to_string(y) + ")");
994 }
995
996 TransformList& rotate(double degrees) {
997 return append("rotate(" + to_string(degrees) + ")");
998 }
999
1000 TransformList& rotate(double degrees, double cx, double cy) {
1001 return append("rotate(" + to_string(degrees) + " " + to_string(cx) + " " +
1002 to_string(cy) + ")");
1003 }
1004
1006 TransformList& rotate_about_bbox(double degrees, Anchor x_anchor, Anchor y_anchor);
1007
1008 TransformList& skew_x(double degrees) {
1009 return append("skewX(" + to_string(degrees) + ")");
1010 }
1011
1012 TransformList& skew_y(double degrees) {
1013 return append("skewY(" + to_string(degrees) + ")");
1014 }
1015
1017 std::string str() const {
1018 return value();
1019 }
1020
1021 private:
1022 static bool is_keyword(const std::string& transform) {
1023 return transform == "inherit" || transform == "initial" || transform == "revert" ||
1024 transform == "revert-layer" || transform == "unset";
1025 }
1026
1027 const std::string& value() const {
1028 static const std::string empty;
1029 return value_ ? *value_ : empty;
1030 }
1031
1032 void validate_appendable() const {
1033 if (is_keyword(value())) {
1034 throw std::logic_error("cannot append transform functions to a transform keyword");
1035 }
1036 }
1037
1038 void write(const std::string& value) {
1039 if (!mutable_value_) {
1040 throw std::logic_error("cannot mutate a const transform list");
1041 }
1042 *mutable_value_ = value;
1043 value_ = mutable_value_;
1044 }
1045
1046 std::string* mutable_value_ = nullptr;
1047 const std::string* value_ = nullptr;
1048 const Element* owner_ = nullptr;
1049 };
1050
1054 namespace util {
1055 enum Orientation {
1056 COLINEAR, CLOCKWISE, COUNTERCLOCKWISE
1057 };
1058
1059 inline std::vector<Point> polar_points(int n, int a, int b, double radius);
1060
1061 template<typename T>
1062 inline T min_or_not_nan(T first, T second) {
1066 if (isnan(first) && isnan(second))
1067 return NAN;
1068 else if (isnan(first) || isnan(second))
1069 return isnan(first) ? second : first;
1070 else
1071 return std::min(first, second);
1072 }
1073
1074 template<typename T>
1075 inline T max_or_not_nan(T first, T second) {
1079 if (isnan(first) && isnan(second))
1080 return NAN;
1081 else if (isnan(first) || isnan(second))
1082 return isnan(first) ? second : first;
1083 else
1084 return std::max(first, second);
1085 }
1086
1087 inline Orientation orientation(Point& p1, Point& p2, Point& p3) {
1088 double value = ((p2.second - p1.second) * (p3.first - p2.first) -
1089 (p2.first - p1.first) * (p3.second - p2.second));
1090
1091 if (value == 0) return COLINEAR;
1092 else if (value > 0) return CLOCKWISE;
1093 else return COUNTERCLOCKWISE;
1094 }
1095
1096 inline std::vector<Point> convex_hull(std::vector<Point>& points) {
1103 if (points.size() < 3) return {}; // Need at least three points
1104 std::vector<Point> hull;
1105
1106 // Find leftmost point (ties don't matter)
1107 int left = 0;
1108 for (size_t i = 0; i < points.size(); i++)
1109 if (points[i].first < points[left].first) left = (int)i;
1110
1111 // While we don't reach leftmost point
1112 int current = left, next;
1113 do {
1114 // Add to convex hull
1115 hull.push_back(points[current]);
1116
1117 // Keep moving counterclockwise
1118 next = (current + 1) % points.size();
1119 for (size_t i = 0; i < points.size(); i++) {
1120 // We've found a more counterclockwise point --> update next
1121 if (orientation(points[current], points[next], points[i]) == COUNTERCLOCKWISE)
1122 next = (int)i;
1123 }
1124
1125 current = next;
1126 } while (current != left);
1127
1128 return hull;
1129 }
1130
1131 inline std::vector<Point> polar_points(int n, int a, int b, double radius) {
1138 std::vector<Point> ret;
1139 for (double degree = 0; degree < 360; degree += 360/n) {
1140 ret.push_back(Point(
1141 a + radius * cos(degree * (PI/180)), // 1 degree = pi/180 radians
1142 b + radius * sin(degree * (PI/180))
1143 ));
1144 }
1145
1146 return ret;
1147 }
1148 }
1149
1150 inline std::string to_string(const double& value) {
1152 std::stringstream ss;
1153 ss << std::fixed << std::setprecision(1);
1154 ss << value;
1155 return ss.str();
1156 }
1157
1158 inline std::string to_string(const Point& point) {
1160 return to_string(point.first) + "," + to_string(point.second);
1161 }
1162
1163 inline std::string to_string(const Color& color) {
1165 return static_cast<std::string>(color);
1166 }
1167
1168 inline std::string escape_xml(const std::string& text) {
1169 std::string out;
1170 out.reserve(text.size());
1171 for (const char ch : text) {
1172 switch (ch) {
1173 case '&':
1174 out += "&amp;";
1175 break;
1176 case '<':
1177 out += "&lt;";
1178 break;
1179 case '>':
1180 out += "&gt;";
1181 break;
1182 case '"':
1183 out += "&quot;";
1184 break;
1185 case '\'':
1186 out += "&apos;";
1187 break;
1188 default:
1189 out.push_back(ch);
1190 break;
1191 }
1192 }
1193 return out;
1194 }
1195
1200 public:
1201 struct AttrSetter {
1202 AttrSetter(SVGAttrib::mapped_type& _attr,
1203 bool _normalize_class = false,
1204 std::function<void(const std::string&)> _on_update = {}) :
1205 attr_(_attr), normalize_class_(_normalize_class), on_update_(_on_update) {};
1206
1208 AttrSetter& operator<<(const Color& value) {
1209 attr_ += static_cast<std::string>(value);
1210 normalize();
1211 notify();
1212 return *this;
1213 }
1214
1215 template<typename T>
1216 AttrSetter& operator<<(T value) {
1217 attr_ += std::to_string(value);
1218 normalize();
1219 notify();
1220 return *this;
1221 }
1222
1223 private:
1224 void normalize() {
1225 if (normalize_class_) {
1226 ClassList(attr_).set(attr_);
1227 }
1228 }
1229
1230 void notify() {
1231 if (on_update_) {
1232 on_update_(attr_);
1233 }
1234 }
1235
1236 SVGAttrib::mapped_type& attr_;
1237 bool normalize_class_ = false;
1238 std::function<void(const std::string&)> on_update_;
1239 };
1240
1241 AttributeMap() = default;
1242 virtual ~AttributeMap() = default;
1243 AttributeMap(SVGAttrib _attr) : attr_(std::move(_attr)) {};
1244
1245 const SVGAttrib& attrs() const {
1246 return this->attr_;
1247 }
1248
1249 bool has_attr(const std::string& key) const {
1250 return this->attr_.find(key) != this->attr_.end();
1251 }
1252
1253 std::string get_attr(const std::string& key, const std::string& fallback = "") const {
1254 const auto found = this->attr_.find(key);
1255 return found == this->attr_.end() ? fallback : found->second;
1256 }
1257
1259 template<typename T>
1260 typename std::enable_if<detail::is_numeric_attr_type<T>::value, T>::type
1261 get_attr(const std::string& key, T fallback) const {
1262 const auto found = this->attr_.find(key);
1263 if (found == this->attr_.end()) return fallback;
1264 return static_cast<T>(detail::parse_number_or(found->second, static_cast<double>(fallback)));
1265 }
1266
1267 template<typename T>
1268 AttributeMap& set_attr(const std::string key, T value) {
1269 this->set_attr_value(key, std::to_string(value));
1270 return *this;
1271 }
1272
1274 AttributeMap& set_attr(const std::string key, const Color& value);
1275
1277 AttributeMap& set_attrs(std::initializer_list<std::pair<std::string, std::string>> values) {
1278 for (const auto& pair : values) {
1279 this->set_attr_value(pair.first, pair.second);
1280 }
1281
1282 return *this;
1283 }
1284
1286 AttributeMap& set_attrs(const SVGAttrib& values) {
1287 for (const auto& pair : values) {
1288 this->set_attr_value(pair.first, pair.second);
1289 }
1290
1291 return *this;
1292 }
1293
1294 AttrSetter set_attr(const std::string key) {
1295 return this->make_attr_setter(key);
1296 };
1297
1298 ClassList class_list() {
1299 return ClassList(this->attr_["class"]);
1300 }
1301
1302 ClassList class_list() const {
1303 const auto found = this->attr_.find("class");
1304 return found == this->attr_.end() ? ClassList() : ClassList(found->second);
1305 }
1306
1307 TransformList transform_list() {
1308 return TransformList(this->attr_["transform"]);
1309 }
1310
1311 TransformList transform_list() const {
1312 const auto found = this->attr_.find("transform");
1313 return found == this->attr_.end() ? TransformList() : TransformList(found->second);
1314 }
1315
1316 TransformList transform() {
1317 return transform_list();
1318 }
1319
1320 TransformList transform() const {
1321 return transform_list();
1322 }
1323
1324 protected:
1325 virtual void set_attr_value(const std::string& key, const std::string& value) {
1326 if (key == "class") {
1327 ClassList(this->attr_[key]).set(value);
1328 return;
1329 }
1330 this->attr_[key] = value;
1331 }
1332
1333 virtual AttrSetter make_attr_setter(const std::string& key) {
1334 if (this->attr_.find(key) == this->attr_.end()) this->attr_[key] = "";
1335 return AttrSetter(this->attr_.at(key), key == "class");
1336 }
1337
1338 SVGAttrib& mutable_attrs() {
1339 return this->attr_;
1340 }
1341
1342 private:
1343 SVGAttrib attr_;
1344 };
1345
1346 template<>
1347 inline AttributeMap::AttrSetter& AttributeMap::AttrSetter::operator<<(const char * value) {
1348 attr_ += value;
1349 normalize();
1350 notify();
1351 return *this;
1352 }
1353
1354 template<>
1355 inline AttributeMap::AttrSetter& AttributeMap::AttrSetter::operator<<(const std::string value) {
1356 attr_ += value;
1357 normalize();
1358 notify();
1359 return *this;
1360 }
1361
1362 template<>
1363 inline AttributeMap& AttributeMap::set_attr(const std::string key, const double value) {
1365 this->set_attr_value(key, to_string(value));
1366 return *this;
1367 }
1368
1369 inline AttributeMap& AttributeMap::set_attr(const std::string key, const Color& value) {
1370 this->set_attr_value(key, static_cast<std::string>(value));
1371 return *this;
1372 }
1373
1374 template<>
1375 inline AttributeMap& AttributeMap::set_attr(const std::string key, const char * value) {
1377 this->set_attr_value(key, value);
1378 return *this;
1379 }
1380
1381 template<>
1382 inline AttributeMap& AttributeMap::set_attr(const std::string key, const std::string value) {
1384 this->set_attr_value(key, value);
1385 return *this;
1386 }
1387
1389 namespace detail {
1391 template<typename T>
1392 class TypedNames {
1393 static_assert(std::is_enum<T>::value, "Typed CSS keys must be an enum type.");
1394
1395 public:
1397 std::string name(T key) const {
1398 const auto found = this->names_.find(key);
1399 if (found == this->names_.end()) {
1400 throw std::invalid_argument("Unknown typed CSS key");
1401 }
1402 return found->second;
1403 }
1404
1405 protected:
1407 void add_name(T key, const std::string& normalized_name, const std::string& duplicate_label) {
1408 if (this->names_.find(key) != this->names_.end()) {
1409 throw std::invalid_argument("Duplicate typed CSS key");
1410 }
1411 if (this->used_names_.find(normalized_name) != this->used_names_.end()) {
1412 throw std::invalid_argument("Duplicate " + duplicate_label + ": " + normalized_name);
1413 }
1414
1415 this->names_[key] = normalized_name;
1416 this->used_names_.insert(normalized_name);
1417 }
1418
1419 private:
1421 std::map<T, std::string> names_;
1423 std::set<std::string> used_names_;
1424 };
1425 }
1429 template<typename T>
1434 std::string css_name;
1436 bool has_value = false;
1438 std::string value;
1439
1441 VariableSpec(T _key, std::string _css_name) :
1442 key(_key), css_name(std::move(_css_name)) {}
1443
1445 VariableSpec(T _key, std::string _css_name, std::string _value) :
1446 key(_key),
1447 css_name(std::move(_css_name)),
1448 has_value(true),
1449 value(std::move(_value)) {}
1450 };
1451
1453 template<typename T>
1454 struct ClassSpec {
1458 std::string css_class;
1459
1461 ClassSpec(T _key, std::string _css_class) :
1462 key(_key), css_class(std::move(_css_class)) {}
1463 };
1464
1466 template<typename T>
1467 class Variables : public detail::TypedNames<T> {
1468 public:
1470 Variables(AttributeMap& target, std::initializer_list<VariableSpec<T>> specs) :
1471 target_(&target) {
1472 std::vector<std::pair<T, std::string>> initial_values;
1473
1474 for (const auto& spec : specs) {
1475 const auto normalized_name = normalize_name(spec.css_name);
1476 this->add_name(spec.key, normalized_name, "CSS variable name");
1477 if (spec.has_value) {
1478 initial_values.push_back({ spec.key, spec.value });
1479 }
1480 }
1481
1482 for (const auto& initial : initial_values) {
1483 this->set(initial.first, initial.second);
1484 }
1485 }
1486
1488 std::string var(T key) const {
1489 return "var(" + this->name(key) + ")";
1490 }
1491
1493 Variables& set(T key, const std::string& value) {
1494 this->target_->set_attr(this->name(key), value);
1495 return *this;
1496 }
1497
1499 Variables& set(T key, const char* value) {
1500 return this->set(key, std::string(value));
1501 }
1502
1504 template<typename... Keys>
1505 std::string format(const std::string& pattern, Keys... keys) const {
1506 std::vector<std::string> args = { this->var(keys)... };
1507 std::vector<bool> used(args.size(), false);
1508 std::string out;
1509
1510 for (size_t i = 0; i < pattern.size(); ++i) {
1511 if (pattern[i] == '{') {
1512 if (i + 1 < pattern.size() && pattern[i + 1] == '{') {
1513 out.push_back('{');
1514 ++i;
1515 continue;
1516 }
1517
1518 const auto start = i + 1;
1519 auto pos = start;
1520 size_t index = 0;
1521 while (pos < pattern.size() && std::isdigit(static_cast<unsigned char>(pattern[pos]))) {
1522 index = index * 10 + static_cast<size_t>(pattern[pos] - '0');
1523 ++pos;
1524 }
1525 if (pos == start || pos >= pattern.size() || pattern[pos] != '}') {
1526 throw std::invalid_argument("Malformed CSS variable format placeholder");
1527 }
1528 if (index >= args.size()) {
1529 throw std::invalid_argument("CSS variable format placeholder index out of range");
1530 }
1531
1532 out += args[index];
1533 used[index] = true;
1534 i = pos;
1535 } else if (pattern[i] == '}') {
1536 if (i + 1 < pattern.size() && pattern[i + 1] == '}') {
1537 out.push_back('}');
1538 ++i;
1539 continue;
1540 }
1541 throw std::invalid_argument("Unmatched CSS variable format brace");
1542 } else {
1543 out.push_back(pattern[i]);
1544 }
1545 }
1546
1547 for (const auto was_used : used) {
1548 if (!was_used) {
1549 throw std::invalid_argument("Unused CSS variable format argument");
1550 }
1551 }
1552
1553 return out;
1554 }
1555
1556 private:
1558 static std::string normalize_name(const std::string& css_name) {
1559 if (css_name.empty()) {
1560 throw std::invalid_argument("CSS variable name cannot be empty");
1561 }
1562 if (css_name.size() >= 2 && css_name[0] == '-' && css_name[1] == '-') {
1563 return css_name;
1564 }
1565 return "--" + css_name;
1566 }
1567
1569 AttributeMap* target_;
1570 };
1571
1573 template<typename T>
1574 class Classes : public detail::TypedNames<T> {
1575 public:
1577 Classes(std::initializer_list<ClassSpec<T>> specs) {
1578 for (const auto& spec : specs) {
1579 this->add_name(spec.key, normalize_name(spec.css_class), "CSS class name");
1580 }
1581 }
1582
1584 template<typename... Keys>
1585 std::string selector(Keys... keys) const {
1586 const auto names = this->names(keys...);
1587 std::string out;
1588 for (const auto& name : names) {
1589 out += "." + name;
1590 }
1591 return out;
1592 }
1593
1595 template<typename... Keys>
1596 std::string classes(Keys... keys) const {
1597 const auto names = this->names(keys...);
1598 std::string out;
1599 for (const auto& name : names) {
1600 if (!out.empty()) out += " ";
1601 out += name;
1602 }
1603 return out;
1604 }
1605
1606 private:
1607 static std::string normalize_name(std::string css_class) {
1608 if (!css_class.empty() && css_class[0] == '.') {
1609 css_class.erase(0, 1);
1610 }
1611 if (css_class.empty()) {
1612 throw std::invalid_argument("CSS class name cannot be empty");
1613 }
1614 for (const auto ch : css_class) {
1615 if (std::isspace(static_cast<unsigned char>(ch))) {
1616 throw std::invalid_argument("CSS class name cannot contain whitespace");
1617 }
1618 }
1619 return css_class;
1620 }
1621
1622 template<typename... Keys>
1623 std::vector<std::string> names(Keys... keys) const {
1624 return std::vector<std::string>{ this->name(keys)... };
1625 }
1626 };
1627
1631 class Element: public AttributeMap {
1632 public:
1636 class BoundingBox : public QuadCoord {
1637 public:
1638 using QuadCoord::QuadCoord;
1639 BoundingBox() = default;
1640 BoundingBox(double a, double b, double c, double d) : QuadCoord({ a, b, c, d }) {};
1641
1642 BoundingBox operator+ (const BoundingBox& other) const {
1644 using namespace util;
1645 BoundingBox new_box;
1646 new_box.x1 = min_or_not_nan(this->x1, other.x1);
1647 new_box.x2 = max_or_not_nan(this->x2, other.x2);
1648 new_box.y1 = min_or_not_nan(this->y1, other.y1);
1649 new_box.y2 = max_or_not_nan(this->y2, other.y2);
1650 return new_box;
1651 }
1652 };
1653 using ChildList = std::vector<Element*>;
1654 using ConstChildList = std::vector<const Element*>;
1655 using ChildMap = std::map<std::string, ChildList>;
1656 using ConstChildMap = std::map<std::string, ConstChildList>;
1657 class DepthFirstIterator;
1658 class ConstDepthFirstIterator;
1659 class DepthFirstRange;
1660 class ConstDepthFirstRange;
1662 TraversalOptions(bool _descend_into_nested_svgs = true) :
1663 descend_into_nested_svgs(_descend_into_nested_svgs) {}
1664
1667 };
1668
1669 Element() = default;
1670 virtual ~Element() = default;
1672 Element(const Element& other);
1673 Element(Element&& other) noexcept :
1674 AttributeMap(std::move(other)),
1675 children(std::move(other.children)),
1676 parent_(nullptr),
1677 owner_(nullptr),
1678 indexed_id_(),
1679 has_layout_bbox_(other.has_layout_bbox_),
1680 layout_bbox_(other.layout_bbox_),
1681 layout_bbox_padding_(other.layout_bbox_padding_) {
1682 other.has_layout_bbox_ = false;
1683 reparent_children();
1684 }
1685 Element& operator=(const Element&) = delete; // No copy assignment
1686 Element& operator=(Element&& other) noexcept {
1687 if (this != &other) {
1688 AttributeMap::operator=(std::move(other));
1689 children = std::move(other.children);
1690 parent_ = nullptr;
1691 owner_ = nullptr;
1692 indexed_id_.clear();
1693 has_layout_bbox_ = other.has_layout_bbox_;
1694 layout_bbox_ = other.layout_bbox_;
1695 layout_bbox_padding_ = other.layout_bbox_padding_;
1696 other.has_layout_bbox_ = false;
1697 reparent_children();
1698 }
1699 return *this;
1700 }
1701
1702 Element(const char* id) : AttributeMap(
1703 SVGAttrib({ { "id", id } })) {};
1704 using AttributeMap::AttributeMap;
1705
1706 // Implicit string conversion
1707 operator std::string() const { return this->svg_to_string(0); };
1708
1712 std::unique_ptr<Element> clone_element() const;
1713
1714 template<typename T, typename... Args>
1715 T* add_child(Args&&... args) {
1717 SVG_TYPE_CHECK;
1718 auto child = detail::make_child<T>(std::forward<Args>(args)...);
1719 return static_cast<T*>(this->insert_child(std::move(child), this->children.end()));
1720 }
1721
1722 template<typename T>
1723 Element& operator<<(T&& node) {
1725 SVG_TYPE_CHECK;
1726 auto child = detail::make_unique<T>(std::move(node));
1727 this->insert_child(std::move(child), this->children.end());
1728 return *this;
1729 }
1730
1731 template<typename T>
1732 std::vector<T*> get_children() {
1734 SVG_TYPE_CHECK;
1735 std::vector<T*> ret;
1736 auto child_elems = this->get_children_helper();
1737
1738 for (auto& child: child_elems)
1739 if (child->kind() == T::static_kind) ret.push_back(static_cast<T*>(child));
1740
1741 return ret;
1742 }
1743
1744 template<typename T>
1745 std::vector<const T*> get_children() const {
1747 SVG_TYPE_CHECK;
1748 std::vector<const T*> ret;
1749 auto child_elems = this->get_children_helper();
1750
1751 for (auto& child: child_elems)
1752 if (child->kind() == T::static_kind) ret.push_back(static_cast<const T*>(child));
1753
1754 return ret;
1755 }
1756
1757 template<typename T>
1758 std::vector<T*> get_immediate_children() {
1760 SVG_TYPE_CHECK;
1761 std::vector<T*> ret;
1762 for (auto& child : this->children) {
1763 if (child->kind() == T::static_kind) ret.push_back(static_cast<T*>(child.get()));
1764 }
1765
1766 return ret;
1767 }
1768
1769 template<typename T>
1770 std::vector<const T*> get_immediate_children() const {
1772 SVG_TYPE_CHECK;
1773 std::vector<const T*> ret;
1774 for (auto& child : this->children) {
1775 if (child->kind() == T::static_kind) ret.push_back(static_cast<const T*>(child.get()));
1776 }
1777
1778 return ret;
1779 }
1780
1781 Element* get_element_by_id(const std::string& id);
1782 const Element* get_element_by_id(const std::string& id) const;
1783 std::vector<Element*> get_elements_by_class(const std::string& clsname);
1784 std::vector<const Element*> get_elements_by_class(const std::string& clsname) const;
1785 const Element* parent() const { return parent_; }
1787 virtual ElementKind kind() const { return ElementKind::Custom; }
1788 Element& id(const std::string& value);
1789 std::string id() const;
1790 void autoscale(const Margins& margins=DEFAULT_MARGINS);
1791 void autoscale(const double margin);
1793 void autoscale(const AutoscaleOptions& options);
1795 void responsive_autoscale(const Margins& margins=DEFAULT_MARGINS);
1797 void responsive_autoscale(const double margin);
1799 void responsive_autoscale(const AutoscaleOptions& options);
1801 Element& layout_bbox(const BoundingBox& bbox);
1803 Element& bbox_padding(const Margins& padding);
1805 Element& bbox_padding(double padding);
1807 Element& clear_layout_bbox();
1809 bool has_layout_bbox() const { return has_layout_bbox_; }
1811 BoundingBox layout_bbox() const;
1813 Element& snap_to(const Element& target,
1814 RelativeAlignment relative,
1815 Point offset = Point(0, 0));
1817 Element& snap_to(const Element& target,
1818 Alignment alignment,
1819 Point offset = Point(0, 0));
1821 Element& align_to(const Element& target,
1822 Axis axis,
1823 Point offset = Point(0, 0));
1825 Element& align_to(const Element& target,
1826 Axis axis,
1827 Anchor anchor,
1828 Point offset = Point(0, 0));
1829 TransformList transform_list() {
1830 return TransformList(this->mutable_attrs()["transform"], this);
1831 }
1832 TransformList transform_list() const {
1833 const auto& attributes = this->attrs();
1834 const auto found = attributes.find("transform");
1835 return found == attributes.end() ? TransformList() : TransformList(found->second, this);
1836 }
1837 TransformList transform() {
1838 return transform_list();
1839 }
1840 TransformList transform() const {
1841 return transform_list();
1842 }
1843 virtual BoundingBox get_bbox() const;
1844 ChildMap get_children();
1845 ConstChildMap get_children() const;
1847 DepthFirstIterator begin();
1849 ConstDepthFirstIterator begin() const;
1850 DepthFirstIterator end();
1851 ConstDepthFirstIterator end() const;
1853 DepthFirstRange depth_first();
1855 DepthFirstRange depth_first(TraversalOptions options);
1857 ConstDepthFirstRange depth_first() const;
1859 ConstDepthFirstRange depth_first(TraversalOptions options) const;
1861 DepthFirstRange descendants();
1863 DepthFirstRange descendants(TraversalOptions options);
1865 ConstDepthFirstRange descendants() const;
1867 ConstDepthFirstRange descendants(TraversalOptions options) const;
1868
1869 protected:
1870 std::vector<std::unique_ptr<Element>> children;
1871 using ChildIterator = std::vector<std::unique_ptr<Element>>::iterator;
1872 std::vector<Element*> get_children_helper();
1873 std::vector<const Element*> get_children_helper() const;
1874 BoundingBox get_autoscale_bbox() const;
1875 BoundingBox measured_layout_bbox() const;
1876 BoundingBox include_stroke_width(const BoundingBox& bbox) const;
1877 void autoscale_nested_svgs(const AutoscaleOptions& options, bool responsive);
1878 static detail::AffineTransform transform_for(const Element* element,
1879 const detail::AffineTransform& parent_transform);
1880 void get_bbox(Element::BoundingBox&, bool visible_only = true) const;
1881 void set_viewbox_from_bbox(const BoundingBox& bbox, const Margins& margins);
1882 virtual std::string svg_to_string(const size_t indent_level) const;
1883 virtual std::string tag() { return tag_name(this->kind()); }
1885 virtual std::string tag() const { return const_cast<Element*>(this)->tag(); }
1886 virtual std::unique_ptr<Element> clone_element_impl() const;
1887
1888 template<typename T>
1889 std::unique_ptr<T> clone_as() const {
1890 static_assert(std::is_copy_constructible<T>::value,
1891 "Cloneable SVG element subclasses must be copy constructible.");
1892 return detail::make_unique<T>(static_cast<const T&>(*this));
1893 }
1894
1895 void set_attr_value(const std::string& key, const std::string& value) override;
1896 AttrSetter make_attr_setter(const std::string& key) override;
1897 SVG* owner_svg();
1898 const SVG* owner_svg() const;
1899 void set_owner_svg(SVG* owner);
1900 void register_subtree_ids();
1901 void unregister_subtree_ids();
1902 void register_own_id();
1903 void unregister_own_id();
1904 void clear_children();
1905
1906 Element* insert_child(std::unique_ptr<Element> child, ChildIterator position) {
1907 child->parent_ = this;
1908 child->set_owner_svg(this->owner_svg());
1909 try {
1910 child->register_subtree_ids();
1911 } catch (...) {
1912 child->unregister_subtree_ids();
1913 child->set_owner_svg(nullptr);
1914 child->parent_ = nullptr;
1915 throw;
1916 }
1917 return children.insert(position, std::move(child))->get();
1918 }
1919
1920 void reparent_children() {
1921 for (auto& child : children) {
1922 child->parent_ = this;
1923 child->set_owner_svg(this->owner_);
1924 child->reparent_children();
1925 }
1926 }
1927
1928 double find_numeric(const std::string& key) const {
1933 if (this->has_attr(key))
1934 return std::stof(this->get_attr(key));
1935 return NAN;
1936 }
1937
1938 private:
1939 Element* parent_ = nullptr;
1940 SVG* owner_ = nullptr;
1941 std::string indexed_id_;
1942 bool has_layout_bbox_ = false;
1943 BoundingBox layout_bbox_ = BoundingBox(NAN, NAN, NAN, NAN);
1944 Margins layout_bbox_padding_ = NO_MARGINS;
1945 };
1946
1947 template<>
1948 inline Element::ChildList Element::get_immediate_children() {
1950 Element::ChildList ret;
1951 for (auto& child : this->children) ret.push_back(child.get());
1952 return ret;
1953 }
1954
1955 template<>
1956 inline Element::ConstChildList Element::get_immediate_children() const {
1958 Element::ConstChildList ret;
1959 for (auto& child : this->children) ret.push_back(child.get());
1960 return ret;
1961 }
1962
1963 inline Element* Element::get_element_by_id(const std::string &id) {
1965 auto child_elems = this->get_children_helper();
1966 for (auto& current: child_elems)
1967 if (current->id() == id) return current;
1968
1969 return nullptr;
1970 }
1971
1972 inline const Element* Element::get_element_by_id(const std::string &id) const {
1974 auto child_elems = this->get_children_helper();
1975 for (auto& current: child_elems)
1976 if (current->id() == id) return current;
1977
1978 return nullptr;
1979 }
1980
1981 inline std::vector<Element*> Element::get_elements_by_class(const std::string &clsname) {
1983 std::vector<Element*> ret;
1984 auto child_elems = this->get_children_helper();
1985
1986 for (auto& current: child_elems) {
1987 if (current->has_attr("class")
1988 && current->class_list().contains(clsname))
1989 ret.push_back(current);
1990 }
1991
1992 return ret;
1993 }
1994
1995 inline std::vector<const Element*> Element::get_elements_by_class(const std::string &clsname) const {
1997 std::vector<const Element*> ret;
1998 auto child_elems = this->get_children_helper();
1999
2000 for (auto& current: child_elems) {
2001 if (current->has_attr("class")
2002 && current->class_list().contains(clsname))
2003 ret.push_back(current);
2004 }
2005
2006 return ret;
2007 }
2008
2009 inline std::unique_ptr<Element> Element::clone_element() const {
2010 return this->clone_element_impl();
2011 }
2012
2013 inline Element::Element(const Element& other) :
2014 AttributeMap(other.attrs()),
2015 parent_(nullptr),
2016 owner_(nullptr),
2017 indexed_id_(),
2018 has_layout_bbox_(other.has_layout_bbox_),
2019 layout_bbox_(other.layout_bbox_),
2020 layout_bbox_padding_(other.layout_bbox_padding_) {
2021 for (const auto& child : other.children) {
2022 this->insert_child(child->clone_element(), this->children.end());
2023 }
2024 }
2025
2026 inline std::unique_ptr<Element> Element::clone_element_impl() const {
2027 throw std::logic_error("Custom SVG element subclasses must override clone_element_impl() to be cloned.");
2028 }
2029
2031 public:
2032 using iterator_category = std::input_iterator_tag;
2033 using value_type = Element*;
2034 using difference_type = std::ptrdiff_t;
2035 using pointer = Element**;
2036 using reference = Element*&;
2037
2038 DepthFirstIterator() = default;
2039 DepthFirstIterator(Element* root, bool include_root, TraversalOptions options = TraversalOptions()) :
2040 options_(options) {
2041 if (!root) return;
2042
2043 const auto root_transform = transform_for(root, detail::AffineTransform());
2044 if (include_root) {
2045 pending_.push_back(Frame(root, root_transform, true));
2046 } else {
2047 push_children(root, root_transform);
2048 }
2049 advance();
2050 }
2051
2052 Element* operator*() const { return current_.element; }
2053 const detail::AffineTransform& transform() const { return current_.transform; }
2054
2055 DepthFirstIterator& operator++() {
2056 advance();
2057 return *this;
2058 }
2059
2060 DepthFirstIterator operator++(int) {
2061 auto copy = *this;
2062 ++(*this);
2063 return copy;
2064 }
2065
2066 bool operator==(const DepthFirstIterator& other) const {
2067 if (end_ || other.end_) return end_ == other.end_;
2068 return current_.element == other.current_.element;
2069 }
2070
2071 bool operator!=(const DepthFirstIterator& other) const {
2072 return !(*this == other);
2073 }
2074
2075 private:
2076 struct Frame {
2077 Frame() : element(nullptr), transform(), root(false) {}
2078 Frame(Element* element, const detail::AffineTransform& transform, bool root = false) :
2079 element(element),
2080 transform(transform),
2081 root(root) {}
2082
2083 Element* element;
2084 detail::AffineTransform transform;
2085 bool root;
2086 };
2087
2088 static detail::AffineTransform transform_for(Element* element,
2089 const detail::AffineTransform& parent_transform) {
2090 if (element->has_attr("transform")) {
2091 return detail::multiply(parent_transform,
2092 detail::parse_supported_transform(element->get_attr("transform")));
2093 }
2094 return parent_transform;
2095 }
2096
2097 void push_children(Element* element, const detail::AffineTransform& transform) {
2098 for (auto child = element->children.rbegin(); child != element->children.rend(); ++child) {
2099 auto* child_ptr = child->get();
2100 pending_.push_back(Frame(child_ptr, transform_for(child_ptr, transform)));
2101 }
2102 }
2103
2104 void advance() {
2105 if (pending_.empty()) {
2106 current_ = Frame();
2107 end_ = true;
2108 return;
2109 }
2110
2111 current_ = pending_.back();
2112 pending_.pop_back();
2113 end_ = false;
2114 if (current_.root || options_.descend_into_nested_svgs ||
2115 current_.element->kind() != ElementKind::SVG) {
2116 push_children(current_.element, current_.transform);
2117 }
2118 }
2119
2120 std::vector<Frame> pending_;
2121 Frame current_;
2122 TraversalOptions options_;
2123 bool end_ = true;
2124 };
2125
2127 public:
2128 using iterator_category = std::input_iterator_tag;
2129 using value_type = const Element*;
2130 using difference_type = std::ptrdiff_t;
2131 using pointer = const Element**;
2132 using reference = const Element*&;
2133
2134 ConstDepthFirstIterator() = default;
2135 ConstDepthFirstIterator(const Element* root,
2136 bool include_root,
2137 TraversalOptions options = TraversalOptions()) :
2138 options_(options) {
2139 if (!root) return;
2140
2141 const auto root_transform = transform_for(root, detail::AffineTransform());
2142 if (include_root) {
2143 pending_.push_back(Frame(root, root_transform, true));
2144 } else {
2145 push_children(root, root_transform);
2146 }
2147 advance();
2148 }
2149
2150 const Element* operator*() const { return current_.element; }
2151 const detail::AffineTransform& transform() const { return current_.transform; }
2152
2153 ConstDepthFirstIterator& operator++() {
2154 advance();
2155 return *this;
2156 }
2157
2158 ConstDepthFirstIterator operator++(int) {
2159 auto copy = *this;
2160 ++(*this);
2161 return copy;
2162 }
2163
2164 bool operator==(const ConstDepthFirstIterator& other) const {
2165 if (end_ || other.end_) return end_ == other.end_;
2166 return current_.element == other.current_.element;
2167 }
2168
2169 bool operator!=(const ConstDepthFirstIterator& other) const {
2170 return !(*this == other);
2171 }
2172
2173 private:
2174 struct Frame {
2175 Frame() : element(nullptr), transform(), root(false) {}
2176 Frame(const Element* element, const detail::AffineTransform& transform, bool root = false) :
2177 element(element),
2178 transform(transform),
2179 root(root) {}
2180
2181 const Element* element;
2182 detail::AffineTransform transform;
2183 bool root;
2184 };
2185
2186 static detail::AffineTransform transform_for(const Element* element,
2187 const detail::AffineTransform& parent_transform) {
2188 if (element->has_attr("transform")) {
2189 return detail::multiply(parent_transform,
2190 detail::parse_supported_transform(element->get_attr("transform")));
2191 }
2192 return parent_transform;
2193 }
2194
2195 void push_children(const Element* element, const detail::AffineTransform& transform) {
2196 for (auto child = element->children.rbegin(); child != element->children.rend(); ++child) {
2197 const auto* child_ptr = child->get();
2198 pending_.push_back(Frame(child_ptr, transform_for(child_ptr, transform)));
2199 }
2200 }
2201
2202 void advance() {
2203 if (pending_.empty()) {
2204 current_ = Frame();
2205 end_ = true;
2206 return;
2207 }
2208
2209 current_ = pending_.back();
2210 pending_.pop_back();
2211 end_ = false;
2212 if (current_.root || options_.descend_into_nested_svgs ||
2213 current_.element->kind() != ElementKind::SVG) {
2214 push_children(current_.element, current_.transform);
2215 }
2216 }
2217
2218 std::vector<Frame> pending_;
2219 Frame current_;
2220 TraversalOptions options_;
2221 bool end_ = true;
2222 };
2223
2225 public:
2226 DepthFirstRange(Element* root, bool include_root, TraversalOptions options = TraversalOptions()) :
2227 root_(root), include_root_(include_root), options_(options) {}
2228
2229 DepthFirstIterator begin() const { return DepthFirstIterator(root_, include_root_, options_); }
2230 DepthFirstIterator end() const { return DepthFirstIterator(); }
2231
2232 private:
2233 Element* root_;
2234 bool include_root_;
2235 TraversalOptions options_;
2236 };
2237
2239 public:
2240 ConstDepthFirstRange(const Element* root,
2241 bool include_root,
2242 TraversalOptions options = TraversalOptions()) :
2243 root_(root), include_root_(include_root), options_(options) {}
2244
2245 ConstDepthFirstIterator begin() const { return ConstDepthFirstIterator(root_, include_root_, options_); }
2246 ConstDepthFirstIterator end() const { return ConstDepthFirstIterator(); }
2247
2248 private:
2249 const Element* root_;
2250 bool include_root_;
2251 TraversalOptions options_;
2252 };
2253
2255 return { this, true };
2256 }
2257
2259 return { this, true, options };
2260 }
2261
2263 return { this, true };
2264 }
2265
2267 return { this, true, options };
2268 }
2269
2271 return DepthFirstIterator(this, true);
2272 }
2273
2275 return ConstDepthFirstIterator(this, true);
2276 }
2277
2278 inline Element::DepthFirstIterator Element::end() {
2279 return DepthFirstIterator();
2280 }
2281
2282 inline Element::ConstDepthFirstIterator Element::end() const {
2283 return ConstDepthFirstIterator();
2284 }
2285
2287 return { this, false };
2288 }
2289
2291 return { this, false, options };
2292 }
2293
2295 return { this, false };
2296 }
2297
2299 return { this, false, options };
2300 }
2301
2304 return { NAN, NAN, NAN, NAN };
2305 }
2306
2310 class Shape: public Element {
2311 public:
2312 using Element::Element;
2313
2314 operator Point() const {
2316 return std::make_pair(this->x(), this->y());
2317 }
2318
2319 virtual std::vector<Point> points() const {
2321 auto bbox = this->get_bbox();
2322 return {
2323 Point(bbox.x1, bbox.y1), // Top left
2324 Point(bbox.x2, bbox.y1), // Top right
2325 Point(bbox.x1, bbox.y2), // Bottom left
2326 Point(bbox.x2, bbox.y2) // Bottom right
2327 };
2328 }
2329
2330 virtual double x() const { return this->find_numeric("x"); }
2331 virtual double y() const { return this->find_numeric("y"); }
2332 virtual double width() const {
2336 return this->find_numeric("width");
2337 }
2338 virtual double height() const {
2342 return this->find_numeric("height");
2343 }
2344 };
2345
2347 class Defs : public Element {
2348 public:
2349 static constexpr ElementKind static_kind = ElementKind::Defs;
2350 using Element::Element;
2351 ElementKind kind() const override { return static_kind; }
2353 LinearGradient& linear_gradient(std::string id);
2355 RadialGradient& radial_gradient(std::string id);
2357 Symbol* symbol(std::string id);
2358
2359 protected:
2360 std::unique_ptr<Element> clone_element_impl() const override { return clone_as<Defs>(); }
2361 };
2362
2364 namespace detail {
2366 class Stop : public Element {
2367 public:
2368 static constexpr ElementKind static_kind = ElementKind::Stop;
2369 Stop() = default;
2370 using Element::Element;
2371 ElementKind kind() const override { return static_kind; }
2372
2373 Stop(std::string offset, std::string color) {
2374 this->set_attr("offset", std::move(offset));
2375 this->set_attr("stop-color", std::move(color));
2376 }
2377
2378 Stop(std::string offset, std::string color, double opacity) :
2379 Stop(std::move(offset), std::move(color)) {
2380 this->set_attr("stop-opacity", opacity);
2381 }
2382
2383 protected:
2384 std::unique_ptr<Element> clone_element_impl() const override { return clone_as<Stop>(); }
2385 };
2386
2388 class GradientElement : public Element {
2389 public:
2390 using Element::Element;
2391
2393 std::string url() const {
2394 const auto gradient_id = this->id();
2395 if (gradient_id.empty()) {
2396 throw std::logic_error("SVG gradient must have an id before it can be referenced");
2397 }
2398 return "url(#" + gradient_id + ")";
2399 }
2400
2401 protected:
2402 void add_stop(std::string offset, std::string color) {
2403 this->add_child<Stop>(std::move(offset), std::move(color));
2404 }
2405
2406 void add_stop(std::string offset, std::string color, double opacity) {
2407 this->add_child<Stop>(std::move(offset), std::move(color), opacity);
2408 }
2409
2410 void set_solid_segments(std::initializer_list<std::string> colors) {
2411 if (colors.size() == 0) {
2412 throw std::invalid_argument("solid_segments requires at least one color");
2413 }
2414
2415 this->clear_children();
2416 if (colors.size() == 1) {
2417 const auto color = *colors.begin();
2418 add_stop("0%", color);
2419 add_stop("100%", color);
2420 return;
2421 }
2422
2423 const auto segment_width = 100.0 / static_cast<double>(colors.size());
2424 std::size_t index = 0;
2425 for (const auto& color : colors) {
2426 const auto start = percent(index * segment_width);
2427 const auto end = percent((index + 1) * segment_width);
2428 add_stop(start, color);
2429 add_stop(end, color);
2430 ++index;
2431 }
2432 }
2433
2434 private:
2435 static std::string percent(double value) {
2436 std::stringstream ss;
2437 ss << std::fixed << std::setprecision(1) << value << "%";
2438 return ss.str();
2439 }
2440 };
2441 }
2445 class LinearGradient : public detail::GradientElement {
2446 public:
2447 static constexpr ElementKind static_kind = ElementKind::LinearGradient;
2448 LinearGradient() = default;
2449 using detail::GradientElement::GradientElement;
2450 ElementKind kind() const override { return static_kind; }
2451
2452 explicit LinearGradient(std::string id) {
2453 this->id(id);
2454 }
2455
2456 LinearGradient& horizontal() {
2457 return endpoints("0%", "0%", "100%", "0%");
2458 }
2459
2460 LinearGradient& vertical() {
2461 return endpoints("0%", "0%", "0%", "100%");
2462 }
2463
2464 LinearGradient& endpoints(std::string x1, std::string y1, std::string x2, std::string y2) {
2465 this->set_attr("x1", std::move(x1));
2466 this->set_attr("y1", std::move(y1));
2467 this->set_attr("x2", std::move(x2));
2468 this->set_attr("y2", std::move(y2));
2469 return *this;
2470 }
2471
2472 LinearGradient& stop(std::string offset, std::string color) {
2473 add_stop(std::move(offset), std::move(color));
2474 return *this;
2475 }
2476
2477 LinearGradient& stop(std::string offset, const Color& color) {
2478 return stop(std::move(offset), static_cast<std::string>(color));
2479 }
2480
2481 LinearGradient& stop(std::string offset, std::string color, double opacity) {
2482 add_stop(std::move(offset), std::move(color), opacity);
2483 return *this;
2484 }
2485
2486 LinearGradient& stop(std::string offset, const Color& color, double opacity) {
2487 return stop(std::move(offset), static_cast<std::string>(color), opacity);
2488 }
2489
2490 LinearGradient& solid_segments(std::initializer_list<std::string> colors) {
2491 set_solid_segments(colors);
2492 return *this;
2493 }
2494
2495 protected:
2496 std::unique_ptr<Element> clone_element_impl() const override { return clone_as<LinearGradient>(); }
2497 };
2498
2500 class RadialGradient : public detail::GradientElement {
2501 public:
2502 static constexpr ElementKind static_kind = ElementKind::RadialGradient;
2503 RadialGradient() = default;
2504 using detail::GradientElement::GradientElement;
2505 ElementKind kind() const override { return static_kind; }
2506
2507 explicit RadialGradient(std::string id) {
2508 this->id(id);
2509 }
2510
2511 RadialGradient& center(std::string cx, std::string cy) {
2512 this->set_attr("cx", std::move(cx));
2513 this->set_attr("cy", std::move(cy));
2514 return *this;
2515 }
2516
2517 RadialGradient& radius(std::string r) {
2518 this->set_attr("r", std::move(r));
2519 return *this;
2520 }
2521
2522 RadialGradient& focal(std::string fx, std::string fy) {
2523 this->set_attr("fx", std::move(fx));
2524 this->set_attr("fy", std::move(fy));
2525 return *this;
2526 }
2527
2528 RadialGradient& stop(std::string offset, std::string color) {
2529 add_stop(std::move(offset), std::move(color));
2530 return *this;
2531 }
2532
2533 RadialGradient& stop(std::string offset, const Color& color) {
2534 return stop(std::move(offset), static_cast<std::string>(color));
2535 }
2536
2537 RadialGradient& stop(std::string offset, std::string color, double opacity) {
2538 add_stop(std::move(offset), std::move(color), opacity);
2539 return *this;
2540 }
2541
2542 RadialGradient& stop(std::string offset, const Color& color, double opacity) {
2543 return stop(std::move(offset), static_cast<std::string>(color), opacity);
2544 }
2545
2546 RadialGradient& solid_segments(std::initializer_list<std::string> colors) {
2547 set_solid_segments(colors);
2548 return *this;
2549 }
2550
2551 protected:
2552 std::unique_ptr<Element> clone_element_impl() const override { return clone_as<RadialGradient>(); }
2553 };
2554
2556 class Symbol : public Element {
2557 public:
2558 static constexpr ElementKind static_kind = ElementKind::Symbol;
2559 Symbol() = default;
2560 using Element::Element;
2561
2562 explicit Symbol(std::string id) {
2563 this->id(id);
2564 }
2565
2567 std::string href() const;
2568 ElementKind kind() const override { return static_kind; }
2570 Element::BoundingBox get_bbox() const override;
2572 Use use(double x, double y) const;
2574 Use use(double x, double y, double width, double height) const;
2575
2576 protected:
2577 std::unique_ptr<Element> clone_element_impl() const override { return clone_as<Symbol>(); }
2578 };
2579
2581 class Use : public Shape {
2582 public:
2583 static constexpr ElementKind static_kind = ElementKind::Use;
2584 Use() = default;
2585 using Shape::Shape;
2586
2587 explicit Use(std::string href) {
2588 set_attr("href", std::move(href));
2589 }
2590
2591 Use(std::string href, double x, double y) : Use(std::move(href)) {
2592 set_attr("x", x);
2593 set_attr("y", y);
2594 }
2595
2596 Use(std::string href, double x, double y, double width, double height) :
2597 Use(std::move(href), x, y) {
2598 set_attr("width", width);
2599 set_attr("height", height);
2600 }
2601
2603 Use& xlink_href(std::string href) {
2604 set_attr("xlink:href", std::move(href));
2605 return *this;
2606 }
2607 ElementKind kind() const override { return static_kind; }
2609 Element::BoundingBox get_bbox() const override;
2610
2611 protected:
2612 std::unique_ptr<Element> clone_element_impl() const override { return clone_as<Use>(); }
2613 };
2614
2615 class SVG : public Shape {
2616 friend class Element;
2617
2618 std::map<std::string, Element*> id_index_;
2619 Defs* defs_ = nullptr;
2620
2621 public:
2622 class Style : public Element {
2623 public:
2624 static constexpr ElementKind static_kind = ElementKind::Style;
2625 Style() = default;
2626 using Element::Element;
2628 std::map<std::string, SelectorProperties> media_queries;
2629 std::map<std::string, SelectorProperties> keyframes;
2630 ElementKind kind() const override { return static_kind; }
2631
2632 protected:
2633 std::string svg_to_string(const size_t) const override;
2634 std::unique_ptr<Element> clone_element_impl() const override;
2635 };
2636
2637 static constexpr ElementKind static_kind = ElementKind::SVG;
2639 SVG(SVGAttrib _attr =
2640 { { "xmlns", "http://www.w3.org/2000/svg" } }
2641 ) : Shape(_attr) {
2642 set_owner_svg(this);
2643 rebuild_id_index();
2644 };
2645
2647 SVG(const SVG& other) : Shape(other.attrs()), id_index_(), defs_(nullptr), css(nullptr) {
2648 clone_children_from(other);
2649 refresh_special_children();
2650 set_owner_svg(this);
2651 rebuild_id_index();
2652 }
2653
2654 SVG(SVG&& other) noexcept :
2655 Shape(std::move(other)),
2656 id_index_(std::move(other.id_index_)),
2657 defs_(nullptr),
2658 css(nullptr) {
2659 refresh_special_children();
2660 set_owner_svg(this);
2661 rebuild_id_index();
2662 }
2663
2664 SVG& operator=(SVG&& other) noexcept {
2665 if (this != &other) {
2666 Shape::operator=(std::move(other));
2667 id_index_ = std::move(other.id_index_);
2668 defs_ = nullptr;
2669 css = nullptr;
2670 refresh_special_children();
2671 set_owner_svg(this);
2672 rebuild_id_index();
2673 }
2674 return *this;
2675 }
2676
2677 SVG& operator=(const SVG& other) = delete;
2678
2680 SVG clone() const {
2681 return SVG(*this);
2682 }
2683
2685 AttributeMap& style(const std::string& key) { return this->css->css[key]; }
2686
2688 SVG& style(const std::string& key, const Attrs& attrs) {
2689 this->style(key).set_attrs(attrs);
2690 return *this;
2691 }
2692
2694 AttributeMap& media_style(const std::string& query, const std::string& key) {
2695 return this->css->media_queries[query][key];
2696 }
2697
2699 SVG& media_style(const std::string& query, const std::string& key, const Attrs& attrs) {
2700 this->media_style(query, key).set_attrs(attrs);
2701 return *this;
2702 }
2703
2705 template<typename T>
2706 Variables<T> set_vars(std::initializer_list<VariableSpec<T>> specs) {
2707 return this->set_vars<T>(":root", specs);
2708 }
2709
2711 template<typename T>
2712 Variables<T> set_vars(const std::string& selector, std::initializer_list<VariableSpec<T>> specs) {
2713 return Variables<T>(this->style(selector), specs);
2714 }
2715
2717 template<typename T>
2718 Variables<T> set_vars(const std::string& query,
2719 const std::string& selector,
2720 std::initializer_list<VariableSpec<T>> specs) {
2721 return Variables<T>(this->media_style(query, selector), specs);
2722 }
2723
2724 std::map<std::string, AttributeMap>& keyframes(const std::string& key) {
2729 if (!this->css) this->css = this->add_child<Style>();
2730 return this->css->keyframes[key];
2731 }
2732
2735 if (!this->defs_) this->defs_ = this->add_child<Defs>();
2736 return this->defs_;
2737 }
2738
2739 template<typename T, typename... Args>
2740 typename std::enable_if<!std::is_same<T, Defs>::value, T*>::type add_child(Args&&... args) {
2741 return Element::add_child<T>(std::forward<Args>(args)...);
2742 }
2743
2744 template<typename T, typename... Args>
2745 typename std::enable_if<std::is_same<T, Defs>::value, T*>::type add_child(Args&&... args) {
2746 if (this->defs_) return this->defs_;
2747
2748 auto child = detail::make_child<T>(std::forward<Args>(args)...);
2749 auto* inserted = static_cast<T*>(
2750 this->insert_child(std::move(child), this->defs_insert_position()));
2751 this->defs_ = inserted;
2752 return inserted;
2753 }
2754
2755 Element* get_element_by_id(const std::string& id) {
2756 const auto found = this->id_index_.find(id);
2757 return found == this->id_index_.end() ? nullptr : found->second;
2758 }
2759
2760 const Element* get_element_by_id(const std::string& id) const {
2761 const auto found = this->id_index_.find(id);
2762 return found == this->id_index_.end() ? nullptr : found->second;
2763 }
2764
2766 template<typename T>
2767 T* get_element_by_id(const std::string& id) {
2768 SVG_TYPE_CHECK;
2769 auto* element = this->get_element_by_id(id);
2770 if (!element || element->kind() != T::static_kind) return nullptr;
2771 return static_cast<T*>(element);
2772 }
2773
2775 template<typename T>
2776 const T* get_element_by_id(const std::string& id) const {
2777 SVG_TYPE_CHECK;
2778 auto* element = this->get_element_by_id(id);
2779 if (!element || element->kind() != T::static_kind) return nullptr;
2780 return static_cast<const T*>(element);
2781 }
2782
2783 Style* css = this->add_child<Style>();
2784 ElementKind kind() const override { return static_kind; }
2786 BoundingBox get_bbox() const override;
2787
2788 protected:
2789 std::unique_ptr<Element> clone_element_impl() const override {
2790 return detail::make_unique<SVG>(*this);
2791 }
2792
2793 private:
2794 void clone_children_from(const SVG& other) {
2795 for (const auto& child : other.children) {
2796 this->insert_child(child->clone_element(), this->children.end());
2797 }
2798 }
2799
2800 void refresh_special_children() {
2801 this->css = nullptr;
2802 this->defs_ = nullptr;
2803 for (auto& child : this->children) {
2804 if (child->kind() == Style::static_kind) {
2805 this->css = static_cast<Style*>(child.get());
2806 } else if (child->kind() == Defs::static_kind) {
2807 this->defs_ = static_cast<Defs*>(child.get());
2808 }
2809 }
2810 }
2811
2812 void register_id(Element& element, const std::string& id) {
2813 if (id.empty()) return;
2814 const auto found = this->id_index_.find(id);
2815 if (found != this->id_index_.end() && found->second != &element) {
2816 throw std::invalid_argument("Duplicate SVG element id: " + id);
2817 }
2818 this->id_index_[id] = &element;
2819 }
2820
2821 void unregister_id(Element& element, const std::string& id) {
2822 if (id.empty()) return;
2823 const auto found = this->id_index_.find(id);
2824 if (found != this->id_index_.end() && found->second == &element) {
2825 this->id_index_.erase(found);
2826 }
2827 }
2828
2829 void rebuild_id_index() {
2830 this->id_index_.clear();
2831 this->register_subtree_ids();
2832 }
2833
2834 ChildIterator defs_insert_position() {
2835 if (!this->css) return this->children.begin();
2836
2837 for (auto it = this->children.begin(); it != this->children.end(); ++it) {
2838 if (it->get() == this->css) return it + 1;
2839 }
2840 return this->children.begin();
2841 }
2842
2843 };
2844
2845 inline SVG* Element::owner_svg() {
2846 return this->owner_;
2847 }
2848
2849 inline const SVG* Element::owner_svg() const {
2850 return this->owner_;
2851 }
2852
2853 inline void Element::set_owner_svg(SVG* owner) {
2854 this->owner_ = owner;
2855 for (auto& child : this->children) {
2856 child->set_owner_svg(owner);
2857 }
2858 }
2859
2860 inline Element& Element::id(const std::string& value) {
2861 const auto old_indexed_id = this->indexed_id_;
2862 auto* root = this->owner_svg();
2863
2864 if (value.empty()) {
2865 if (root && !old_indexed_id.empty()) {
2866 root->unregister_id(*this, old_indexed_id);
2867 }
2868 this->mutable_attrs().erase("id");
2869 this->indexed_id_.clear();
2870 return *this;
2871 }
2872
2873 if (root && old_indexed_id != value) {
2874 root->register_id(*this, value);
2875 }
2876 if (root && !old_indexed_id.empty() && old_indexed_id != value) {
2877 root->unregister_id(*this, old_indexed_id);
2878 }
2879
2880 this->mutable_attrs()["id"] = value;
2881 this->indexed_id_ = root ? value : "";
2882 return *this;
2883 }
2884
2885 inline std::string Element::id() const {
2886 return this->get_attr("id");
2887 }
2888
2889 inline void Element::set_attr_value(const std::string& key, const std::string& value) {
2890 if (key == "id") {
2891 this->id(value);
2892 return;
2893 }
2894 AttributeMap::set_attr_value(key, value);
2895 }
2896
2897 inline AttributeMap::AttrSetter Element::make_attr_setter(const std::string& key) {
2898 if (key != "id") {
2899 return AttributeMap::make_attr_setter(key);
2900 }
2901
2902 this->id("");
2903 this->mutable_attrs()[key] = "";
2904 return AttrSetter(this->mutable_attrs().at(key), false, [this](const std::string& value) {
2905 const auto previous = this->indexed_id_;
2906 try {
2907 this->id(value);
2908 } catch (...) {
2909 if (previous.empty()) {
2910 this->mutable_attrs().erase("id");
2911 } else {
2912 this->mutable_attrs()["id"] = previous;
2913 }
2914 throw;
2915 }
2916 });
2917 }
2918
2919 inline void Element::register_subtree_ids() {
2920 this->register_own_id();
2921 for (auto& child : this->children) {
2922 child->register_subtree_ids();
2923 }
2924 }
2925
2926 inline void Element::unregister_subtree_ids() {
2927 this->unregister_own_id();
2928 for (auto& child : this->children) {
2929 child->unregister_subtree_ids();
2930 }
2931 }
2932
2933 inline void Element::register_own_id() {
2934 const auto current_id = this->id();
2935 if (current_id.empty()) return;
2936
2937 if (auto* root = this->owner_svg()) {
2938 root->register_id(*this, current_id);
2939 this->indexed_id_ = current_id;
2940 }
2941 }
2942
2943 inline void Element::unregister_own_id() {
2944 if (this->indexed_id_.empty()) return;
2945
2946 if (auto* root = this->owner_svg()) {
2947 root->unregister_id(*this, this->indexed_id_);
2948 }
2949 this->indexed_id_.clear();
2950 }
2951
2952 inline void Element::clear_children() {
2953 for (auto& child : this->children) {
2954 child->unregister_subtree_ids();
2955 child->set_owner_svg(nullptr);
2956 child->parent_ = nullptr;
2957 }
2958 this->children.clear();
2959 }
2960
2961 inline LinearGradient& Defs::linear_gradient(std::string id) {
2962 for (auto* child : this->get_immediate_children<LinearGradient>()) {
2963 if (child->id() == id) return *child;
2964 }
2965 return *this->add_child<LinearGradient>(std::move(id));
2966 }
2967
2968 inline RadialGradient& Defs::radial_gradient(std::string id) {
2969 for (auto* child : this->get_immediate_children<RadialGradient>()) {
2970 if (child->id() == id) return *child;
2971 }
2972 return *this->add_child<RadialGradient>(std::move(id));
2973 }
2974
2975 inline Symbol* Defs::symbol(std::string id) {
2976 for (auto* child : this->get_immediate_children<Symbol>()) {
2977 if (child->id() == id) return child;
2978 }
2979 return this->add_child<Symbol>(std::move(id));
2980 }
2981
2982 inline std::string Symbol::href() const {
2983 const auto symbol_id = this->id();
2984 if (symbol_id.empty()) {
2985 throw std::logic_error("SVG symbol must have an id before it can be referenced");
2986 }
2987 return "#" + symbol_id;
2988 }
2989
2991 Element::BoundingBox bbox(NAN, NAN, NAN, NAN);
2992 this->Element::get_bbox(bbox, false);
2993 return bbox;
2994 }
2995
2996 inline Use Symbol::use(double x, double y) const {
2997 return Use(this->href(), x, y);
2998 }
2999
3000 inline Use Symbol::use(double x, double y, double width, double height) const {
3001 return Use(this->href(), x, y, width, height);
3002 }
3003
3004 class Path : public Shape {
3005 public:
3006 static constexpr ElementKind static_kind = ElementKind::Path;
3007 using Shape::Shape;
3008 ElementKind kind() const override { return static_kind; }
3009
3010 template<typename T>
3011 inline void start(T x, T y) {
3015 this->set_attr("d", "M " + std::to_string(x) + " " + std::to_string(y));
3016 this->x_start = x;
3017 this->y_start = y;
3018 }
3019
3020 template<typename T>
3021 inline void line_to(T x, T y) {
3027 if (!this->has_attr("d"))
3028 start(x, y);
3029 else
3030 this->mutable_attrs()["d"] += " L " + std::to_string(x) +
3031 " " + std::to_string(y);
3032 }
3033
3034 inline void line_to(std::pair<double, double> coord) {
3035 this->line_to(coord.first, coord.second);
3036 }
3037
3038 inline void to_origin() {
3040 this->line_to(x_start, y_start);
3041 }
3042
3043 protected:
3044 std::unique_ptr<Element> clone_element_impl() const override {
3045 return clone_as<Path>();
3046 }
3047
3048 private:
3049 double x_start = 0;
3050 double y_start = 0;
3051 };
3052
3054 class Text : public Element {
3055 public:
3056 static constexpr ElementKind static_kind = ElementKind::Text;
3057 Text() = default;
3058 using Element::Element;
3059 ElementKind kind() const override { return static_kind; }
3060
3061 Text(double x, double y, std::string _content) {
3062 set_attr("x", to_string(x));
3063 set_attr("y", to_string(y));
3064 content = _content;
3065 }
3066
3067 Text(std::pair<double, double> xy, std::string _content) :
3068 Text(xy.first, xy.second, _content) {};
3069
3071 BoundingBox get_bbox() const override;
3072
3073 protected:
3074 std::string content;
3075 std::string svg_to_string(const size_t) const override;
3076 std::unique_ptr<Element> clone_element_impl() const override {
3077 return clone_as<Text>();
3078 }
3079 };
3080
3082 class Title : public Element {
3083 public:
3085 static constexpr ElementKind static_kind = ElementKind::Title;
3086 Title() = default;
3088 explicit Title(const char* _content) : content(_content) {}
3090 explicit Title(std::string _content) : content(std::move(_content)) {}
3091 ElementKind kind() const override { return static_kind; }
3092
3093 protected:
3095 std::string content;
3097 std::string svg_to_string(const size_t) const override;
3098 std::unique_ptr<Element> clone_element_impl() const override {
3099 return clone_as<Title>();
3100 }
3101 };
3102
3103 class Group : public Element {
3104 public:
3105 static constexpr ElementKind static_kind = ElementKind::Group;
3106 using Element::Element;
3107 ElementKind kind() const override { return static_kind; }
3108
3109 protected:
3110 std::unique_ptr<Element> clone_element_impl() const override { return clone_as<Group>(); }
3111 };
3112
3114 using G = Group;
3115
3116 class Line : public Shape {
3117 public:
3118 static constexpr ElementKind static_kind = ElementKind::Line;
3119 Line() = default;
3120 using Shape::Shape;
3121 ElementKind kind() const override { return static_kind; }
3122
3123 Line(double x1, double x2, double y1, double y2) : Shape({
3124 { "x1", to_string(x1) },
3125 { "x2", to_string(x2) },
3126 { "y1", to_string(y1) },
3127 { "y2", to_string(y2) }
3128 }) {};
3129
3130 Line(Point x, Point y) : Line(x.first, y.first, x.second, y.second) {};
3131
3132 virtual double x() const override { return x1() + (x2() - x1()) / 2; }
3133 virtual double y() const override { return y1() + (y2() - y1()) / 2; }
3134 double x1() const { return this->find_numeric("x1"); }
3135 double x2() const { return this->find_numeric("x2"); }
3136 double y1() const { return this->find_numeric("y1"); }
3137 double y2() const { return this->find_numeric("y2"); }
3138
3139 double width() const override { return std::abs(x2() - x1()); }
3140 double height() const override { return std::abs(y2() - y1()); }
3141 double length() const { return std::sqrt(pow(width(), 2) + pow(height(), 2)); }
3142 double slope() const { return (y2() - y1()) / (x2() - x1()); }
3143 double angle() const { return atan(this->slope()) * RAD_TO_DEG; }
3144
3145 std::pair<double, double> along(double percent) const;
3146
3147 protected:
3148 Element::BoundingBox get_bbox() const override;
3149 std::unique_ptr<Element> clone_element_impl() const override { return clone_as<Line>(); }
3150 };
3151
3152 class Rect : public Shape {
3153 public:
3154 static constexpr ElementKind static_kind = ElementKind::Rect;
3155 Rect() = default;
3156 using Shape::Shape;
3157 ElementKind kind() const override { return static_kind; }
3158
3159 Rect(
3160 double x, double y, double width, double height) :
3161 Shape({
3162 { "x", to_string(x) },
3163 { "y", to_string(y) },
3164 { "width", to_string(width) },
3165 { "height", to_string(height) }
3166 }) {};
3167
3168 Element::BoundingBox get_bbox() const override;
3169
3170 protected:
3171 std::unique_ptr<Element> clone_element_impl() const override { return clone_as<Rect>(); }
3172 };
3173
3174 class Circle : public Shape {
3175 public:
3176 static constexpr ElementKind static_kind = ElementKind::Circle;
3177 Circle() = default;
3178 using Shape::Shape;
3179 ElementKind kind() const override { return static_kind; }
3180
3181 Circle(double cx, double cy, double radius) :
3182 Shape({
3183 { "cx", to_string(cx) },
3184 { "cy", to_string(cy) },
3185 { "r", to_string(radius) }
3186 }) {
3187 };
3188
3189 Circle(std::pair<double, double> xy, double radius) : Circle(xy.first, xy.second, radius) {};
3190 double radius() const { return this->find_numeric("r"); }
3191 virtual double x() const override { return this->find_numeric("cx"); }
3192 virtual double y() const override { return this->find_numeric("cy"); }
3193 virtual double width() const override { return this->radius() * 2; }
3194 virtual double height() const override { return this->width(); }
3195 Element::BoundingBox get_bbox() const override;
3196
3197 protected:
3198 std::unique_ptr<Element> clone_element_impl() const override { return clone_as<Circle>(); }
3199 };
3200
3201 class Polygon : public Element {
3202 public:
3203 static constexpr ElementKind static_kind = ElementKind::Polygon;
3204 Polygon() = default;
3205 using Element::Element;
3206 ElementKind kind() const override { return static_kind; }
3207
3208 Polygon(const std::vector<Point>& points) {
3209 // Quick and dirty
3210 std::string point_str;
3211 for (auto& pt : points)
3212 point_str += to_string(pt) + " ";
3213 this->set_attr("points", point_str);
3214 };
3215
3216 protected:
3217 std::unique_ptr<Element> clone_element_impl() const override { return clone_as<Polygon>(); }
3218 };
3219
3221 if (content.empty()) {
3222 const auto x = get_attr<double>("x", 0) + get_attr<double>("dx", 0);
3223 const auto y = get_attr<double>("y", 0) + get_attr<double>("dy", 0);
3224 return include_stroke_width({ x, x, y, y });
3225 }
3226
3227 double font_size = get_attr<double>("font-size", 16);
3228 if (!(font_size > 0)) font_size = 16;
3229
3230 const double x = get_attr<double>("x", 0) + get_attr<double>("dx", 0);
3231 const double y = get_attr<double>("y", 0) + get_attr<double>("dy", 0);
3232 const auto metrics = detail::TextEstimator(content, font_size, get_attr("font-weight")).estimate();
3233 const double width = metrics.width;
3234 const double height = metrics.height;
3235
3236 double x1 = x;
3237 const auto anchor = this->get_attr("text-anchor", "start");
3238 if (anchor == "middle") {
3239 x1 = x - width / 2;
3240 } else if (anchor == "end") {
3241 x1 = x - width;
3242 }
3243
3244 const auto baseline = this->get_attr(
3245 "dominant-baseline",
3246 this->get_attr("alignment-baseline", "alphabetic"));
3247 double y1 = y - metrics.ascent;
3248 if (baseline == "middle" || baseline == "central" || baseline == "mathematical") {
3249 y1 = y - height / 2;
3250 } else if (baseline == "hanging" || baseline == "text-before-edge" || baseline == "text-top") {
3251 y1 = y;
3252 } else if (baseline == "text-after-edge" || baseline == "text-bottom") {
3253 y1 = y - height;
3254 }
3255
3256 return include_stroke_width({ x1, x1 + width, y1, y1 + height });
3257 }
3258
3259 inline Element::BoundingBox Line::get_bbox() const {
3260 return include_stroke_width({ std::min(x1(), x2()), std::max(x1(), x2()),
3261 std::min(y1(), y2()), std::max(y1(), y2()) });
3262 }
3263
3264 inline Element::BoundingBox Rect::get_bbox() const {
3265 double x = this->x(), y = this->y(),
3266 width = this->width(), height = this->height();
3267 return include_stroke_width({ x, x + width, y, y + height });
3268 }
3269
3270 inline Element::BoundingBox Circle::get_bbox() const {
3271 double x = this->x(), y = this->y(), radius = this->radius();
3272
3273 return include_stroke_width({
3274 x - radius,
3275 x + radius,
3276 y - radius,
3277 y + radius
3278 });
3279 }
3280
3281 inline std::pair<double, double> Line::along(double percent) const {
3286 double x_pos, y_pos;
3287
3288 if (x1() != x2()) {
3289 double length = percent * this->length();
3290 double discrim = std::sqrt(4 * pow(length, 2) * (1 / (1 + pow(slope(), 2))));
3291
3292 double x_a = (2 * x1() + discrim) / 2;
3293 double x_b = (2 * x1() - discrim) / 2;
3294 x_pos = x_a;
3295
3296 if ((x_a > x1() && x_a > x2()) || (x_a < x1() && x_a < x2()))
3297 x_pos = x_b;
3298
3299 y_pos = slope() * (x_pos - x1()) + y1();
3300 }
3301 else { // Edge case:: Completely vertical lines
3302 x_pos = x1();
3303
3304 if (y1() > y2()) // Downward pointing
3305 y_pos = y1() - percent * this->length();
3306 else
3307 y_pos = y1() + percent * this->length();
3308 }
3309
3310 return std::make_pair(x_pos, y_pos);
3311 }
3312
3313 inline std::string Element::svg_to_string(const size_t indent_level) const {
3319 auto indent = std::string(indent_level, '\t');
3320 std::string ret = indent + "<" + tag();
3321
3322 // Set attributes
3323 for (auto& pair: attrs())
3324 ret += " " + pair.first + "=" + "\"" + escape_xml(pair.second) + "\"";
3325
3326 if (!this->children.empty()) {
3327 ret += ">\n";
3328
3329 // Recursively get strings for child elements
3330 for (auto& child : children) {
3331 // Avoid adding empty strings
3332 auto str = child->svg_to_string(indent_level + 1);
3333 if (str.size()) ret += str +"\n";
3334 }
3335
3336 return ret += indent + "</" + tag() + ">";
3337 }
3338
3339 return ret += " />";
3340 }
3341
3342 namespace detail {
3344 inline std::string text_content_element_to_string(const Element& element,
3345 const std::string& tag,
3346 const std::string& content,
3347 const size_t indent_level) {
3348 auto indent = std::string(indent_level, '\t');
3349 std::string ret = indent + "<" + tag;
3350 for (auto& pair: element.attrs())
3351 ret += " " + pair.first + "=" + "\"" + escape_xml(pair.second) + "\"";
3352 return ret += ">" + escape_xml(content) + "</" + tag + ">";
3353 }
3354 }
3355
3356 inline std::string to_string(const std::map<std::string, AttributeMap>& css, const size_t indent_level) {
3358 auto indent = std::string(indent_level, '\t'), ret = std::string();
3359 for (auto& selector : css) {
3360 // Loop over each selector's attribute/value pairs
3361 ret += indent + "\t\t" + selector.first + " {\n";
3362 for (auto& attr : selector.second.attrs())
3363 ret += indent + "\t\t\t" + attr.first + ": " + attr.second + ";\n";
3364 ret += indent + "\t\t" + "}\n";
3365 }
3366 return ret;
3367 }
3368
3369 inline std::string SVG::Style::svg_to_string(const size_t indent_level) const {
3371 auto indent = std::string(indent_level, '\t');
3372
3373 if (!this->css.empty() || !this->media_queries.empty() || !this->keyframes.empty()) {
3374 std::string ret = indent + "<style type=\"text/css\">\n" +
3375 indent + "\t<![CDATA[\n";
3376
3377 // Begin CSS stylesheet
3378 ret += to_string(this->css, indent_level);
3379
3380 // Media queries
3381 for (auto& media : this->media_queries) {
3382 ret += indent + "\t\t@media " + media.first + " {\n" +
3383 to_string(media.second, indent_level + 1) +
3384 indent + "\t\t" + "}\n";
3385 }
3386
3387 // Animation frames
3388 for (auto& anim : this->keyframes) {
3389 ret += indent + "\t\t@keyframes " + anim.first + " {\n" +
3390 to_string(anim.second, indent_level + 1) +
3391 indent + "\t\t" + "}\n";
3392 }
3393
3394 ret += indent + "\t]]>\n";
3395 return ret + indent + "</style>";
3396 }
3397
3398 return "";
3399 }
3400
3401 inline std::unique_ptr<Element> SVG::Style::clone_element_impl() const {
3402 return clone_as<Style>();
3403 }
3404
3405 inline std::string Text::svg_to_string(const size_t indent_level) const {
3406 return detail::text_content_element_to_string(*this, tag_name(this->kind()), this->content, indent_level);
3407 }
3408
3409 inline std::string Title::svg_to_string(const size_t indent_level) const {
3410 return detail::text_content_element_to_string(*this, tag_name(this->kind()), this->content, indent_level);
3411 }
3412
3414 layout_bbox_ = bbox;
3415 has_layout_bbox_ = true;
3416 return *this;
3417 }
3418
3419 inline Element& Element::bbox_padding(const Margins& padding) {
3420 layout_bbox_padding_ = padding;
3421 return *this;
3422 }
3423
3424 inline Element& Element::bbox_padding(double padding) {
3425 return bbox_padding({ padding, padding, padding, padding });
3426 }
3427
3429 has_layout_bbox_ = false;
3430 layout_bbox_ = BoundingBox(NAN, NAN, NAN, NAN);
3431 return *this;
3432 }
3433
3435 return measured_layout_bbox();
3436 }
3437
3438 inline Element::BoundingBox Element::measured_layout_bbox() const {
3439 if (has_layout_bbox_) return layout_bbox_;
3440 auto bbox = this->get_bbox();
3441 if (std::isnan(bbox.x1) || std::isnan(bbox.x2) ||
3442 std::isnan(bbox.y1) || std::isnan(bbox.y2)) {
3443 return bbox;
3444 }
3445 return {
3446 bbox.x1 - layout_bbox_padding_.x1,
3447 bbox.x2 + layout_bbox_padding_.x2,
3448 bbox.y1 - layout_bbox_padding_.y1,
3449 bbox.y2 + layout_bbox_padding_.y2
3450 };
3451 }
3452
3453 inline detail::AffineTransform Element::transform_for(
3454 const Element* element,
3455 const detail::AffineTransform& parent_transform) {
3456 if (element->has_attr("transform")) {
3457 return detail::multiply(parent_transform,
3458 detail::parse_supported_transform(element->get_attr("transform")));
3459 }
3460 return parent_transform;
3461 }
3462
3463 namespace detail {
3464 inline unsigned alignment_count_bits(Alignment value) {
3465 unsigned count = 0;
3466 while (value) {
3467 count += value & 1u;
3468 value >>= 1u;
3469 }
3470 return count;
3471 }
3472
3473 inline bool bbox_is_measured(const Element::BoundingBox& bbox) {
3474 return !(std::isnan(bbox.x1) || std::isnan(bbox.x2) ||
3475 std::isnan(bbox.y1) || std::isnan(bbox.y2));
3476 }
3477
3478 inline double visible_stroke_width(const Element& element) {
3479 const auto stroke = element.get_attr("stroke");
3480 if (stroke.empty() || stroke == "none") return 0;
3481
3482 const auto width = element.get_attr<double>("stroke-width", 1);
3483 return width > 0 ? width : 0;
3484 }
3485
3486 inline bool renders_in_place(const Element* element) {
3487 for (auto* current = element; current; current = current->parent()) {
3488 const auto kind = current->kind();
3489 if (kind == ElementKind::Defs || kind == ElementKind::Style ||
3490 kind == ElementKind::Symbol ||
3491 kind == ElementKind::LinearGradient ||
3492 kind == ElementKind::RadialGradient ||
3493 kind == ElementKind::Stop) {
3494 return false;
3495 }
3496 }
3497 return true;
3498 }
3499
3500 inline double bbox_center_x(const Element::BoundingBox& bbox) {
3501 return bbox.x1 + (bbox.x2 - bbox.x1) / 2;
3502 }
3503
3504 inline double bbox_center_y(const Element::BoundingBox& bbox) {
3505 return bbox.y1 + (bbox.y2 - bbox.y1) / 2;
3506 }
3507
3508 inline double bbox_anchor_x(const Element::BoundingBox& bbox, Anchor anchor) {
3509 if (anchor == Anchor::Start) return bbox.x1;
3510 if (anchor == Anchor::Center) return bbox_center_x(bbox);
3511 if (anchor == Anchor::End) return bbox.x2;
3512 throw std::invalid_argument("rotate_about_bbox requires a valid x anchor");
3513 }
3514
3515 inline double bbox_anchor_y(const Element::BoundingBox& bbox, Anchor anchor) {
3516 if (anchor == Anchor::Start) return bbox.y1;
3517 if (anchor == Anchor::Center) return bbox_center_y(bbox);
3518 if (anchor == Anchor::End) return bbox.y2;
3519 throw std::invalid_argument("rotate_about_bbox requires a valid y anchor");
3520 }
3521 }
3522
3523 inline Element::BoundingBox Element::include_stroke_width(const BoundingBox& bbox) const {
3524 if (!detail::bbox_is_measured(bbox)) return bbox;
3525 const auto outset = detail::visible_stroke_width(*this) / 2;
3526 return { bbox.x1 - outset, bbox.x2 + outset, bbox.y1 - outset, bbox.y2 + outset };
3527 }
3528
3530 Anchor x_anchor,
3531 Anchor y_anchor) {
3532 validate_appendable();
3533 if (!owner_) {
3534 throw std::logic_error("rotate_about_bbox requires an owning element transform list");
3535 }
3536 const auto bbox = owner_->layout_bbox();
3537 if (!detail::bbox_is_measured(bbox)) {
3538 throw std::logic_error("rotate_about_bbox requires measured layout bounds");
3539 }
3540 return rotate(degrees,
3541 detail::bbox_anchor_x(bbox, x_anchor),
3542 detail::bbox_anchor_y(bbox, y_anchor));
3543 }
3544
3546 const auto href = this->get_attr("href", this->get_attr("xlink:href"));
3547 if (href.empty() || href[0] != '#') return { NAN, NAN, NAN, NAN };
3548
3549 const auto* root = this->owner_svg();
3550 if (!root) return { NAN, NAN, NAN, NAN };
3551
3552 const auto* referenced = root->get_element_by_id(href.substr(1));
3553 if (!referenced || referenced == this) return { NAN, NAN, NAN, NAN };
3554
3555 Element::BoundingBox bbox = referenced->layout_bbox();
3556 if (!detail::bbox_is_measured(bbox)) return bbox;
3557
3558 const auto x = this->get_attr<double>("x", 0);
3559 const auto y = this->get_attr<double>("y", 0);
3560 const auto width = this->get_attr<double>("width", NAN);
3561 const auto height = this->get_attr<double>("height", NAN);
3562 const auto viewbox = detail::parse_transform_args(referenced->get_attr("viewBox"));
3563 if (!std::isnan(width) && !std::isnan(height) && viewbox.size() == 4 &&
3564 viewbox[2] != 0 && viewbox[3] != 0) {
3565 const auto sx = width / viewbox[2];
3566 const auto sy = height / viewbox[3];
3567 bbox = {
3568 x + (bbox.x1 - viewbox[0]) * sx,
3569 x + (bbox.x2 - viewbox[0]) * sx,
3570 y + (bbox.y1 - viewbox[1]) * sy,
3571 y + (bbox.y2 - viewbox[1]) * sy
3572 };
3573 return include_stroke_width(bbox);
3574 }
3575
3576 bbox = { bbox.x1 + x, bbox.x2 + x, bbox.y1 + y, bbox.y2 + y };
3577 return include_stroke_width(bbox);
3578 }
3579
3581 const double x = this->get_attr<double>("x", 0);
3582 const double y = this->get_attr<double>("y", 0);
3583 const double width = this->get_attr<double>("width", NAN);
3584 const double height = this->get_attr<double>("height", NAN);
3585
3586 if (!std::isnan(width) && !std::isnan(height)) {
3587 return { x, x + width, y, y + height };
3588 }
3589
3590 Element::BoundingBox bbox(NAN, NAN, NAN, NAN);
3591 this->Element::get_bbox(bbox);
3592 if (!detail::bbox_is_measured(bbox)) return bbox;
3593 return { bbox.x1 + x, bbox.x2 + x, bbox.y1 + y, bbox.y2 + y };
3594 }
3595
3596 inline Element& Element::snap_to(const Element& target,
3597 RelativeAlignment relative,
3598 Point offset) {
3599 return snap_to(target, static_cast<Alignment>(relative), offset);
3600 }
3601
3602 inline Element& Element::snap_to(const Element& target,
3603 Alignment alignment,
3604 Point offset) {
3605 const Alignment relative_mask =
3606 static_cast<Alignment>(RelativeAlignment::Left) |
3607 static_cast<Alignment>(RelativeAlignment::Top) |
3608 static_cast<Alignment>(RelativeAlignment::Right) |
3609 static_cast<Alignment>(RelativeAlignment::Bottom);
3610 const Alignment anchor_mask =
3611 static_cast<Alignment>(Anchor::Start) |
3612 static_cast<Alignment>(Anchor::Center) |
3613 static_cast<Alignment>(Anchor::End);
3614
3615 const Alignment relative = alignment & relative_mask;
3616 Alignment anchor = alignment & anchor_mask;
3617 if (detail::alignment_count_bits(relative) != 1) {
3618 throw std::invalid_argument("snap_to requires exactly one relative alignment");
3619 }
3620 if (anchor == 0) {
3621 anchor = static_cast<Alignment>(Anchor::Center);
3622 } else if (detail::alignment_count_bits(anchor) != 1) {
3623 throw std::invalid_argument("snap_to requires at most one anchor");
3624 }
3625 if ((alignment & ~(relative_mask | anchor_mask)) != 0) {
3626 throw std::invalid_argument("snap_to received unknown alignment bits");
3627 }
3628
3629 const auto source_box = this->layout_bbox();
3630 const auto target_box = target.layout_bbox();
3631 if (!detail::bbox_is_measured(source_box) || !detail::bbox_is_measured(target_box)) {
3632 throw std::logic_error("snap_to requires measured layout bounds");
3633 }
3634
3635 double dx = 0;
3636 double dy = 0;
3637 if (relative == static_cast<Alignment>(RelativeAlignment::Left)) {
3638 dx = target_box.x1 - source_box.x2;
3639 } else if (relative == static_cast<Alignment>(RelativeAlignment::Right)) {
3640 dx = target_box.x2 - source_box.x1;
3641 } else if (relative == static_cast<Alignment>(RelativeAlignment::Top)) {
3642 dy = target_box.y1 - source_box.y2;
3643 } else if (relative == static_cast<Alignment>(RelativeAlignment::Bottom)) {
3644 dy = target_box.y2 - source_box.y1;
3645 }
3646
3647 if (relative == static_cast<Alignment>(RelativeAlignment::Left) ||
3648 relative == static_cast<Alignment>(RelativeAlignment::Right)) {
3649 if (anchor == static_cast<Alignment>(Anchor::Start)) {
3650 dy = target_box.y1 - source_box.y1;
3651 } else if (anchor == static_cast<Alignment>(Anchor::Center)) {
3652 dy = detail::bbox_center_y(target_box) - detail::bbox_center_y(source_box);
3653 } else {
3654 dy = target_box.y2 - source_box.y2;
3655 }
3656 } else {
3657 if (anchor == static_cast<Alignment>(Anchor::Start)) {
3658 dx = target_box.x1 - source_box.x1;
3659 } else if (anchor == static_cast<Alignment>(Anchor::Center)) {
3660 dx = detail::bbox_center_x(target_box) - detail::bbox_center_x(source_box);
3661 } else {
3662 dx = target_box.x2 - source_box.x2;
3663 }
3664 }
3665
3666 this->transform().translate(dx + offset.first, dy + offset.second);
3667 return *this;
3668 }
3669
3670 inline Element& Element::align_to(const Element& target,
3671 Axis axis,
3672 Point offset) {
3673 return align_to(target, axis, Anchor::Center, offset);
3674 }
3675
3676 inline Element& Element::align_to(const Element& target,
3677 Axis axis,
3678 Anchor anchor,
3679 Point offset) {
3680 if (axis != Axis::X && axis != Axis::Y) {
3681 throw std::invalid_argument("align_to requires a valid axis");
3682 }
3683 if (anchor != Anchor::Start && anchor != Anchor::Center && anchor != Anchor::End) {
3684 throw std::invalid_argument("align_to requires a valid anchor");
3685 }
3686
3687 const auto source_box = this->layout_bbox();
3688 const auto target_box = target.layout_bbox();
3689 if (!detail::bbox_is_measured(source_box) || !detail::bbox_is_measured(target_box)) {
3690 throw std::logic_error("align_to requires measured layout bounds");
3691 }
3692
3693 double dx = offset.first;
3694 double dy = offset.second;
3695 if (axis == Axis::X) {
3696 if (anchor == Anchor::Start) {
3697 dx += target_box.x1 - source_box.x1;
3698 } else if (anchor == Anchor::Center) {
3699 dx += detail::bbox_center_x(target_box) - detail::bbox_center_x(source_box);
3700 } else {
3701 dx += target_box.x2 - source_box.x2;
3702 }
3703 } else {
3704 if (anchor == Anchor::Start) {
3705 dy += target_box.y1 - source_box.y1;
3706 } else if (anchor == Anchor::Center) {
3707 dy += detail::bbox_center_y(target_box) - detail::bbox_center_y(source_box);
3708 } else {
3709 dy += target_box.y2 - source_box.y2;
3710 }
3711 }
3712
3713 this->transform().translate(dx, dy);
3714 return *this;
3715 }
3716
3717 inline void Element::autoscale_nested_svgs(const AutoscaleOptions& options, bool responsive) {
3718 for (auto& child : this->children) {
3719 if (child->kind() == SVG::static_kind) {
3720 auto* svg_child = static_cast<SVG*>(child.get());
3721 if (responsive) {
3722 svg_child->responsive_autoscale(options);
3723 } else {
3724 svg_child->autoscale(options);
3725 }
3726 } else {
3727 child->autoscale_nested_svgs(options, responsive);
3728 }
3729 }
3730 }
3731
3732 inline Element::BoundingBox Element::get_autoscale_bbox() const {
3733 Element::BoundingBox bbox =
3734 this->kind() == SVG::static_kind && !this->has_layout_bbox()
3735 ? Element::BoundingBox(NAN, NAN, NAN, NAN)
3736 : this->measured_layout_bbox();
3737 this->get_bbox(bbox); // Compute the transform-aware descendant bounds
3738 return bbox;
3739 }
3740
3741 inline void Element::set_viewbox_from_bbox(const BoundingBox& bbox, const Margins& margins) {
3742 double width = bbox.x2 - bbox.x1,
3743 height = bbox.y2 - bbox.y1;
3744
3745 width += margins.x1 + margins.x2;
3746 height += margins.y1 + margins.y2;
3747 double x1 = bbox.x1 - margins.x1,
3748 y1 = bbox.y1 - margins.y1;
3749
3750 std::stringstream viewbox;
3751 viewbox << std::fixed << std::setprecision(1)
3752 << x1 << " " // min-x
3753 << y1 << " " // min-y
3754 << width << " "
3755 << height;
3756 this->set_attr("viewBox", viewbox.str());
3757 }
3758
3759 inline void Element::autoscale(const double margin) {
3761 AutoscaleOptions nested_options(NO_MARGINS);
3762 this->autoscale_nested_svgs(nested_options, false);
3763 auto bbox = this->get_autoscale_bbox();
3764 double width = bbox.x2 - bbox.x1,
3765 height = bbox.y2 - bbox.y1;
3766
3767 AutoscaleOptions options({
3768 width * margin, width * margin,
3769 height * margin, height * margin
3770 }, false);
3771 this->autoscale(options);
3772 }
3773
3774 inline void Element::autoscale(const Margins& margins) {
3775 this->autoscale(AutoscaleOptions(margins));
3776 }
3777
3778 inline void Element::autoscale(const AutoscaleOptions& options) {
3784 if (options.autoscale_nested_svgs) {
3785 this->autoscale_nested_svgs(options, false);
3786 }
3787 auto bbox = this->get_autoscale_bbox();
3788 double width = bbox.x2 - bbox.x1 + options.margins.x1 + options.margins.x2,
3789 height = bbox.y2 - bbox.y1 + options.margins.y1 + options.margins.y2;
3790
3791 this->set_attr("width", width)
3792 .set_attr("height", height);
3793
3794 this->set_viewbox_from_bbox(bbox, options.margins);
3795 }
3796
3797 inline void Element::responsive_autoscale(const double margin) {
3798 AutoscaleOptions nested_options(NO_MARGINS);
3799 this->autoscale_nested_svgs(nested_options, true);
3800 auto bbox = this->get_autoscale_bbox();
3801 double width = bbox.x2 - bbox.x1,
3802 height = bbox.y2 - bbox.y1;
3803
3805 width * margin, width * margin,
3806 height * margin, height * margin
3807 }, false));
3808 }
3809
3810 inline void Element::responsive_autoscale(const Margins& margins) {
3811 this->responsive_autoscale(AutoscaleOptions(margins));
3812 }
3813
3815 if (options.autoscale_nested_svgs) {
3816 this->autoscale_nested_svgs(options, true);
3817 }
3818 this->set_viewbox_from_bbox(this->get_autoscale_bbox(), options.margins);
3819 }
3820
3821 inline void Element::get_bbox(Element::BoundingBox& box, bool visible_only) const {
3823 auto elements = this->descendants(TraversalOptions(false));
3824 for (auto element = elements.begin(); element != elements.end(); ++element) {
3825 const auto* current = *element;
3826 if (visible_only && !detail::renders_in_place(current)) {
3827 continue;
3828 }
3829 auto current_box = current->measured_layout_bbox();
3830 if (!detail::bbox_is_measured(current_box)) {
3831 continue;
3832 }
3833
3834 const auto& transform = element.transform();
3835 const auto p1 = transform.apply({ current_box.x1, current_box.y1 });
3836 const auto p2 = transform.apply({ current_box.x2, current_box.y1 });
3837 const auto p3 = transform.apply({ current_box.x2, current_box.y2 });
3838 const auto p4 = transform.apply({ current_box.x1, current_box.y2 });
3839 Element::BoundingBox transformed_box(
3840 std::min(std::min(p1.first, p2.first), std::min(p3.first, p4.first)),
3841 std::max(std::max(p1.first, p2.first), std::max(p3.first, p4.first)),
3842 std::min(std::min(p1.second, p2.second), std::min(p3.second, p4.second)),
3843 std::max(std::max(p1.second, p2.second), std::max(p3.second, p4.second)));
3844 box = transformed_box + box;
3845 }
3846 }
3847
3848 inline Element::ChildMap Element::get_children() {
3850 Element::ChildMap child_map;
3851 for (auto* child : this->descendants())
3852 child_map[child->tag()].push_back(child);
3853 return child_map;
3854 }
3855
3856 inline Element::ConstChildMap Element::get_children() const {
3858 Element::ConstChildMap child_map;
3859 for (const auto* child : this->descendants())
3860 child_map[child->tag()].push_back(child);
3861 return child_map;
3862 }
3863
3864 inline std::vector<Element*> Element::get_children_helper() {
3866 std::vector<Element*> ret;
3867 for (auto* child : this->descendants()) ret.push_back(child);
3868
3869 return ret;
3870 };
3871
3872 inline std::vector<const Element*> Element::get_children_helper() const {
3874 std::vector<const Element*> ret;
3875 for (const auto* child : this->descendants()) ret.push_back(child);
3876
3877 return ret;
3878 };
3879
3880 inline SVG merge(SVG& left, SVG& right, const Margins& margins) {
3882 SVG ret;
3883
3884 // Move items
3885 ret << std::move(left) << std::move(right);
3886
3887 // Set bounding box of individual pieces
3888 for (auto& svg_child: ret.get_immediate_children<SVG>())
3889 svg_child->autoscale(margins);
3890
3891 // Set x position for child SVG elements, and compute width/height for this
3892 double x = 0, height = 0;
3893 for (auto& svg_child: ret.get_immediate_children<SVG>()) {
3894 svg_child->set_attr("x", x).set_attr("y", 0);
3895 x += svg_child->width();
3896 height = std::max(height, svg_child->height());
3897 }
3898
3899 ret.set_attr("width", x).set_attr("height", height);
3900 return ret;
3901 }
3902
3903 inline std::vector<Point> bounding_polygon(std::vector<Shape*>& shapes) {
3904 /* Convert shapes into sets of points, aggregate them, and then calculate
3905 * convex hull for aggregate set
3906 */
3907 std::vector<Point> points;
3908 for (auto& shp : shapes) {
3909 auto temp_points = shp->points();
3910 std::move(temp_points.begin(), temp_points.end(), std::back_inserter(points));
3911 }
3912
3913 return util::convex_hull(points);
3914 }
3915
3916 inline SVG merge(std::vector<SVG>& frames, const double width, const int max_frame_width) {
3920 SVG root;
3921 double x = 0, y = 0, total_width = 0, total_height = 0;
3922 for (auto& frame : frames) {
3923 // Scale
3924 frame.autoscale();
3925 if (frame.width() > max_frame_width) {
3926 const double scale_factor = max_frame_width/frame.width();
3927 frame.set_attr("width", max_frame_width);
3928 frame.set_attr("height", frame.height() * scale_factor); // Scale height proportionally
3929 }
3930 }
3931
3932 // Move
3933 double current_height = 0;
3934 for (auto& frame : frames) {
3935 // Push to next row
3936 if ((x + frame.width()) > width) {
3937 total_width = std::max(total_width, x);
3938 x = 0;
3939 y += current_height;
3940 current_height = 0;
3941 }
3942
3943 frame.set_attr("x", x).set_attr("y", y);
3944 x += frame.width();
3945 current_height = std::max(current_height, frame.height());
3946 root << std::move(frame);
3947 }
3948
3949 total_height = y + current_height;
3950
3951 // Set viewbox
3952 root.set_attr("viewBox") << 0 << " " << 0 << " " << total_width << " " << total_height;
3953 root.set_attr("width", total_width).set_attr("height", total_height);
3954 return root;
3955 }
3956
3957 inline SVG frame_animate(std::vector<SVG>& frames, const double fps) {
3959 SVG root;
3960 const double duration = (double)frames.size() / fps; // [seconds]
3961 const double frame_step = 1.0 / fps; // duration of each frame [seconds]
3962 int current_frame = 0;
3963
3964 root.style("svg.animated").set_attr("animation-iteration-count", "infinite")
3965 .set_attr("animation-timing-function", "step-end")
3966 .set_attr("animation-duration", std::to_string(duration) + "s")
3967 .set_attr("opacity", 0);
3968
3969 // Move frames into new SVG
3970 for (auto& frame : frames) {
3971 std::string frame_id = "frame_" + std::to_string(current_frame);
3972 frame.set_attr("id", frame_id).set_attr("class", "animated");
3973 root.style("#" + frame_id).set_attr("animation-name",
3974 "anim_" + std::to_string(current_frame));
3975 current_frame++;
3976 root << std::move(frame);
3977 }
3978
3979 // Set animation frames
3980 for (size_t i = 0, ilen = frames.size(); i < ilen; i++) {
3981 auto& anim = root.keyframes("anim_" + std::to_string(i));
3982 double begin_pct = (double)i / frames.size(),
3983 end_pct = (double)(i + 1) / frames.size();
3984 anim["0%"].set_attr("opacity", 0);
3985 anim[std::to_string(begin_pct * 100) + "%"].set_attr("opacity", 1);
3986 anim[std::to_string(end_pct * 100) + "%"].set_attr("opacity", 0);
3987 }
3988
3989 // Scale and center child SVGs
3990 double width = 0, height = 0;
3991
3992 for (auto& child : root.get_immediate_children<SVG>()) {
3993 child->autoscale();
3994 width = std::max(width, child->width());
3995 height = std::max(height, child->height());
3996 }
3997
3998 root.set_attr("viewBox", "0 0 " + std::to_string(width) + " " + std::to_string(height));
3999
4000 // Center child SVGs
4001 for (auto& child : root.get_immediate_children<SVG>())
4002 child->set_attr("x", (width - child->width())/2).set_attr("y", (height - child->height())/2);
4003
4004 return root;
4005 }
4006}
Base class for anything that has attributes (e.g. SVG elements, CSS stylesheets)
Definition svg.hpp:1199
AttributeMap & set_attrs(const SVGAttrib &values)
Definition svg.hpp:1286
AttributeMap & set_attrs(std::initializer_list< std::pair< std::string, std::string > > values)
Definition svg.hpp:1277
std::enable_if< detail::is_numeric_attr_type< T >::value, T >::type get_attr(const std::string &key, T fallback) const
Definition svg.hpp:1261
Definition svg.hpp:3174
ElementKind kind() const override
Definition svg.hpp:3179
Ordered token list for managing the class attribute.
Definition svg.hpp:803
bool toggle(const std::string &token)
Definition svg.hpp:840
ClassList & remove(const std::string &token)
Definition svg.hpp:828
std::string str() const
Definition svg.hpp:863
std::vector< std::string > tokens() const
Definition svg.hpp:868
ClassList & set(const std::string &class_names)
Definition svg.hpp:851
bool contains(const std::string &token) const
Definition svg.hpp:810
ClassList & clear()
Definition svg.hpp:857
ClassList & add(const std::string &token)
Definition svg.hpp:817
Definition svg.hpp:1574
std::string classes(Keys... keys) const
Definition svg.hpp:1596
Classes(std::initializer_list< ClassSpec< T > > specs)
Definition svg.hpp:1577
std::string selector(Keys... keys) const
Definition svg.hpp:1585
Definition svg.hpp:181
Color mix(const Color &other, double amount) const
Definition svg.hpp:260
Color tint(double amount) const
Definition svg.hpp:268
static Color hsl(double hue, double saturation, double lightness)
Definition svg.hpp:216
static Color rgb(int red, int green, int blue)
Definition svg.hpp:184
static Color hex(std::string value)
Definition svg.hpp:189
static Color black()
Definition svg.hpp:283
static Color white()
Definition svg.hpp:278
Color shade(double amount) const
Definition svg.hpp:273
Definition svg.hpp:2347
RadialGradient & radial_gradient(std::string id)
Definition svg.hpp:2968
LinearGradient & linear_gradient(std::string id)
Definition svg.hpp:2961
ElementKind kind() const override
Definition svg.hpp:2351
Symbol * symbol(std::string id)
Definition svg.hpp:2975
Represents the top left and bottom right corners of a bounding rectangle.
Definition svg.hpp:1636
Definition svg.hpp:2238
Definition svg.hpp:2030
Definition svg.hpp:2224
Abstract base class for all SVG elements.
Definition svg.hpp:1631
std::vector< T * > get_immediate_children()
Definition svg.hpp:1758
std::vector< std::unique_ptr< Element > >::iterator ChildIterator
Definition svg.hpp:1871
Element & snap_to(const Element &target, RelativeAlignment relative, Point offset=Point(0, 0))
Definition svg.hpp:3596
std::vector< Element * > get_children_helper()
Definition svg.hpp:3864
DepthFirstRange depth_first()
Definition svg.hpp:2254
Element & operator<<(T &&node)
Definition svg.hpp:1723
virtual std::string tag() const
Definition svg.hpp:1885
virtual std::string tag()
Definition svg.hpp:1883
std::vector< const T * > get_children() const
Definition svg.hpp:1745
bool has_layout_bbox() const
Definition svg.hpp:1809
Element * get_element_by_id(const std::string &id)
Definition svg.hpp:1963
T * add_child(Args &&... args)
Definition svg.hpp:1715
Element & layout_bbox(const BoundingBox &bbox)
Definition svg.hpp:3413
Element & align_to(const Element &target, Axis axis, Point offset=Point(0, 0))
Definition svg.hpp:3670
Element & clear_layout_bbox()
Definition svg.hpp:3428
std::vector< const T * > get_immediate_children() const
Definition svg.hpp:1770
std::vector< T * > get_children()
Definition svg.hpp:1732
void responsive_autoscale(const Margins &margins=DEFAULT_MARGINS)
Definition svg.hpp:3810
virtual ElementKind kind() const
Definition svg.hpp:1787
DepthFirstIterator begin()
Definition svg.hpp:2270
virtual BoundingBox get_bbox() const
Definition svg.hpp:2302
BoundingBox layout_bbox() const
Definition svg.hpp:3434
virtual std::string svg_to_string(const size_t indent_level) const
Definition svg.hpp:3313
std::vector< Element * > get_elements_by_class(const std::string &clsname)
Definition svg.hpp:1981
double find_numeric(const std::string &key) const
Definition svg.hpp:1928
Element & bbox_padding(const Margins &padding)
Definition svg.hpp:3419
std::unique_ptr< Element > clone_element() const
Definition svg.hpp:2009
DepthFirstRange descendants()
Definition svg.hpp:2286
Definition svg.hpp:3103
ElementKind kind() const override
Definition svg.hpp:3107
Definition svg.hpp:3116
std::pair< double, double > along(double percent) const
Definition svg.hpp:3281
ElementKind kind() const override
Definition svg.hpp:3121
Definition svg.hpp:2445
Definition svg.hpp:3004
void line_to(T x, T y)
Definition svg.hpp:3021
void start(T x, T y)
Definition svg.hpp:3011
void to_origin()
Definition svg.hpp:3038
ElementKind kind() const override
Definition svg.hpp:3008
Definition svg.hpp:3201
ElementKind kind() const override
Definition svg.hpp:3206
Definition svg.hpp:2500
Definition svg.hpp:3152
ElementKind kind() const override
Definition svg.hpp:3157
Definition svg.hpp:2622
SelectorProperties css
Definition svg.hpp:2627
std::map< std::string, SelectorProperties > media_queries
Definition svg.hpp:2628
std::string svg_to_string(const size_t) const override
Definition svg.hpp:3369
ElementKind kind() const override
Definition svg.hpp:2630
std::map< std::string, SelectorProperties > keyframes
Definition svg.hpp:2629
AttributeMap & media_style(const std::string &query, const std::string &key)
Definition svg.hpp:2694
SVG(const SVG &other)
Definition svg.hpp:2647
AttributeMap & style(const std::string &key)
Definition svg.hpp:2685
const T * get_element_by_id(const std::string &id) const
Definition svg.hpp:2776
SVG(SVGAttrib _attr={ { "xmlns", "http://www.w3.org/2000/svg" } })
Definition svg.hpp:2639
SVG clone() const
Definition svg.hpp:2680
T * get_element_by_id(const std::string &id)
Definition svg.hpp:2767
ElementKind kind() const override
Definition svg.hpp:2784
Variables< T > set_vars(std::initializer_list< VariableSpec< T > > specs)
Definition svg.hpp:2706
BoundingBox get_bbox() const override
Definition svg.hpp:3580
std::map< std::string, AttributeMap > & keyframes(const std::string &key)
Definition svg.hpp:2724
Defs * defs()
Definition svg.hpp:2734
Variables< T > set_vars(const std::string &selector, std::initializer_list< VariableSpec< T > > specs)
Definition svg.hpp:2712
Variables< T > set_vars(const std::string &query, const std::string &selector, std::initializer_list< VariableSpec< T > > specs)
Definition svg.hpp:2718
SVG & media_style(const std::string &query, const std::string &key, const Attrs &attrs)
Definition svg.hpp:2699
SVG & style(const std::string &key, const Attrs &attrs)
Definition svg.hpp:2688
Base class for any SVG elements that have a width and height.
Definition svg.hpp:2310
virtual std::vector< Point > points() const
Definition svg.hpp:2319
virtual double height() const
Definition svg.hpp:2338
virtual double width() const
Definition svg.hpp:2332
Definition svg.hpp:2556
Element::BoundingBox get_bbox() const override
Definition svg.hpp:2990
ElementKind kind() const override
Definition svg.hpp:2568
std::string href() const
Definition svg.hpp:2982
Use use(double x, double y) const
Definition svg.hpp:2996
Definition svg.hpp:3054
ElementKind kind() const override
Definition svg.hpp:3059
BoundingBox get_bbox() const override
Definition svg.hpp:3220
Definition svg.hpp:3082
std::string svg_to_string(const size_t) const override
Definition svg.hpp:3409
Title(const char *_content)
Definition svg.hpp:3088
ElementKind kind() const override
Definition svg.hpp:3091
static constexpr ElementKind static_kind
Definition svg.hpp:3085
std::string content
Definition svg.hpp:3095
Title(std::string _content)
Definition svg.hpp:3090
Ordered function list for managing the transform attribute.
Definition svg.hpp:938
TransformList & rotate_about_bbox(double degrees, Anchor x_anchor, Anchor y_anchor)
Definition svg.hpp:3529
TransformList & append(const std::string &transform)
Definition svg.hpp:947
TransformList & clear()
Definition svg.hpp:968
TransformList & set(const std::string &transform)
Definition svg.hpp:962
std::string str() const
Definition svg.hpp:1017
Definition svg.hpp:2581
ElementKind kind() const override
Definition svg.hpp:2607
Element::BoundingBox get_bbox() const override
Definition svg.hpp:3545
Use & xlink_href(std::string href)
Definition svg.hpp:2603
Definition svg.hpp:1467
std::string var(T key) const
Definition svg.hpp:1488
Variables & set(T key, const std::string &value)
Definition svg.hpp:1493
Variables(AttributeMap &target, std::initializer_list< VariableSpec< T > > specs)
Definition svg.hpp:1470
Variables & set(T key, const char *value)
Definition svg.hpp:1499
std::string format(const std::string &pattern, Keys... keys) const
Definition svg.hpp:1505
Main namespace for SVG for C++.
Definition svg.hpp:60
SVG frame_animate(std::vector< SVG > &frames, const double fps)
Definition svg.hpp:3957
std::string tag_name(ElementKind kind)
Definition svg.hpp:95
unsigned Alignment
Definition svg.hpp:170
SVG merge(SVG &left, SVG &right, const Margins &margins=DEFAULT_MARGINS)
Definition svg.hpp:3880
RelativeAlignment
Definition svg.hpp:149
ElementKind
Definition svg.hpp:74
std::string escape_xml(const std::string &text)
Definition svg.hpp:1168
std::map< std::string, AttributeMap > SelectorProperties
Definition svg.hpp:126
Anchor
Definition svg.hpp:157
Axis
Definition svg.hpp:164
std::string to_string(const double &value)
Definition svg.hpp:1150
Various utility and mathematical functions.
Definition svg.hpp:1201
AttrSetter & operator<<(const Color &value)
Definition svg.hpp:1208
Definition svg.hpp:129
Definition svg.hpp:137
bool autoscale_nested_svgs
Definition svg.hpp:145
Definition svg.hpp:1454
ClassSpec(T _key, std::string _css_class)
Definition svg.hpp:1461
T key
Definition svg.hpp:1456
std::string css_class
Definition svg.hpp:1458
Definition svg.hpp:1661
bool descend_into_nested_svgs
Definition svg.hpp:1666
Definition svg.hpp:118
Definition svg.hpp:1430
VariableSpec(T _key, std::string _css_name, std::string _value)
Definition svg.hpp:1445
T key
Definition svg.hpp:1432
std::string css_name
Definition svg.hpp:1434
std::string value
Definition svg.hpp:1438
VariableSpec(T _key, std::string _css_name)
Definition svg.hpp:1441
std::string text_content_element_to_string(const Element &element, const std::string &tag, const std::string &content, const size_t indent_level)
Definition svg.hpp:3344