]> granicus.if.org Git - graphviz/commitdiff
tests: SVGAnalyzer: add support for retrieving the Graphviz node color
authorMagnus Jacobsson <Magnus.Jacobsson@berotec.se>
Wed, 10 Aug 2022 13:54:00 +0000 (15:54 +0200)
committerMagnus Jacobsson <Magnus.Jacobsson@berotec.se>
Mon, 5 Sep 2022 06:52:12 +0000 (08:52 +0200)
tests/CMakeLists.txt
tests/graphviz_node.cpp
tests/graphviz_node.h
tests/svg_element.cpp
tests/svg_element.h
tests/test_node_color.cpp [new file with mode: 0644]

index 603298ef74c71e7eee7ce06fc8c205d79d0d39a1..beced86bf20debb2d935cbd21842679a5ea733fc 100644 (file)
@@ -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)
index ff5c9e2c843278882b98579a50a0892b3607b105..37b356f08941f955d5110079c389679fe9c33298 100644 (file)
@@ -1,10 +1,22 @@
+#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);
index 2a27e1e8f6339b211abc163caa30823b551e43d4..b2af29e1f46282258a34c01bede3c563a0f5663d 100644 (file)
@@ -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
index 9e7aa81eef433072f0b3400406684f58c133250d..76a5b9ce323efad78464ce5adfc650d63649084a 100644 (file)
@@ -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
index d7cc6b70347727e817b4c91f136e6cf07e087d1b..5dd4918cfd319baaf600367e1310a58fd47f7d05 100644 (file)
@@ -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 (file)
index 0000000..e65ef9d
--- /dev/null
@@ -0,0 +1,55 @@
+#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);
+    }
+  }
+}