SVG - implement Cairo group renderer [WIP] [skip ci]

This commit is contained in:
Artem Pavlenko 2022-11-26 11:13:37 +00:00
parent 7c24ff09ee
commit 0e5e77121c
7 changed files with 174 additions and 59 deletions

View file

@ -34,7 +34,7 @@ class cairo_context;
void render_vector_marker(cairo_context& context,
svg_path_adapter& svg_path,
svg_attribute_type const& attributes,
svg::group const& group_attr,
box2d<double> const& bbox,
agg::trans_affine const& tr,
double opacity);

View file

@ -56,11 +56,11 @@ struct cairo_renderer_process_visitor_p
cairo_context context(cairo);
svg_storage_type& svg = *marker.get_data();
svg_attribute_type const& svg_attributes = svg.attributes();
auto const& svg_group = svg.svg_group();
svg::vertex_stl_adapter<svg::svg_path_storage> stl_storage(svg.source());
svg::svg_path_adapter svg_path(stl_storage);
render_vector_marker(context, svg_path, svg_attributes, bbox, tr, opacity_);
render_vector_marker(context, svg_path, svg_group, bbox, tr, opacity_);
return surface;
}

View file

@ -30,23 +30,46 @@
#include <mapnik/svg/svg_path_attributes.hpp>
namespace mapnik {
void render_vector_marker(cairo_context& context,
svg_path_adapter& svg_path,
svg_attribute_type const& attributes,
box2d<double> const& bbox,
agg::trans_affine const& tr,
double opacity)
namespace {
struct group_renderer
{
agg::trans_affine transform;
group_renderer(agg::trans_affine const& transform,
cairo_context& context,
svg_path_adapter& svg_path,
box2d<double> const& bbox)
: transform_(transform)
, context_(context)
, svg_path_(svg_path)
, bbox_(bbox)
{}
for (auto const& attr : attributes)
void operator()(svg::group const& g) const
{
double opacity = g.opacity;
if (opacity < 1.0)
{
for (auto const& elem : g.elements)
{
mapbox::util::apply_visitor(group_renderer(transform_, context_, svg_path_, bbox_), elem);
}
}
else
{
for (auto const& elem : g.elements)
{
mapbox::util::apply_visitor(group_renderer(transform_, context_, svg_path_, bbox_), elem);
}
}
}
void operator()(svg::path_attributes const& attr) const
{
if (!attr.visibility_flag)
continue;
cairo_save_restore guard(context);
transform = attr.transform;
transform *= tr;
return;
cairo_save_restore guard(context_);
agg::trans_affine transform = attr.transform;
transform *= transform_;
// TODO - this 'is_valid' check is not used in the AGG renderer and also
// appears to lead to bogus results with
@ -58,66 +81,96 @@ void render_vector_marker(cairo_context& context,
transform.store_to(m);
cairo_matrix_t matrix;
cairo_matrix_init(&matrix, m[0], m[1], m[2], m[3], m[4], m[5]);
context.transform(matrix);
context_.transform(matrix);
}
if (attr.fill_flag || attr.fill_gradient.get_gradient_type() != NO_GRADIENT)
{
context.add_agg_path(svg_path, attr.index);
context_.add_agg_path(svg_path_, attr.index);
if (attr.even_odd_flag)
{
context.set_fill_rule(CAIRO_FILL_RULE_EVEN_ODD);
context_.set_fill_rule(CAIRO_FILL_RULE_EVEN_ODD);
}
else
{
context.set_fill_rule(CAIRO_FILL_RULE_WINDING);
context_.set_fill_rule(CAIRO_FILL_RULE_WINDING);
}
if (attr.fill_gradient.get_gradient_type() != NO_GRADIENT)
{
cairo_gradient g(attr.fill_gradient, attr.fill_opacity * attr.opacity * opacity);
cairo_gradient g(attr.fill_gradient, attr.fill_opacity * attr.opacity);
context.set_gradient(g, bbox);
context.fill();
context_.set_gradient(g, bbox_);
context_.fill();
}
else if (attr.fill_flag)
{
double fill_opacity = attr.fill_opacity * attr.opacity * opacity * attr.fill_color.opacity();
context.set_color(attr.fill_color.r / 255.0,
attr.fill_color.g / 255.0,
attr.fill_color.b / 255.0,
fill_opacity);
context.fill();
double fill_opacity = attr.fill_opacity * attr.opacity * attr.fill_color.opacity();
context_.set_color(attr.fill_color.r / 255.0,
attr.fill_color.g / 255.0,
attr.fill_color.b / 255.0,
fill_opacity);
context_.fill();
}
}
if (attr.stroke_gradient.get_gradient_type() != NO_GRADIENT || attr.stroke_flag)
{
context.add_agg_path(svg_path, attr.index);
context_.add_agg_path(svg_path_, attr.index);
if (attr.stroke_gradient.get_gradient_type() != NO_GRADIENT)
{
context.set_line_width(attr.stroke_width);
context.set_line_cap(line_cap_enum(attr.line_cap));
context.set_line_join(line_join_enum(attr.line_join));
context.set_miter_limit(attr.miter_limit);
cairo_gradient g(attr.stroke_gradient, attr.fill_opacity * attr.opacity * opacity);
context.set_gradient(g, bbox);
context.stroke();
context_.set_line_width(attr.stroke_width);
context_.set_line_cap(line_cap_enum(attr.line_cap));
context_.set_line_join(line_join_enum(attr.line_join));
context_.set_miter_limit(attr.miter_limit);
cairo_gradient g(attr.stroke_gradient, attr.fill_opacity * attr.opacity);
context_.set_gradient(g, bbox_);
context_.stroke();
}
else if (attr.stroke_flag)
{
double stroke_opacity = attr.stroke_opacity * attr.opacity * opacity * attr.stroke_color.opacity();
context.set_color(attr.stroke_color.r / 255.0,
attr.stroke_color.g / 255.0,
attr.stroke_color.b / 255.0,
stroke_opacity);
context.set_line_width(attr.stroke_width);
context.set_line_cap(line_cap_enum(attr.line_cap));
context.set_line_join(line_join_enum(attr.line_join));
context.set_miter_limit(attr.miter_limit);
context.stroke();
double stroke_opacity = attr.stroke_opacity * attr.opacity * attr.stroke_color.opacity();
context_.set_color(attr.stroke_color.r / 255.0,
attr.stroke_color.g / 255.0,
attr.stroke_color.b / 255.0,
stroke_opacity);
context_.set_line_width(attr.stroke_width);
context_.set_line_cap(line_cap_enum(attr.line_cap));
context_.set_line_join(line_join_enum(attr.line_join));
context_.set_miter_limit(attr.miter_limit);
context_.stroke();
}
}
}
agg::trans_affine const& transform_;
cairo_context& context_;
svg_path_adapter& svg_path_;
box2d<double> const& bbox_;
};
} // namespace
void render_vector_marker(cairo_context& context,
svg_path_adapter& svg_path,
svg::group const& group_attrs,
box2d<double> const& bbox,
agg::trans_affine const& tr,
double opacity)
{
double adjusted_opacity = opacity * group_attrs.opacity; // adjust top level opacity
if (adjusted_opacity < 1.0)
{
for (auto const& elem : group_attrs.elements)
{
mapbox::util::apply_visitor(group_renderer(tr, context, svg_path, bbox), elem);
}
}
else
{
for (auto const& elem : group_attrs.elements)
{
mapbox::util::apply_visitor(group_renderer(tr, context, svg_path, bbox), elem);
}
}
}
} // namespace mapnik

View file

@ -269,11 +269,10 @@ struct cairo_render_marker_visitor
marker_tr *= tr_;
}
marker_tr *= agg::trans_affine_scaling(common_.scale_factor_);
auto const& attributes = vmarker->attributes();
svg::vertex_stl_adapter<svg::svg_path_storage> stl_storage(vmarker->source());
svg::svg_path_adapter svg_path(stl_storage);
marker_tr.translate(pos_.x, pos_.y);
render_vector_marker(context_, svg_path, attributes, bbox, marker_tr, opacity_);
render_vector_marker(context_, svg_path, vmarker->svg_group(), bbox, marker_tr, opacity_);
}
}

View file

@ -73,7 +73,7 @@ struct thunk_renderer : render_thunk_list_dispatch
offset_tr.translate(offset_.x, offset_.y);
mapnik::render_vector_marker(context_,
svg_path,
thunk.attrs_,
thunk.group_attrs_,
thunk.src_->bounding_box(),
offset_tr,
thunk.opacity_);

View file

@ -40,11 +40,11 @@ struct cairo_markers_renderer_context : markers_renderer_context
virtual void render_marker(svg_path_ptr const& src,
svg_path_adapter& path,
svg_attribute_type const& attrs,
svg::group const& group_attrs,
markers_dispatch_params const& params,
agg::trans_affine const& marker_tr)
{
render_vector_marker(ctx_, path, attrs, src->bounding_box(), marker_tr, params.opacity);
render_vector_marker(ctx_, path, group_attrs, src->bounding_box(), marker_tr, params.opacity);
}
virtual void

View file

@ -36,6 +36,55 @@
namespace // internal
{
template<int MAX>
struct group_attribute_visitor
{
group_attribute_visitor(int& index)
: index_(index)
{}
void operator()(mapnik::svg::group const& g) const
{
std::cerr << " <g opacity=" << g.opacity << ">" << std::endl;
for (auto const& elem : g.elements)
{
mapbox::util::apply_visitor(group_attribute_visitor<MAX>(index_), elem);
}
std::cerr << " </g>" << std::endl;
}
void operator()(mapnik::svg::path_attributes const& attr) const
{
if (index_++ == MAX)
{
std::cerr << " <attribute index=" << attr.index << ">" << std::endl;
std::cerr << " <opacity>" << attr.opacity << "</opacity>" << std::endl;
// std::cerr << " <fill>" << attr.fill_color << "</fill>" << std::endl;
std::cerr << " <fill-opacity>" << attr.fill_opacity << "</fill-opacity>" << std::endl;
// std::cerr << " <stroke>" << attr.stroke_color << "</stroke>" << std::endl;
std::cerr << " <stroke-width>" << attr.stroke_width << "</stroke-width>" << std::endl;
std::cerr << " <stroke-opacity>" << attr.stroke_opacity << "</stroke-opacity>" << std::endl;
std::cerr << " </attribute>" << std::endl;
}
else
{
std::cerr << " </skip>" << std::endl;
}
}
int& index_;
};
template<int MAX>
void group_attribute_printer(mapnik::svg::group const& g)
{
std::cerr << "<svg opacity=" << g.opacity << ">" << std::endl;
int index = 0;
for (auto const& elem : g.elements)
{
mapbox::util::apply_visitor(group_attribute_visitor<MAX>(index), elem);
}
std::cerr << "</svg>" << std::endl;
}
struct test_parser
{
mapnik::svg_storage_type path;
@ -265,8 +314,9 @@ TEST_CASE("SVG parser")
agg::line_cap_e expected_cap(agg::square_cap);
REQUIRE(group_attrs.elements.size() == 1);
// FIXME
// REQUIRE(attrs[0].line_cap == expected_cap);
// REQUIRE(attrs[0].line_cap == expected_cap);
group_attribute_printer<0>(group_attrs);
double x, y;
unsigned cmd;
std::vector<std::tuple<double, double, unsigned>> vec;
@ -445,7 +495,7 @@ TEST_CASE("SVG parser")
auto bbox = svg.bounding_box();
REQUIRE(
bbox ==
mapnik::box2d<double>(0.3543307086614174, 0.3543307086614174, 424.8425196850394059, 141.3779527559055396));
mapnik::box2d<double>(0.3779527559055118, 0.3779527559055118, 453.1653543307086807, 150.8031496062992005));
auto storage = svg.get_data();
REQUIRE(storage);
mapnik::svg::vertex_stl_adapter<mapnik::svg::svg_path_storage> stl_storage(storage->source());
@ -479,7 +529,9 @@ TEST_CASE("SVG parser")
REQUIRE(marker->is<mapnik::marker_svg>());
mapnik::marker_svg const& svg = mapnik::util::get<mapnik::marker_svg>(*marker);
auto bbox = svg.bounding_box();
REQUIRE(bbox == mapnik::box2d<double>(1.0, 1.0, 1199.0, 399.0));
REQUIRE(
bbox ==
mapnik::box2d<double>(0.3779527559055118, 0.3779527559055118, 453.1653543307086807, 150.8031496062992005));
auto storage = svg.get_data();
REQUIRE(storage);
mapnik::svg::vertex_stl_adapter<mapnik::svg::svg_path_storage> stl_storage(storage->source());
@ -517,7 +569,9 @@ TEST_CASE("SVG parser")
REQUIRE(marker->is<mapnik::marker_svg>());
mapnik::marker_svg const& svg = mapnik::util::get<mapnik::marker_svg>(*marker);
auto bbox = svg.bounding_box();
REQUIRE(bbox == mapnik::box2d<double>(1.0, 1.0, 1199.0, 399.0));
REQUIRE(
bbox ==
mapnik::box2d<double>(0.3779527559055118, 0.3779527559055118, 453.1653543307086807, 150.8031496062992005));
auto storage = svg.get_data();
REQUIRE(storage);
mapnik::svg::vertex_stl_adapter<mapnik::svg::svg_path_storage> stl_storage(storage->source());
@ -554,7 +608,10 @@ TEST_CASE("SVG parser")
REQUIRE(marker->is<mapnik::marker_svg>());
mapnik::marker_svg const& svg = mapnik::util::get<mapnik::marker_svg>(*marker);
auto bbox = svg.bounding_box();
REQUIRE(bbox == mapnik::box2d<double>(1.0, 1.0, 799.0, 599.0));
REQUIRE(
bbox ==
mapnik::box2d<double>(75.7795275590551114, 0.1889763779527559, 226.5826771653543119, 113.1968503937007853));
auto storage = svg.get_data();
REQUIRE(storage);
mapnik::svg::vertex_stl_adapter<mapnik::svg::svg_path_storage> stl_storage(storage->source());
@ -650,13 +707,17 @@ TEST_CASE("SVG parser")
REQUIRE(marker->is<mapnik::marker_svg>());
mapnik::marker_svg const& svg = mapnik::util::get<mapnik::marker_svg>(*marker);
auto bbox = svg.bounding_box();
REQUIRE(bbox == mapnik::box2d<double>(1.0, 1.0, 699.0, 199.0));
REQUIRE(
bbox ==
mapnik::box2d<double>(0.3779527559055118, 0.3779527559055118, 264.1889763779527698, 75.2125984251968447));
auto storage = svg.get_data();
REQUIRE(storage);
mapnik::svg::vertex_stl_adapter<mapnik::svg::svg_path_storage> stl_storage(storage->source());
auto const& group_attrs = storage->svg_group();
REQUIRE(group_attrs.elements.size() == 3);
// REQUIRE(group_attrs.elements.size() == 3);
// FIXME
// REQUIRE(attrs[1].fill_gradient == attrs[2].fill_gradient);
@ -700,7 +761,9 @@ TEST_CASE("SVG parser")
REQUIRE(marker->is<mapnik::marker_svg>());
mapnik::marker_svg const& svg = mapnik::util::get<mapnik::marker_svg>(*marker);
auto bbox = svg.bounding_box();
REQUIRE(bbox == mapnik::box2d<double>(1.0, 1.0, 799.0, 599.0));
REQUIRE(
bbox ==
mapnik::box2d<double>(75.7795275590551114, 0.1889763779527559, 226.5826771653543119, 113.1968503937007853));
auto storage = svg.get_data();
REQUIRE(storage);