init
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
cmake_minimum_required(VERSION 3.28)
|
||||
|
||||
# define CXX interface library
|
||||
|
||||
# define target & alias (for other projects consuming the "source-tree")
|
||||
add_library(iue-svg_svg INTERFACE)
|
||||
|
||||
# define sources of target
|
||||
target_sources(iue-svg_svg
|
||||
INTERFACE FILE_SET public_headers TYPE HEADERS BASE_DIRS ${PROJECT_SOURCE_DIR} FILES tags.hpp tree.hpp render.hpp)
|
||||
|
||||
# define installation step
|
||||
install(TARGETS iue-svg_svg EXPORT iue-svg-export
|
||||
FILE_SET public_headers DESTINATION "./")
|
||||
|
||||
# define test
|
||||
|
||||
if(BUILD_TESTING AND PROJECT_IS_TOP_LEVEL)
|
||||
add_executable(iue-svg_tree.test tree.test.cpp)
|
||||
target_link_libraries(iue-svg_tree.test iue-svg_svg)
|
||||
add_test(NAME iue-svg_tree.test COMMAND iue-svg_tree.test)
|
||||
|
||||
add_executable(iue-svg_tags.test tags.test.cpp)
|
||||
target_link_libraries(iue-svg_tags.test iue-svg_svg)
|
||||
add_test(NAME iue-svg_tags.test COMMAND iue-svg_tags.test)
|
||||
|
||||
add_executable(iue-svg_render.test render.test.cpp)
|
||||
target_link_libraries(iue-svg_render.test iue-svg_svg)
|
||||
add_test(NAME iue-svg_render.test COMMAND iue-svg_render.test)
|
||||
|
||||
endif()
|
||||
|
||||
# export targets for consuming cmake projects
|
||||
|
||||
# generate and install a .cmake file to allow dependend projects consuming the "install-tree" to import all targets
|
||||
install(EXPORT iue-svg-export DESTINATION "iue-svg" )
|
||||
|
||||
# allow dependend projects consuming the "build-tree" to use all targets
|
||||
export(EXPORT iue-svg-export)
|
||||
@@ -0,0 +1,157 @@
|
||||
/// @file
|
||||
/// @brief Educational implementation of a SVG-Writer
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "iue-svg/tags.hpp" // iue::svg:::XMLTag, iue::svg::svg
|
||||
#include "iue-svg/tree.hpp" // iue::svg::Tree
|
||||
|
||||
#include <cassert> // assert
|
||||
#include <filesystem> // std::filesystem::path
|
||||
#include <fstream> // std::ofstream
|
||||
#include <sstream> // std::stringstream
|
||||
#include <string> // std::string
|
||||
#include <vector> // std::vector
|
||||
|
||||
namespace iue::svg {
|
||||
|
||||
/// @brief Generate a SVG file from sequences of compatible primitives
|
||||
/// @return Enclosing bounding box (original unscaled coordinates)
|
||||
/// Imporant notes:
|
||||
/// 1. The outline of the svg will be the bounding box enclosing all occuring geometric primitives (based on coordinate
|
||||
/// based)
|
||||
/// - the detail of the extend of an object (textlength, fontsize, linewidth, ...) is **not** considered
|
||||
/// 2. The coordinates are **scaled/rotated/translated before serializing**
|
||||
/// - this is not so nice, but a scaling is required to improve the viewing experience for small coordinate spans
|
||||
/// 3. All primitives are rendered with black lines and no fill
|
||||
/// - the linewidth is choosen relative the the dimension of the enclosing bounding box
|
||||
/// - the font-size for text is relative to the linewith
|
||||
inline iue::svg::BBox render(std::filesystem::path filepath, const std::vector<iue::svg::BBox>& bboxes,
|
||||
const std::vector<iue::svg::Circle>& circles, const std::vector<iue::svg::Triangle>& triangles) {
|
||||
|
||||
using XMLTree = Tree<XMLTag>;
|
||||
|
||||
assert(bboxes.size() != 0 || circles.size() != 0 || triangles.size() != 0);
|
||||
|
||||
// calculate maximum extends
|
||||
Vec2d bbmin = {+std::numeric_limits<double>::max(), +std::numeric_limits<double>::max()};
|
||||
Vec2d bbmax = {-std::numeric_limits<double>::max(), -std::numeric_limits<double>::max()};
|
||||
|
||||
// update minmax from single coordinate
|
||||
auto minmax = [&bbmin, &bbmax](const Vec2d& p) {
|
||||
bbmin[0] = bbmin[0] < p[0] ? bbmin[0] : p[0];
|
||||
bbmin[1] = bbmin[1] < p[1] ? bbmin[1] : p[1];
|
||||
bbmax[0] = bbmax[0] > p[0] ? bbmax[0] : p[0];
|
||||
bbmax[1] = bbmax[1] > p[1] ? bbmax[1] : p[1];
|
||||
};
|
||||
|
||||
for (const auto& circle : circles) {
|
||||
const auto& [c, r] = circle;
|
||||
minmax({c[0] - r, c[1] - r});
|
||||
minmax({c[0] + r, c[1] + r});
|
||||
}
|
||||
|
||||
for (const auto& bbox : bboxes) {
|
||||
const auto& [min, max] = bbox;
|
||||
minmax(min);
|
||||
minmax(max);
|
||||
}
|
||||
|
||||
for (const auto& triangle : triangles) {
|
||||
for (const auto& p : triangle)
|
||||
minmax(p);
|
||||
}
|
||||
|
||||
// scaling
|
||||
double resolution;
|
||||
{
|
||||
auto const& [xmin, ymin] = bbmin;
|
||||
auto const& [xmax, ymax] = bbmax;
|
||||
double width = std::abs(xmax - xmin);
|
||||
double height = std::abs(ymax - ymin);
|
||||
resolution = std::max(width, height);
|
||||
}
|
||||
double unit = resolution / 800; // stroke referens is "thin" for a reference resolution
|
||||
double scale = 1 / unit;
|
||||
|
||||
// scaling to have a unit stroke of 1
|
||||
auto sc = [scale](const Vec2d& p) -> Vec2d { return {p[0] * scale, p[1] * scale}; };
|
||||
auto scd = [scale](const double& val) -> double { return val * scale; };
|
||||
|
||||
// transform to have bottom left origin (svg default is top left origin)
|
||||
auto tf = [xmin = bbmin[0], ymax = bbmax[1]](const Vec2d& p) -> Vec2d { return {(p[0] - xmin), (-p[1] + ymax)}; };
|
||||
|
||||
// create tree with svg
|
||||
XMLTree dom{svg(sc(bbmin), sc(bbmax))};
|
||||
dom.org.nodes.push_back({svg::group()});
|
||||
auto& g = dom.org.nodes.back();
|
||||
|
||||
{ // plot all triangles
|
||||
for (const auto& triangle : triangles) {
|
||||
const auto& [a, b, c] = triangle;
|
||||
g.nodes.push_back({svg::polygon({sc(tf(a)), sc(tf(b)), sc(tf(c))})});
|
||||
}
|
||||
}
|
||||
|
||||
{ // plot all BBoxes
|
||||
for (const auto& bbox : bboxes) {
|
||||
const auto& [min, max] = bbox;
|
||||
const auto& [xmin, ymin] = min;
|
||||
const auto& [xmax, ymax] = max;
|
||||
std::vector<Vec2d> coords;
|
||||
coords.push_back(sc(tf({xmin, ymin})));
|
||||
coords.push_back(sc(tf({xmax, ymin})));
|
||||
coords.push_back(sc(tf({xmax, ymax})));
|
||||
coords.push_back(sc(tf({xmin, ymax})));
|
||||
g.nodes.push_back({svg::polygon(coords)});
|
||||
}
|
||||
}
|
||||
|
||||
{ // plot all circles
|
||||
for (const auto& circle : circles) {
|
||||
const auto& [c, r] = circle;
|
||||
g.nodes.push_back({svg::circle({sc(tf(c)), scd(r)})});
|
||||
}
|
||||
}
|
||||
|
||||
{ // plot bounding box of viewport
|
||||
const auto& [xmin, ymin] = bbmin;
|
||||
const auto& [xmax, ymax] = bbmax;
|
||||
std::vector<Vec2d> limits;
|
||||
limits.push_back(sc(tf({xmin, ymin})));
|
||||
limits.push_back(sc(tf({xmax, ymin})));
|
||||
limits.push_back(sc(tf({xmax, ymax})));
|
||||
limits.push_back(sc(tf({xmin, ymax})));
|
||||
g.nodes.push_back({svg::polygon(limits)});
|
||||
}
|
||||
|
||||
// serialize tree to string
|
||||
std::stringstream stream;
|
||||
{
|
||||
|
||||
auto down = [&stream](const XMLTag& value, std::size_t level) -> void {
|
||||
stream << std::string(2 * level, ' ') << value.open() << std::endl;
|
||||
auto text = value.text();
|
||||
if (!text.empty())
|
||||
stream << std::string(2 * level, ' ') << value.text() << std::endl;
|
||||
};
|
||||
|
||||
auto up = [&stream](const XMLTag& value, std::size_t level) -> void {
|
||||
auto close = value.close();
|
||||
if (!close.empty())
|
||||
stream << std::string(2 * level, ' ') << value.close() << std::endl;
|
||||
};
|
||||
|
||||
XMLTree::preorder(dom.org, down, up);
|
||||
}
|
||||
|
||||
// save as file
|
||||
std::ofstream fs;
|
||||
fs.exceptions(std::ifstream::badbit);
|
||||
fs.open(filepath);
|
||||
fs << stream.str();
|
||||
|
||||
return {bbmin, bbmax};
|
||||
}
|
||||
|
||||
} // namespace iue::svg
|
||||
@@ -0,0 +1,37 @@
|
||||
/// @file
|
||||
/// @brief Test for 'iue::svg::render'
|
||||
|
||||
#include "iue-svg/render.hpp" // iue::svg::render
|
||||
#include "iue-num/numerics.hpp" // iue::num::isclose
|
||||
#include "iue-svg/tags.hpp" // iue::svg::Circle, iue::svg::Triangle, iue::svg::BBox
|
||||
|
||||
int main() {
|
||||
|
||||
std::vector<iue::svg::Circle> circles;
|
||||
circles.push_back(iue::svg::Circle{{1, 1}, 1});
|
||||
circles.push_back(iue::svg::Circle{{2, 2}, 2});
|
||||
circles.push_back(iue::svg::Circle{{3, 3}, 3});
|
||||
circles.push_back(iue::svg::Circle{{10, 10}, 10});
|
||||
|
||||
std::vector<iue::svg::Triangle> triangles;
|
||||
triangles.push_back(iue::svg::Triangle{{{50, 50}, {49, 50}, {50, 49}}});
|
||||
triangles.push_back(iue::svg::Triangle{{{50, 50}, {48, 50}, {50, 48}}});
|
||||
triangles.push_back(iue::svg::Triangle{{{50, 50}, {47, 50}, {50, 47}}});
|
||||
triangles.push_back(iue::svg::Triangle{{{50, 50}, {40, 50}, {50, 40}}});
|
||||
|
||||
std::vector<iue::svg::BBox> bboxes;
|
||||
bboxes.push_back(iue::svg::BBox{{0, 49}, {1, 50}});
|
||||
bboxes.push_back(iue::svg::BBox{{0, 48}, {2, 50}});
|
||||
bboxes.push_back(iue::svg::BBox{{0, 47}, {4, 50}});
|
||||
bboxes.push_back(iue::svg::BBox{{0, 40}, {10, 50}});
|
||||
|
||||
auto [bbmin, bbmax] = iue::svg::render("render.test.svg", bboxes, circles, triangles);
|
||||
|
||||
assert(iue::num::isclose(bbmin[0], 0));
|
||||
assert(iue::num::isclose(bbmin[1], 0));
|
||||
|
||||
assert(iue::num::isclose(bbmax[0], 50));
|
||||
assert(iue::num::isclose(bbmax[1], 50));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/// @file
|
||||
/// @brief Implementation of SVG-Tags for the SVG-writer in render.hpp
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array> // std::array
|
||||
#include <sstream> // std::stringstream
|
||||
#include <string> // std::string
|
||||
#include <unordered_map> // std::unordered_map
|
||||
#include <vector> // std::vector
|
||||
|
||||
namespace iue::svg {
|
||||
|
||||
/// @brief convenience aliases for short notations
|
||||
using Vec2d = std::array<double, 2>;
|
||||
using Circle = std::tuple<Vec2d, double>;
|
||||
using Polygon = std::vector<Vec2d>;
|
||||
using Triangle = std::array<Vec2d, 3>;
|
||||
using BBox = std::tuple<Vec2d, Vec2d>;
|
||||
using Text = std::tuple<Vec2d, std::string>;
|
||||
|
||||
/// @brief Map for storing an open set of attribute/value pairs
|
||||
using Attributes = std::unordered_map<std::string, std::string>;
|
||||
|
||||
/// @brief Requirements towards an XMLTag
|
||||
template <typename T>
|
||||
concept Tag = requires(const T& t) {
|
||||
{ t.open() } -> std::same_as<std::string>;
|
||||
{ t.text() } -> std::same_as<std::string>;
|
||||
{ t.close() } -> std::same_as<std::string>;
|
||||
};
|
||||
|
||||
/// @brief Implementation of an SVG XML-tag
|
||||
struct XMLTag {
|
||||
std::string name;
|
||||
Attributes attributes;
|
||||
std::string content;
|
||||
bool self_closing;
|
||||
std::string text() const { return this->content; }
|
||||
std::string open() const {
|
||||
std::stringstream res;
|
||||
res << "<" << name;
|
||||
for (auto const& [key, val] : attributes)
|
||||
res << " " << key << "='" << val << "'";
|
||||
if (self_closing)
|
||||
res << "/>";
|
||||
else
|
||||
res << ">";
|
||||
return res.str();
|
||||
}
|
||||
std::string close() const {
|
||||
std::stringstream res;
|
||||
if (!self_closing)
|
||||
res << "</" << name << ">";
|
||||
return res.str();
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Check if Tag requirements are fullfilled
|
||||
static_assert(Tag<XMLTag>);
|
||||
|
||||
/// @brief Root SVG Element
|
||||
/// <svg preserveAspectRatio='xMidYMid meet' viewbox='0 0 w h' height='w' width='h'></svg>
|
||||
inline XMLTag svg(const Vec2d& bbmin, const Vec2d& bbmax) {
|
||||
Attributes attributes;
|
||||
// add data attributes
|
||||
auto const& [xmin, ymin] = bbmin;
|
||||
auto const& [xmax, ymax] = bbmax;
|
||||
double width = std::abs(xmax - xmin);
|
||||
double height = std::abs(ymax - ymin);
|
||||
attributes.insert({"width", std::to_string(width)});
|
||||
attributes.insert({"height", std::to_string(height)});
|
||||
std::stringstream viewbox;
|
||||
for (const auto& val : {xmin, ymin, width, height})
|
||||
viewbox << val << " ";
|
||||
attributes.insert({"viewbox", viewbox.str()});
|
||||
// add default attributes
|
||||
attributes.insert({"xmlns", "http://www.w3.org/2000/svg"});
|
||||
attributes.insert({"version", "1.1"});
|
||||
attributes.insert({"preserveAspectRatio", "xMidYMid meet"});
|
||||
// final assembly
|
||||
return {"svg", attributes, std::string{}, false};
|
||||
}
|
||||
|
||||
/// @brief Group SVG Element
|
||||
/// <g></g>
|
||||
inline XMLTag group(const Attributes& custom_attributes = {}) {
|
||||
Attributes attributes;
|
||||
// add custom attributes
|
||||
attributes.insert(custom_attributes.begin(), custom_attributes.end());
|
||||
// final assembly
|
||||
return {"g", attributes, std::string{}, false};
|
||||
}
|
||||
|
||||
/// @brief Circle SVG Element
|
||||
/// <circle r='r0' cx='cx' cy='cy' stroke-width='1' fill='none' stroke='black'/>
|
||||
inline XMLTag circle(const Circle& circle, const Attributes& custom_attributes = {}) {
|
||||
Attributes attributes;
|
||||
// add data attributes
|
||||
const auto& [center, radius] = circle;
|
||||
const auto& [cx, cy] = center;
|
||||
attributes.insert({"cx", std::to_string(cx)});
|
||||
attributes.insert({"cy", std::to_string(cy)});
|
||||
attributes.insert({"r", std::to_string(radius)});
|
||||
// add custom attributes
|
||||
attributes.insert(custom_attributes.begin(), custom_attributes.end());
|
||||
// add default attributes
|
||||
attributes.insert({"stroke-width", std::to_string(1)});
|
||||
attributes.insert({"stroke", "black"});
|
||||
attributes.insert({"fill", "none"});
|
||||
// final assembly
|
||||
return {"circle", attributes, std::string{}, true};
|
||||
}
|
||||
|
||||
/// @brief Polygon SVG Element
|
||||
/// <polygon fill='none' stroke='black' stroke-width='1' points=' x1,y1 x2,y2, x3,y3 ... '/>
|
||||
inline XMLTag polygon(const Polygon& polygon, const Attributes& custom_attributes = {}) {
|
||||
Attributes attributes;
|
||||
// add data attributes
|
||||
std::stringstream points;
|
||||
for (const auto& [x, y] : polygon)
|
||||
points << x << "," << y << " ";
|
||||
attributes.insert({"points", points.str()});
|
||||
// add custom attributes
|
||||
attributes.insert(custom_attributes.begin(), custom_attributes.end());
|
||||
// add default attributes
|
||||
attributes.insert({"stroke-width", std::to_string(1)});
|
||||
attributes.insert({"stroke", "black"});
|
||||
attributes.insert({"fill", "none"});
|
||||
// final assembly
|
||||
return {"polygon", attributes, std::string{}, true};
|
||||
}
|
||||
|
||||
/// @brief Text SVG Element
|
||||
/// <text font-size='10' text-anchor='middle' x='x' y='y' > content </text>
|
||||
inline XMLTag text(const Text& text, const Attributes& custom_attributes = {}) {
|
||||
Attributes attributes;
|
||||
// add data attributes
|
||||
const auto& [anchor, content] = text;
|
||||
const auto& [x, y] = anchor;
|
||||
attributes.insert({"x", std::to_string(x)});
|
||||
attributes.insert({"y", std::to_string(y)});
|
||||
// add custom attributes
|
||||
attributes.insert(custom_attributes.begin(), custom_attributes.end());
|
||||
// add default attributes
|
||||
attributes.insert({"font-size", std::to_string(10)});
|
||||
attributes.insert({"text-anchor", "middle"});
|
||||
return {"text", attributes, content, false};
|
||||
}
|
||||
|
||||
} // namespace iue::svg
|
||||
@@ -0,0 +1,62 @@
|
||||
/// @file
|
||||
/// @brief Test for iue::svg::XMLTag
|
||||
|
||||
#include "iue-svg/tags.hpp" // iue::svg::XMLTag, iue::svg::svg, iue::svg::circle, iue::svg::polygon, iue::svg::, iue::svg::text
|
||||
|
||||
#include <cstdlib> // EXIT_SUCCESS
|
||||
#include <iostream> // std::cout, std::endl
|
||||
#include <string> // std::string
|
||||
|
||||
struct Value {
|
||||
int a = 1;
|
||||
};
|
||||
|
||||
int main() {
|
||||
|
||||
{ // w/ default attributes
|
||||
auto svg = iue::svg::svg({0, 0}, {100, 100});
|
||||
auto group = iue::svg::group();
|
||||
auto text = iue::svg::text({{50, 50}, "text"});
|
||||
auto circle = iue::svg::circle({{50, 50}, 0.5});
|
||||
auto polygon = iue::svg::polygon({{50, 50}, {52, 51}, {51, 52}, {78, 100}});
|
||||
|
||||
std::cout << svg.open() << std::endl;
|
||||
std::cout << group.open() << std::endl;
|
||||
std::cout << text.open() << std::endl;
|
||||
std::cout << text.text() << std::endl;
|
||||
std::cout << text.close() << std::endl;
|
||||
std::cout << circle.open() << std::endl;
|
||||
std::cout << circle.close() << std::endl;
|
||||
std::cout << polygon.open() << std::endl;
|
||||
std::cout << polygon.close() << std::endl;
|
||||
std::cout << group.close() << std::endl;
|
||||
std::cout << svg.close() << std::endl;
|
||||
}
|
||||
|
||||
{ // w/ custom attributes
|
||||
iue::svg::Attributes attributes;
|
||||
attributes.insert({"stroke-width", std::to_string(10)});
|
||||
attributes.insert({"stroke", "green"});
|
||||
attributes.insert({"fill", "blue"});
|
||||
attributes.insert({"invalid-attribute", "no check is performed"});
|
||||
auto svg = iue::svg::svg({0, 0}, {100, 100});
|
||||
auto group = iue::svg::group(attributes);
|
||||
auto text = iue::svg::text({{50, 50}, "text"}, attributes);
|
||||
auto circle = iue::svg::circle({{50, 50}, 0.5}, attributes);
|
||||
auto polygon = iue::svg::polygon({{50, 50}, {52, 51}, {51, 52}, {78, 100}}, attributes);
|
||||
|
||||
std::cout << svg.open() << std::endl;
|
||||
std::cout << group.open() << std::endl;
|
||||
std::cout << text.open() << std::endl;
|
||||
std::cout << text.text() << std::endl;
|
||||
std::cout << text.close() << std::endl;
|
||||
std::cout << circle.open() << std::endl;
|
||||
std::cout << circle.close() << std::endl;
|
||||
std::cout << polygon.open() << std::endl;
|
||||
std::cout << polygon.close() << std::endl;
|
||||
std::cout << group.close() << std::endl;
|
||||
std::cout << svg.close() << std::endl;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/// @file
|
||||
/// @brief Educational implementation of a tree data structure with value semantics
|
||||
/// @note The implementation relies on the "minimal incomplete type support" incomplete type support for std::list
|
||||
/// - https://en.cppreference.com/w/cpp/feature_test#cpp_lib_incomplete_container_elements
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // std::size_t
|
||||
#include <functional> // std::function
|
||||
#include <list> // std::string
|
||||
|
||||
namespace iue::svg {
|
||||
|
||||
template <typename T>
|
||||
concept Value = requires(const T& a, T& b) {
|
||||
T(a); ///< copy constructor (std::copy_constructible)
|
||||
b = a; ///< copy assignment (std::assignable_from)
|
||||
};
|
||||
|
||||
/// @brief Generic tree (tree with a dynamic unbound number of child nodes)
|
||||
template <Value Value> struct Tree {
|
||||
|
||||
/// @brief Node in the tree
|
||||
struct Node {
|
||||
Value value; ///< Stored value
|
||||
std::list<Node> nodes; ///< List of child nodes
|
||||
};
|
||||
|
||||
using ValueVisitor = std::function<void(const Value& value, std::size_t level)>;
|
||||
using NodeVisitor = std::function<void(const Node& node, std::size_t level)>;
|
||||
|
||||
Node org; ///< Root node of the tree
|
||||
|
||||
/// @brief Constructs a tree
|
||||
/// @param Value held by the root node of the tree
|
||||
/// @note This constructor avoids that the value type needs to be default-constructible
|
||||
Tree(const Value& value) : org{value} {}
|
||||
|
||||
/// @brief Visit all values in the nodes in preorder using two visitors for down and up path
|
||||
static void preorder(Node& root, const ValueVisitor& down, const ValueVisitor& up, std::size_t level = 0) {
|
||||
down(root.value, level);
|
||||
for (auto& child : root.nodes)
|
||||
preorder(child, down, up, level + 1);
|
||||
up(root.value, level);
|
||||
}
|
||||
|
||||
/// @brief Visit all nodes in preorder using two visitors for down and up path
|
||||
static void preorder(Node& root, const NodeVisitor& down, const NodeVisitor& up, std::size_t level = 0) {
|
||||
down(root, level);
|
||||
for (auto& child : root.nodes)
|
||||
preorder(child, down, up, level + 1);
|
||||
up(root, level);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace iue::svg
|
||||
@@ -0,0 +1,60 @@
|
||||
/// @file
|
||||
/// @brief Test for iue::svg::Tree
|
||||
|
||||
#include "iue-svg/tree.hpp" // iue::svg::Tree
|
||||
|
||||
#include <cassert> // assert
|
||||
#include <cstddef> // std::size_t
|
||||
#include <cstdlib> // EXIT_SUCCESS
|
||||
#include <iostream> // std::cout, std::endl
|
||||
#include <sstream> // std::stringstream
|
||||
#include <string> // std::string
|
||||
|
||||
struct Value {
|
||||
int a = 1;
|
||||
};
|
||||
|
||||
int main() {
|
||||
|
||||
using Tree = iue::svg::Tree<Value>;
|
||||
using Node = Tree::Node;
|
||||
|
||||
Tree tree = {Value{0}};
|
||||
|
||||
auto& l0 = tree.org;
|
||||
|
||||
l0.nodes.push_back(Node{Value{1}});
|
||||
l0.nodes.push_back(Node{Value{1}});
|
||||
l0.nodes.push_back(Node{Value{1}});
|
||||
|
||||
for (auto& l1 : l0.nodes) {
|
||||
l1.nodes.push_back(Node{Value{2}});
|
||||
l1.nodes.push_back(Node{Value{2}});
|
||||
l1.nodes.push_back(Node{Value{2}});
|
||||
for (auto& l1 : l1.nodes) {
|
||||
l1.nodes.push_back(Node{Value{3}});
|
||||
l1.nodes.push_back(Node{Value{3}});
|
||||
}
|
||||
}
|
||||
|
||||
auto noop = [](const Value& value, std::size_t level) -> void {};
|
||||
|
||||
{ // print tree
|
||||
std::stringstream stream;
|
||||
|
||||
auto print = [&stream](const Value& value, std::size_t level) -> void {
|
||||
stream << std::string(2 * level, ' ') << value.a << std::endl;
|
||||
};
|
||||
tree.preorder(l0, print, noop);
|
||||
std::cout << stream.str() << std::endl;
|
||||
}
|
||||
|
||||
{ // sum values in tree
|
||||
int sum = 0;
|
||||
auto count = [&sum](const Value& value, std::size_t level) -> void { sum += value.a; };
|
||||
tree.preorder(l0, count, noop);
|
||||
assert(sum == 1 * 0 + (1 * 3 * 1) + (1 * 3 * 3 * 2) + (1 * 3 * 3 * 2 * 3));
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user