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)
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);
/// 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
--- /dev/null
+#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);
+ }
+ }
+}
"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"};
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>