]> granicus.if.org Git - graphviz/commitdiff
tests: SVGAnalyzer: add support for retrieving the Graphviz node fillcolor
authorMagnus Jacobsson <Magnus.Jacobsson@berotec.se>
Sat, 13 Aug 2022 13:53:15 +0000 (15:53 +0200)
committerMagnus Jacobsson <Magnus.Jacobsson@berotec.se>
Mon, 5 Sep 2022 07:01:20 +0000 (09:01 +0200)
tests/CMakeLists.txt
tests/graphviz_node.cpp
tests/graphviz_node.h
tests/test_node_fillcolor.cpp [new file with mode: 0644]
tests/test_utilities.cpp
tests/test_utilities.h

index 9d1b82692458465b9ef6c7b360eef8e0aa312a20..55f63ae056d7dcc8011c417c3bfded994e976d99 100644 (file)
@@ -87,6 +87,7 @@ CREATE_TEST(GVLayout_construction)
 CREATE_TEST(GVLayout_render)
 CREATE_TEST(neatopack)
 CREATE_TEST(node_color)
+CREATE_TEST(node_fillcolor)
 CREATE_TEST(node_penwidth)
 CREATE_TEST(rankdir)
 CREATE_TEST(simple)
index 37b356f08941f955d5110079c389679fe9c33298..630c34264ac723a38b687ef8a8297a5670a5777b 100644 (file)
@@ -17,6 +17,14 @@ std::string GraphvizNode::color() const {
   return SVG::to_dot_color(stroke, stroke_opacity);
 }
 
+std::string GraphvizNode::fillcolor() const {
+  const auto fill = m_svg_g_element.attribute_from_subtree<std::string>(
+      &SVG::SVGAttributes::fill, &SVG::SVGElement::is_shape_element, "");
+  const auto fill_opacity = m_svg_g_element.attribute_from_subtree<double>(
+      &SVG::SVGAttributes::fill_opacity, &SVG::SVGElement::is_shape_element, 1);
+  return SVG::to_dot_color(fill, fill_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 b2af29e1f46282258a34c01bede3c563a0f5663d..dbe8b46ff482c63fa5772c8dcaddf532616a2957 100644 (file)
@@ -22,6 +22,9 @@ public:
   /// 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 `fillcolor` attribute in RGB hex format if the opacity
+  /// is 100 % or in RGBA hex format otherwise.
+  std::string fillcolor() 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/test_node_fillcolor.cpp b/tests/test_node_fillcolor.cpp
new file mode 100644 (file)
index 0000000..d3e1bb5
--- /dev/null
@@ -0,0 +1,58 @@
+#include <string_view>
+
+#include <catch2/catch.hpp>
+#include <fmt/format.h>
+
+#include "svg_analyzer.h"
+#include "test_utilities.h"
+
+TEST_CASE("Node fillcolor",
+          "Test that the Graphviz `fillcolor` attribute is used to set "
+          "the `fill` and `fill-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_fillcolor =
+      GENERATE("#000000", "#ffffff", "#ff0000", "#aa0f55");
+  const auto opacity = GENERATE(0, 200, 255);
+  const auto node_rgba_fillcolor =
+      fmt::format("{}{:02x}", node_rgb_fillcolor, opacity);
+  INFO(fmt::format("Node fillcolor: {}", node_rgba_fillcolor));
+
+  auto dot = fmt::format(
+      "digraph g1 {{node [shape={} style=filled fillcolor=\"{}\"]; a -> b}}",
+      shape, node_rgba_fillcolor);
+
+  const auto engine = "dot";
+  auto svg_analyzer = SVGAnalyzer::make_from_dot(dot, engine);
+
+  const auto expected_node_fillcolor = [&]() -> const std::string {
+    if (opacity == 255) {
+      return node_rgb_fillcolor;
+    }
+    if (opacity == 0) {
+      return "#00000000"; // transparent/none
+    }
+    return node_rgba_fillcolor;
+  }();
+
+  for (const auto &graph : svg_analyzer.graphs()) {
+    for (const auto &node : graph.nodes()) {
+      if (contains_multiple_shapes_with_different_fill(shape) && opacity != 0) {
+        // FIXME: these node shapes consists of both a transparent shape and a
+        // shape with the specified filllcolor which the node fillcolor
+        // retrieval function cannot yet handle (unless the specified fillcolor
+        // is also transparent) and will throw an exception for
+        REQUIRE_THROWS_AS(node.fillcolor(), std::runtime_error);
+        continue;
+      }
+      CHECK(node.fillcolor() == expected_node_fillcolor);
+    }
+  }
+}
index b7e964370ec1802b146c60b4d95ddf7ea257806f..161531420db68156ead5588960b7f3a8d94f64ff 100644 (file)
@@ -144,16 +144,36 @@ const std::unordered_set<std::string_view> all_node_shapes = {
     "lpromoter"        //
 };
 
+static const std::unordered_set<std::string_view>
+    node_shapes_containing_multiple_same_shapes_with_different_fill = {
+        "cylinder",      // two paths
+        "doublecircle",  // two ellipses
+        "doubleoctagon", // two polygons
+        "tripleoctagon", // three polygons
+};
+
 bool contains_polygon_shape(const std::string_view shape) {
   return node_shapes_consisting_of_polygon.contains(shape) ||
          node_shapes_consisting_of_polygon_and_polyline.contains(shape);
 }
 
+bool contains_polyline_shape(const std::string_view shape) {
+  return node_shapes_consisting_of_ellipse_and_polyline.contains(shape) ||
+         node_shapes_consisting_of_polygon_and_polyline.contains(shape);
+}
+
 bool contains_ellipse_shape(const std::string_view shape) {
   return node_shapes_consisting_of_ellipse.contains(shape) ||
          node_shapes_consisting_of_ellipse_and_polyline.contains(shape);
 }
 
+bool contains_multiple_shapes_with_different_fill(
+    const std::string_view shape) {
+  return node_shapes_containing_multiple_same_shapes_with_different_fill
+             .contains(shape) ||
+         contains_polyline_shape(shape);
+}
+
 const std::unordered_set<std::string_view> primitive_polygon_arrow_shapes = {
     "crow", "diamond", "inv", "normal", "vee"};
 
index 9bab6cac294c9407b38e71af6658e91469923377..d3f4c7d431ac9868d6f1cfc994ec9ae983780ab9 100644 (file)
@@ -19,7 +19,9 @@ extern const std::unordered_set<std::string_view>
 extern const std::unordered_set<std::string_view> node_shapes_without_svg_shape;
 
 bool contains_ellipse_shape(std::string_view shape);
+bool contains_multiple_shapes_with_different_fill(std::string_view shape);
 bool contains_polygon_shape(std::string_view shape);
+bool contains_polyline_shape(std::string_view shape);
 
 /// arrow shapes
 extern const std::unordered_set<std::string_view>