From 4f60be0728007893aadd383447e85e7fcef3d5e8 Mon Sep 17 00:00:00 2001 From: Magnus Jacobsson Date: Wed, 10 Aug 2022 15:54:00 +0200 Subject: [PATCH] tests: SVGAnalyzer: add support for retrieving the Graphviz node color --- tests/CMakeLists.txt | 1 + tests/graphviz_node.cpp | 12 +++++++++ tests/graphviz_node.h | 3 +++ tests/svg_element.cpp | 16 ++++++++++++ tests/svg_element.h | 2 ++ tests/test_node_color.cpp | 55 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 89 insertions(+) create mode 100644 tests/test_node_color.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 603298ef7..beced86bf 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -85,6 +85,7 @@ CREATE_TEST(GVContext_render_svg) 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) diff --git a/tests/graphviz_node.cpp b/tests/graphviz_node.cpp index ff5c9e2c8..37b356f08 100644 --- a/tests/graphviz_node.cpp +++ b/tests/graphviz_node.cpp @@ -1,10 +1,22 @@ +#include + #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( + &SVG::SVGAttributes::stroke, &SVG::SVGElement::is_shape_element, ""); + const auto stroke_opacity = m_svg_g_element.attribute_from_subtree( + &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( &SVG::SVGAttributes::stroke_width, &SVG::SVGElement::is_shape_element, 1); diff --git a/tests/graphviz_node.h b/tests/graphviz_node.h index 2a27e1e8f..b2af29e1f 100644 --- a/tests/graphviz_node.h +++ b/tests/graphviz_node.h @@ -19,6 +19,9 @@ public: 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 diff --git a/tests/svg_element.cpp b/tests/svg_element.cpp index 9e7aa81ee..76a5b9ce3 100644 --- a/tests/svg_element.cpp +++ b/tests/svg_element.cpp @@ -81,6 +81,22 @@ static std::string to_graphviz_color(const std::string &color) { } } +// 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 diff --git a/tests/svg_element.h b/tests/svg_element.h index d7cc6b703..5dd4918cf 100644 --- a/tests/svg_element.h +++ b/tests/svg_element.h @@ -78,6 +78,8 @@ struct SVGAttributes { double y; }; +std::string to_dot_color(const std::string &color, double opacity = 1.0); + /** * @brief The SVGElement class represents an SVG element */ diff --git a/tests/test_node_color.cpp b/tests/test_node_color.cpp new file mode 100644 index 000000000..e65ef9da8 --- /dev/null +++ b/tests/test_node_color.cpp @@ -0,0 +1,55 @@ +#include + +#include +#include + +#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); + } + } +} -- 2.40.0