TU-Programmieren_2/lab3/modules/iue-svg/render.hpp
2025-04-09 10:22:44 +02:00

158 lines
5.2 KiB
C++

/// @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