CREATE_TEST(subgraph_layout)
CREATE_TEST(subgraphs)
CREATE_TEST(svg_analyzer)
+CREATE_TEST(svg_analyzer_color)
CREATE_TEST(svg_analyzer_penwidth)
current_element().attributes.stroke = stroke;
}
+void SVGAnalyzer::set_stroke_opacity(double stroke_opacity) {
+ current_element().attributes.stroke_opacity = stroke_opacity;
+}
+
void SVGAnalyzer::set_stroke_width(double stroke_width) {
current_element().attributes.stroke_width = stroke_width;
}
void set_ry(double ry) override;
void set_class(std::string_view) override;
void set_stroke(std::string_view stroke) override;
+ void set_stroke_opacity(double stroke_opacity) override;
void set_stroke_width(double stroke_width) override;
void set_point(std::pair<double, double> point) override;
void set_text(std::string_view text) override;
virtual void set_ry(double ry) = 0;
virtual void set_point(std::pair<double, double> point) = 0;
virtual void set_stroke(std::string_view stroke) = 0;
+ virtual void set_stroke_opacity(double stroke_opacity) = 0;
virtual void set_stroke_width(double stroke_width) = 0;
virtual void set_text(std::string_view text) = 0;
virtual void set_text_anchor(std::string_view text_anchor) = 0;
stroke_to_graphviz_color(attributes.stroke));
}
+std::string SVG::SVGElement::stroke_opacity_attribute_to_string() const {
+ if (attributes.stroke_opacity == 1) {
+ // Graphviz doesn't set `stroke-opacity` to 1 since that's the default
+ return "";
+ }
+
+ if (attributes.stroke_opacity == 0) {
+ // Graphviz doesn't set `stroke-opacity` to 0 since in that case it sets
+ // `stroke` to "none" instead
+ return "";
+ }
+
+ return fmt::format(R"(stroke-opacity="{}")", attributes.stroke_opacity);
+}
+
std::string SVG::SVGElement::stroke_width_attribute_to_string() const {
if (attributes.stroke_width == 1) {
// Graphviz doesn't set `stroke-width` to 1 since that's the default
append_attribute(attributes_str, fill_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());
attributes_str +=
fmt::format(R"( cx="{}" cy="{}" rx="{}" ry="{}")", attributes.cx,
attributes.cy, attributes.rx, attributes.ry);
append_attribute(attributes_str, fill_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());
attributes_str += R"|( d=")|";
auto command = 'M';
for (const auto &point : path_points) {
append_attribute(attributes_str, fill_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());
append_attribute(attributes_str, points_attribute_to_string());
break;
case SVG::SVGElementType::Polyline:
append_attribute(attributes_str, fill_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());
append_attribute(attributes_str, points_attribute_to_string());
break;
case SVG::SVGElementType::Svg:
double rx;
double ry;
std::string stroke;
+ double stroke_opacity = 1;
double stroke_width = 1;
std::string text_anchor;
std::optional<SVGMatrix> transform;
std::string fill_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;
std::string stroke_width_attribute_to_string() const;
std::string stroke_to_graphviz_color(const std::string &color) const;
SVG::SVGRect text_bbox() const;
m_svg_analyzer->set_stroke(to_color_string(color));
}
+void SvgppContext::set(svgpp::tag::attribute::stroke_opacity, const double v) {
+ m_svg_analyzer->set_stroke_opacity(v);
+}
+
void SvgppContext::set(svgpp::tag::attribute::stroke_width, const double v) {
m_svg_analyzer->set_stroke_width(v);
}
throw std::runtime_error{
"this flavor of the 'stroke' attribute is not yet implemented"};
};
+ void set(svgpp::tag::attribute::stroke_opacity, double v);
void set(svgpp::tag::attribute::stroke_width, double v);
void transform_matrix(const boost::array<double, 6> &matrix);
void set(svgpp::tag::attribute::r r, double v);
using processed_attributes_t =
boost::mpl::set<svgpp::traits::shapes_attributes_by_element,
- svgpp::tag::attribute::class_, //
- svgpp::tag::attribute::cx, //
- svgpp::tag::attribute::cy, //
- svgpp::tag::attribute::d, //
- svgpp::tag::attribute::fill, //
- svgpp::tag::attribute::font_family, //
- svgpp::tag::attribute::font_size, //
- svgpp::tag::attribute::height, //
- svgpp::tag::attribute::id, //
- svgpp::tag::attribute::points, //
- svgpp::tag::attribute::rx, //
- svgpp::tag::attribute::ry, //
- svgpp::tag::attribute::stroke, //
- svgpp::tag::attribute::stroke_width, //
- svgpp::tag::attribute::text_anchor, //
- svgpp::tag::attribute::transform, //
- svgpp::tag::attribute::viewBox, //
- svgpp::tag::attribute::width, //
- svgpp::tag::attribute::x, //
- svgpp::tag::attribute::y //
+ svgpp::tag::attribute::class_, //
+ svgpp::tag::attribute::cx, //
+ svgpp::tag::attribute::cy, //
+ svgpp::tag::attribute::d, //
+ svgpp::tag::attribute::fill, //
+ svgpp::tag::attribute::font_family, //
+ svgpp::tag::attribute::font_size, //
+ svgpp::tag::attribute::height, //
+ svgpp::tag::attribute::id, //
+ svgpp::tag::attribute::points, //
+ svgpp::tag::attribute::rx, //
+ svgpp::tag::attribute::ry, //
+ svgpp::tag::attribute::stroke, //
+ svgpp::tag::attribute::stroke_opacity, //
+ svgpp::tag::attribute::stroke_width, //
+ svgpp::tag::attribute::text_anchor, //
+ svgpp::tag::attribute::transform, //
+ svgpp::tag::attribute::viewBox, //
+ svgpp::tag::attribute::width, //
+ svgpp::tag::attribute::x, //
+ svgpp::tag::attribute::y //
>::type;
svgpp::document_traversal<
--- /dev/null
+#include <string_view>
+
+#include <catch2/catch.hpp>
+#include <fmt/format.h>
+
+#include "svg_analyzer.h"
+#include "test_utilities.h"
+
+TEST_CASE("SvgAnalyzer color",
+ "Test that the SvgAnalyzer can recreate the original "
+ "SVG with the correct `stroke` and `stroke-opacity` attributes when "
+ "the Graphviz `color` 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 color =
+ GENERATE("", "\"#10204000\"", "\"#10204080\"", "\"#102040ff\"");
+ 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);
+
+ 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();
+ }
+}