CREATE_TEST(subgraphs)
CREATE_TEST(svg_analyzer)
CREATE_TEST(svg_analyzer_color)
+CREATE_TEST(svg_analyzer_fillcolor)
CREATE_TEST(svg_analyzer_penwidth)
current_element().attributes.fill = fill;
}
+void SVGAnalyzer::set_fill_opacity(double fill_opacity) {
+ current_element().attributes.fill_opacity = fill_opacity;
+}
+
void SVGAnalyzer::set_height(double height) {
current_element().attributes.height = height;
}
void set_font_family(std::string_view font_family) override;
void set_font_size(double font_size) override;
void set_fill(std::string_view fill) override;
+ void set_fill_opacity(double fill_opacity) override;
void set_height(double height) override;
void set_id(std::string_view id) override;
void set_rx(double rx) override;
virtual void set_font_family(std::string_view font_family) = 0;
virtual void set_font_size(double font_size) = 0;
virtual void set_fill(std::string_view fill) = 0;
+ virtual void set_fill_opacity(double fill_opacity) = 0;
virtual void set_height(double height) = 0;
virtual void set_id(std::string_view id) = 0;
virtual void set_rx(double rx) = 0;
return fmt::format(R"(id="{}")", attributes.id);
}
+std::string SVG::SVGElement::fill_opacity_attribute_to_string() const {
+ if (attributes.fill_opacity == 1) {
+ // Graphviz doesn't set `fill-opacity` to 1 since that's the default
+ return "";
+ }
+
+ if (attributes.fill_opacity == 0) {
+ // Graphviz doesn't set `fill-opacity` to 0 since in that case it sets
+ // `fill` to "none" instead
+ return "";
+ }
+
+ return fmt::format(R"(fill-opacity="{}")", attributes.fill_opacity);
+}
+
std::string SVG::SVGElement::points_attribute_to_string() const {
std::string points_attribute_str = R"|(points=")|";
const char *separator = "";
switch (type) {
case SVG::SVGElementType::Ellipse:
append_attribute(attributes_str, fill_attribute_to_string());
+ append_attribute(attributes_str, fill_opacity_attribute_to_string());
append_attribute(attributes_str, stroke_attribute_to_string());
append_attribute(attributes_str, stroke_width_attribute_to_string());
append_attribute(attributes_str, stroke_opacity_attribute_to_string());
break;
case SVG::SVGElementType::Path: {
append_attribute(attributes_str, fill_attribute_to_string());
+ append_attribute(attributes_str, fill_opacity_attribute_to_string());
append_attribute(attributes_str, stroke_attribute_to_string());
append_attribute(attributes_str, stroke_width_attribute_to_string());
append_attribute(attributes_str, stroke_opacity_attribute_to_string());
}
case SVG::SVGElementType::Polygon:
append_attribute(attributes_str, fill_attribute_to_string());
+ append_attribute(attributes_str, fill_opacity_attribute_to_string());
append_attribute(attributes_str, stroke_attribute_to_string());
append_attribute(attributes_str, stroke_width_attribute_to_string());
append_attribute(attributes_str, stroke_opacity_attribute_to_string());
double cx;
double cy;
std::string fill;
+ double fill_opacity = 1;
std::string font_family;
double font_size;
double height;
const std::string &attribute) const;
std::string id_attribute_to_string() const;
std::string fill_attribute_to_string() const;
+ std::string fill_opacity_attribute_to_string() const;
std::string points_attribute_to_string() const;
std::string stroke_attribute_to_string() const;
std::string stroke_opacity_attribute_to_string() const;
m_svg_analyzer->set_fill(to_color_string(color));
}
+void SvgppContext::set(svgpp::tag::attribute::fill_opacity, const double v) {
+ m_svg_analyzer->set_fill_opacity(v);
+}
+
void SvgppContext::set(svgpp::tag::attribute::stroke, svgpp::tag::value::none) {
m_svg_analyzer->set_stroke("none");
}
throw std::runtime_error{
"this flavor of the 'fill' attribute is not yet implemented"};
};
+
+ void set(svgpp::tag::attribute::fill_opacity, double v);
void set(svgpp::tag::attribute::stroke, svgpp::tag::value::none);
void set(svgpp::tag::attribute::stroke, svgpp::tag::value::currentColor);
void set(svgpp::tag::attribute::stroke, color_t color,
svgpp::tag::attribute::cy, //
svgpp::tag::attribute::d, //
svgpp::tag::attribute::fill, //
+ svgpp::tag::attribute::fill_opacity, //
svgpp::tag::attribute::font_family, //
svgpp::tag::attribute::font_size, //
svgpp::tag::attribute::height, //
INFO(fmt::format("Color: {}", color));
const auto color_attr = color.empty() ? "" : fmt::format(" color={}", color);
- // FIXME: Edge arrowheads use `color` also for fill when `fillcolor` is not
- // set. This can result in `fill-opacity' being set which we do not yet
- // support. We therefore temporarily avoid specifying an edge color when
- // opacity is not 0 or 100 %.
- const auto edge_color_attr =
- !color.ends_with("00\"") && !color.ends_with("ff\"")
- ? ""
- : fmt::format(" color={}", color);
-
auto dot = fmt::format("digraph g1 {{node [shape={}{}]; edge [{}]; a -> b}}",
- shape, color_attr, edge_color_attr);
+ shape, color_attr, color_attr);
- if (shape == "point" && !color.ends_with("00\"") &&
- !color.ends_with("ff\"")) {
- // FIXME: The `point` shape implicitly uses style="filled" and this in turn
- // causes `color` to be used for fill when `fillcolor` is not set.
- // This can result in `fill-opacity' being set which we do not yet
- // support. We therefore avoid checking the SVG for the `point` shape when
- // the opacity is not 0 or 100 %.
- } else {
- SVGAnalyzer::make_from_dot(dot).re_create_and_verify_svg();
- }
+ SVGAnalyzer::make_from_dot(dot).re_create_and_verify_svg();
}
--- /dev/null
+#include <string_view>
+
+#include <catch2/catch.hpp>
+#include <fmt/format.h>
+
+#include "svg_analyzer.h"
+#include "test_utilities.h"
+
+TEST_CASE("SvgAnalyzer fillcolor",
+ "Test that the SvgAnalyzer can recreate the original SVG with the "
+ "correct `fill` and `fill-opacity` attributes when the Graphviz "
+ "`fillcolor` attribute is used for nodes and edges ") {
+
+ const auto shape = GENERATE(from_range(all_node_shapes));
+ INFO(fmt::format("Shape: {}", shape));
+
+ const std::string_view fillcolor =
+ GENERATE("", "\"#10204000\"", "\"#10204080\"", "\"#102040ff\"");
+ INFO(fmt::format("Fillcolor: {}", fillcolor));
+ const auto fillcolor_attr =
+ fillcolor.empty() ? "" : fmt::format(" fillcolor={}", fillcolor);
+ const std::string_view node_style = fillcolor.empty() ? "" : "filled";
+ const auto node_style_attr =
+ node_style.empty() ? "" : fmt::format(" style={}", node_style);
+
+ auto dot =
+ fmt::format("digraph g1 {{node [shape={}{}{}]; edge [{}]; a -> b}}",
+ shape, node_style_attr, fillcolor_attr, fillcolor_attr);
+
+ SVGAnalyzer::make_from_dot(dot).re_create_and_verify_svg();
+}