CREATE_TEST(GVLayout_construction)
CREATE_TEST(GVLayout_render)
CREATE_TEST(neatopack)
+CREATE_TEST(node_color)
CREATE_TEST(node_penwidth)
CREATE_TEST(rankdir)
CREATE_TEST(simple)
+#include <string>
+
#include "graphviz_node.h"
+#include "svg_element.h"
GraphvizNode::GraphvizNode(SVG::SVGElement &svg_element)
: m_node_id(svg_element.graphviz_id), m_svg_g_element(svg_element) {}
SVG::SVGPoint GraphvizNode::center() const { return bbox().center(); }
+std::string GraphvizNode::color() const {
+ const auto stroke = m_svg_g_element.attribute_from_subtree<std::string>(
+ &SVG::SVGAttributes::stroke, &SVG::SVGElement::is_shape_element, "");
+ const auto stroke_opacity = m_svg_g_element.attribute_from_subtree<double>(
+ &SVG::SVGAttributes::stroke_opacity, &SVG::SVGElement::is_shape_element,
+ 1);
+ return SVG::to_dot_color(stroke, stroke_opacity);
+}
+
double GraphvizNode::penwidth() const {
return m_svg_g_element.attribute_from_subtree<double>(
&SVG::SVGAttributes::stroke_width, &SVG::SVGElement::is_shape_element, 1);
SVG::SVGRect bbox() const;
/// Return the center of the node's bounding box
SVG::SVGPoint center() const;
+ /// Return the node's `color` attribute in RGB hex format if the opacity is
+ /// 100 % or in RGBA hex format otherwise.
+ std::string color() const;
/// Return the node's `node_id` as defined by the DOT language
std::string_view node_id() const;
/// Return the node's `penwidth` attribute
}
}
+// convert a valid color specification to the RGB or RGBA type that Graphviz
+// uses in the DOT source
+std::string SVG::to_dot_color(const std::string &color, double opacity) {
+ if (color == "none") {
+ return "#00000000";
+ }
+ if (opacity < 1.0) {
+ if (!color.starts_with("rgb")) {
+ throw std::runtime_error{fmt::format(
+ "Cannot convert stroke={}, stroke_opacity={} to Graphviz color",
+ color, opacity)};
+ }
+ }
+ return rgb_to_hex(color, opacity);
+}
+
SVG::SVGRect SVG::SVGElement::bbox(bool throw_if_bbox_not_defined) {
if (!m_bbox.has_value()) {
// negative width and height bbox that will be imediately replaced by the
double y;
};
+std::string to_dot_color(const std::string &color, double opacity = 1.0);
+
/**
* @brief The SVGElement class represents an SVG element
*/
--- /dev/null
+#include <string_view>
+
+#include <catch2/catch.hpp>
+#include <fmt/format.h>
+
+#include "svg_analyzer.h"
+#include "test_utilities.h"
+
+TEST_CASE("Node color",
+ "Test that the Graphviz `color` attribute is used to set the "
+ "`stroke` and `stroke-opacity` attributes correctly for nodes in the "
+ "generated SVG") {
+
+ const auto shape = GENERATE(filter(
+ [](std::string_view shape) {
+ return !node_shapes_without_svg_shape.contains(shape);
+ },
+ from_range(all_node_shapes)));
+ INFO(fmt::format("Shape: {}", shape));
+
+ const auto node_rgb_color = GENERATE("#000000", "#ffffff", "#5580aa");
+ const auto opacity = GENERATE(0, 100, 255);
+ const auto node_rgba_color = fmt::format("{}{:02x}", node_rgb_color, opacity);
+ INFO(fmt::format("Node color: {}", node_rgba_color));
+
+ auto dot = fmt::format("digraph g1 {{node [shape={} color=\"{}\"]; a -> b}}",
+ shape, node_rgba_color);
+
+ const auto engine = "dot";
+ auto svg_analyzer = SVGAnalyzer::make_from_dot(dot, engine);
+
+ const auto expected_node_color = [&]() -> const std::string {
+ if (opacity == 255) {
+ return node_rgb_color;
+ }
+ if (opacity == 0) {
+ return "#00000000"; // transparent/none
+ }
+ return node_rgba_color;
+ }();
+
+ for (const auto &graph : svg_analyzer.graphs()) {
+ for (const auto &node : graph.nodes()) {
+ if (shape == "underline" && opacity != 0) {
+ // FIXME: The 'underline' node shape consists of a transparent polygon
+ // and a polyline with the specified color which the node color
+ // retrieval function cannot yet handle (unless the specified color is
+ // also transparent) and will throw an exception for
+ REQUIRE_THROWS_AS(node.color(), std::runtime_error);
+ continue;
+ }
+ CHECK(node.color() == expected_node_color);
+ }
+ }
+}