]> granicus.if.org Git - graphviz/commitdiff
tests: SVGAnalyzer: add support for retrieving the Graphviz node penwidth
authorMagnus Jacobsson <Magnus.Jacobsson@berotec.se>
Wed, 10 Aug 2022 09:26:23 +0000 (11:26 +0200)
committerMagnus Jacobsson <Magnus.Jacobsson@berotec.se>
Mon, 5 Sep 2022 06:17:08 +0000 (08:17 +0200)
tests/CMakeLists.txt
tests/graphviz_node.cpp
tests/graphviz_node.h
tests/svg_element.cpp
tests/svg_element.h
tests/test_node_penwidth.cpp [new file with mode: 0644]

index 73daa0e529a5f7dfdee752e00d67177d73329c76..bf134144218553638fa1b62caeccf608c6693c01 100644 (file)
@@ -84,6 +84,7 @@ CREATE_TEST(GVContext_render_svg)
 CREATE_TEST(GVLayout_construction)
 CREATE_TEST(GVLayout_render)
 CREATE_TEST(neatopack)
+CREATE_TEST(node_penwidth)
 CREATE_TEST(rankdir)
 CREATE_TEST(simple)
 CREATE_TEST(subgraph_layout)
index ecaa751748e4b8abc2855fbc8723085e154c946c..ff5c9e2c843278882b98579a50a0892b3607b105 100644 (file)
@@ -5,6 +5,11 @@ GraphvizNode::GraphvizNode(SVG::SVGElement &svg_element)
 
 SVG::SVGPoint GraphvizNode::center() const { return bbox().center(); }
 
+double GraphvizNode::penwidth() const {
+  return m_svg_g_element.attribute_from_subtree<double>(
+      &SVG::SVGAttributes::stroke_width, &SVG::SVGElement::is_shape_element, 1);
+}
+
 std::string_view GraphvizNode::node_id() const { return m_node_id; }
 
 SVG::SVGRect GraphvizNode::bbox() const { return m_svg_g_element.bbox(); }
index 74713197bd159afe43d95f853a7ad1522cb12536..2a27e1e8f6339b211abc163caa30823b551e43d4 100644 (file)
@@ -21,6 +21,8 @@ public:
   SVG::SVGPoint center() 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
+  double penwidth() const;
   /// Return a non-mutable reference to the SVG `g` element corresponding to the
   /// node
   const SVG::SVGElement &svg_g_element() const;
index f2fee723c4f82ab5f662cbc27de9fa9829b8b62c..94da9d57b86f5e2b0a2aaf82b1ae58ba62fa4055 100644 (file)
@@ -15,6 +15,21 @@ static double px_to_pt(double px) {
   return px * 3 / 4;
 }
 
+bool SVG::SVGElement::is_shape_element() const {
+  switch (type) {
+  case SVG::SVGElementType::Circle:
+  case SVG::SVGElementType::Ellipse:
+  case SVG::SVGElementType::Line:
+  case SVG::SVGElementType::Path:
+  case SVG::SVGElementType::Polygon:
+  case SVG::SVGElementType::Polyline:
+  case SVG::SVGElementType::Rect:
+    return true;
+  default:
+    return false;
+  }
+}
+
 static std::string xml_encode(const std::string &text) {
   std::string out;
   for (const char &ch : text) {
index 5aed78a018ee20cfe49ed317baf10f4eff8937b9..b5e1ca2b3a96ed6d41d0383796680ceef0fd0514 100644 (file)
@@ -2,10 +2,13 @@
 
 #include <cmath>
 #include <optional>
+#include <stdexcept>
 #include <string>
 #include <string_view>
 #include <vector>
 
+#include <fmt/format.h>
+
 namespace SVG {
 
 struct SVGPoint {
@@ -83,6 +86,44 @@ public:
   SVGElement() = delete;
   explicit SVGElement(SVG::SVGElementType type);
 
+  /// \brief Return the value of an attribute retrieved from the element and its
+  /// children
+  ///
+  /// @param attribute_ptr pointer to a member of the `SVGAttributes` class.
+  /// @param predicate pointer to a member function of the `SVGElement` class
+  /// which returns `true` for elements on which the attribute exists.
+  /// @param default_value the value to return if the attribute does not exist
+  /// on any of the elements.
+  ///
+  /// Returns the value of the attribute if it has the same value on all
+  /// elements on which it exists and throws an exception if the value is not
+  /// consistent on those elements. Returns the default value if the attribute
+  /// does not exist on any of the elements.
+  template <typename T>
+  T attribute_from_subtree(T SVGAttributes::*attribute_ptr,
+                           bool (SVGElement::*predicate)() const,
+                           T default_value) const {
+    std::optional<T> attribute;
+    if ((this->*predicate)()) {
+      attribute = this->attributes.*attribute_ptr;
+    }
+    for (const auto &child : children) {
+      if (!(child.*predicate)()) {
+        continue;
+      }
+      const auto child_attribute = child.attributes.*attribute_ptr;
+      if (!attribute.has_value()) {
+        attribute = child_attribute;
+        continue;
+      }
+      if (attribute.value() != child_attribute) {
+        throw std::runtime_error{fmt::format(
+            "Inconsistent value of attribute: current {}: {}, child {}: {}",
+            tag(type), attribute.value(), tag(child.type), child_attribute)};
+      }
+    }
+    return attribute.value_or(default_value);
+  }
   /// Return the bounding box of the element and its children. The bounding box
   /// is calculated and stored the first time this function is called and later
   /// calls will return the already calculated value. If this function is called
@@ -90,6 +131,7 @@ public:
   /// throw an exception unless the `throw_if_bbox_not_defined` parameter is
   /// `false`.
   SVG::SVGRect bbox(bool throw_if_bbox_not_defined = true);
+  bool is_shape_element() const;
   std::string to_string(std::size_t indent_size) const;
 
   SVGAttributes attributes;
diff --git a/tests/test_node_penwidth.cpp b/tests/test_node_penwidth.cpp
new file mode 100644 (file)
index 0000000..cfae763
--- /dev/null
@@ -0,0 +1,34 @@
+#include <string_view>
+
+#include <catch2/catch.hpp>
+#include <fmt/format.h>
+
+#include "svg_analyzer.h"
+#include "test_utilities.h"
+
+TEST_CASE("Node penwidth",
+          "Test that the Graphviz 'penwidth' attribute is used to set the "
+          "'stroke-width' attribute 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_penwidth = GENERATE(0.5, 1.0, 2.0);
+  INFO(fmt::format("Node penwidth: {}", node_penwidth));
+
+  auto dot = fmt::format("digraph g1 {{node [shape={} penwidth={}]; a -> b}}",
+                         shape, node_penwidth);
+
+  const auto engine = "dot";
+  auto svg_analyzer = SVGAnalyzer::make_from_dot(dot, engine);
+
+  for (const auto &graph : svg_analyzer.graphs()) {
+    for (const auto &node : graph.nodes()) {
+      CHECK(node.penwidth() == node_penwidth);
+    }
+  }
+}