From: Magnus Jacobsson Date: Wed, 10 Aug 2022 09:26:23 +0000 (+0200) Subject: tests: SVGAnalyzer: add support for retrieving the Graphviz node penwidth X-Git-Tag: 6.0.1~10^2~10 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=8caffd31bcd6c16c720ddc429475e7b2dac743a1;p=graphviz tests: SVGAnalyzer: add support for retrieving the Graphviz node penwidth --- diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 73daa0e52..bf1341442 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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) diff --git a/tests/graphviz_node.cpp b/tests/graphviz_node.cpp index ecaa75174..ff5c9e2c8 100644 --- a/tests/graphviz_node.cpp +++ b/tests/graphviz_node.cpp @@ -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( + &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(); } diff --git a/tests/graphviz_node.h b/tests/graphviz_node.h index 74713197b..2a27e1e8f 100644 --- a/tests/graphviz_node.h +++ b/tests/graphviz_node.h @@ -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; diff --git a/tests/svg_element.cpp b/tests/svg_element.cpp index f2fee723c..94da9d57b 100644 --- a/tests/svg_element.cpp +++ b/tests/svg_element.cpp @@ -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) { diff --git a/tests/svg_element.h b/tests/svg_element.h index 5aed78a01..b5e1ca2b3 100644 --- a/tests/svg_element.h +++ b/tests/svg_element.h @@ -2,10 +2,13 @@ #include #include +#include #include #include #include +#include + 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 + T attribute_from_subtree(T SVGAttributes::*attribute_ptr, + bool (SVGElement::*predicate)() const, + T default_value) const { + std::optional 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 index 000000000..cfae76361 --- /dev/null +++ b/tests/test_node_penwidth.cpp @@ -0,0 +1,34 @@ +#include + +#include +#include + +#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); + } + } +}