From 0e5e77121ce4322aa14e708f80a6ec705cc24243 Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Sat, 26 Nov 2022 11:13:37 +0000 Subject: [PATCH] SVG - implement Cairo group renderer [WIP] [skip ci] --- include/mapnik/cairo/cairo_render_vector.hpp | 2 +- .../mapnik/cairo/render_polygon_pattern.hpp | 4 +- src/cairo/cairo_render_vector.cpp | 139 ++++++++++++------ src/cairo/cairo_renderer.cpp | 3 +- src/cairo/process_group_symbolizer.cpp | 2 +- src/cairo/process_markers_symbolizer.cpp | 4 +- test/unit/svg/svg_parser_test.cpp | 79 +++++++++- 7 files changed, 174 insertions(+), 59 deletions(-) diff --git a/include/mapnik/cairo/cairo_render_vector.hpp b/include/mapnik/cairo/cairo_render_vector.hpp index e28f499f4..9fca4b617 100644 --- a/include/mapnik/cairo/cairo_render_vector.hpp +++ b/include/mapnik/cairo/cairo_render_vector.hpp @@ -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 const& bbox, agg::trans_affine const& tr, double opacity); diff --git a/include/mapnik/cairo/render_polygon_pattern.hpp b/include/mapnik/cairo/render_polygon_pattern.hpp index 89dba18e3..9f2885624 100644 --- a/include/mapnik/cairo/render_polygon_pattern.hpp +++ b/include/mapnik/cairo/render_polygon_pattern.hpp @@ -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 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; } diff --git a/src/cairo/cairo_render_vector.cpp b/src/cairo/cairo_render_vector.cpp index 49dde34ef..f5c9adec9 100644 --- a/src/cairo/cairo_render_vector.cpp +++ b/src/cairo/cairo_render_vector.cpp @@ -30,23 +30,46 @@ #include namespace mapnik { -void render_vector_marker(cairo_context& context, - svg_path_adapter& svg_path, - svg_attribute_type const& attributes, - box2d 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 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 const& bbox_; +}; + +} // namespace + +void render_vector_marker(cairo_context& context, + svg_path_adapter& svg_path, + svg::group const& group_attrs, + box2d 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 diff --git a/src/cairo/cairo_renderer.cpp b/src/cairo/cairo_renderer.cpp index 952eb3755..310c0eed3 100644 --- a/src/cairo/cairo_renderer.cpp +++ b/src/cairo/cairo_renderer.cpp @@ -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 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_); } } diff --git a/src/cairo/process_group_symbolizer.cpp b/src/cairo/process_group_symbolizer.cpp index 637d20d9e..eb0724dfe 100644 --- a/src/cairo/process_group_symbolizer.cpp +++ b/src/cairo/process_group_symbolizer.cpp @@ -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_); diff --git a/src/cairo/process_markers_symbolizer.cpp b/src/cairo/process_markers_symbolizer.cpp index 895884598..8f4f7da1b 100644 --- a/src/cairo/process_markers_symbolizer.cpp +++ b/src/cairo/process_markers_symbolizer.cpp @@ -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 diff --git a/test/unit/svg/svg_parser_test.cpp b/test/unit/svg/svg_parser_test.cpp index fc2803b23..6e05db806 100644 --- a/test/unit/svg/svg_parser_test.cpp +++ b/test/unit/svg/svg_parser_test.cpp @@ -36,6 +36,55 @@ namespace // internal { +template +struct group_attribute_visitor +{ + group_attribute_visitor(int& index) + : index_(index) + {} + + void operator()(mapnik::svg::group const& g) const + { + std::cerr << " " << std::endl; + for (auto const& elem : g.elements) + { + mapbox::util::apply_visitor(group_attribute_visitor(index_), elem); + } + std::cerr << " " << std::endl; + } + void operator()(mapnik::svg::path_attributes const& attr) const + { + if (index_++ == MAX) + { + std::cerr << " " << std::endl; + std::cerr << " " << attr.opacity << "" << std::endl; + // std::cerr << " " << attr.fill_color << "" << std::endl; + std::cerr << " " << attr.fill_opacity << "" << std::endl; + // std::cerr << " " << attr.stroke_color << "" << std::endl; + std::cerr << " " << attr.stroke_width << "" << std::endl; + std::cerr << " " << attr.stroke_opacity << "" << std::endl; + std::cerr << " " << std::endl; + } + else + { + std::cerr << " " << std::endl; + } + } + int& index_; +}; + +template +void group_attribute_printer(mapnik::svg::group const& g) +{ + std::cerr << "" << std::endl; + int index = 0; + for (auto const& elem : g.elements) + { + mapbox::util::apply_visitor(group_attribute_visitor(index), elem); + } + std::cerr << "" << 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> vec; @@ -445,7 +495,7 @@ TEST_CASE("SVG parser") auto bbox = svg.bounding_box(); REQUIRE( bbox == - mapnik::box2d(0.3543307086614174, 0.3543307086614174, 424.8425196850394059, 141.3779527559055396)); + mapnik::box2d(0.3779527559055118, 0.3779527559055118, 453.1653543307086807, 150.8031496062992005)); auto storage = svg.get_data(); REQUIRE(storage); mapnik::svg::vertex_stl_adapter stl_storage(storage->source()); @@ -479,7 +529,9 @@ TEST_CASE("SVG parser") REQUIRE(marker->is()); mapnik::marker_svg const& svg = mapnik::util::get(*marker); auto bbox = svg.bounding_box(); - REQUIRE(bbox == mapnik::box2d(1.0, 1.0, 1199.0, 399.0)); + REQUIRE( + bbox == + mapnik::box2d(0.3779527559055118, 0.3779527559055118, 453.1653543307086807, 150.8031496062992005)); auto storage = svg.get_data(); REQUIRE(storage); mapnik::svg::vertex_stl_adapter stl_storage(storage->source()); @@ -517,7 +569,9 @@ TEST_CASE("SVG parser") REQUIRE(marker->is()); mapnik::marker_svg const& svg = mapnik::util::get(*marker); auto bbox = svg.bounding_box(); - REQUIRE(bbox == mapnik::box2d(1.0, 1.0, 1199.0, 399.0)); + REQUIRE( + bbox == + mapnik::box2d(0.3779527559055118, 0.3779527559055118, 453.1653543307086807, 150.8031496062992005)); auto storage = svg.get_data(); REQUIRE(storage); mapnik::svg::vertex_stl_adapter stl_storage(storage->source()); @@ -554,7 +608,10 @@ TEST_CASE("SVG parser") REQUIRE(marker->is()); mapnik::marker_svg const& svg = mapnik::util::get(*marker); auto bbox = svg.bounding_box(); - REQUIRE(bbox == mapnik::box2d(1.0, 1.0, 799.0, 599.0)); + + REQUIRE( + bbox == + mapnik::box2d(75.7795275590551114, 0.1889763779527559, 226.5826771653543119, 113.1968503937007853)); auto storage = svg.get_data(); REQUIRE(storage); mapnik::svg::vertex_stl_adapter stl_storage(storage->source()); @@ -650,13 +707,17 @@ TEST_CASE("SVG parser") REQUIRE(marker->is()); mapnik::marker_svg const& svg = mapnik::util::get(*marker); auto bbox = svg.bounding_box(); - REQUIRE(bbox == mapnik::box2d(1.0, 1.0, 699.0, 199.0)); + + REQUIRE( + bbox == + mapnik::box2d(0.3779527559055118, 0.3779527559055118, 264.1889763779527698, 75.2125984251968447)); auto storage = svg.get_data(); REQUIRE(storage); mapnik::svg::vertex_stl_adapter 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 const& svg = mapnik::util::get(*marker); auto bbox = svg.bounding_box(); - REQUIRE(bbox == mapnik::box2d(1.0, 1.0, 799.0, 599.0)); + REQUIRE( + bbox == + mapnik::box2d(75.7795275590551114, 0.1889763779527559, 226.5826771653543119, 113.1968503937007853)); auto storage = svg.get_data(); REQUIRE(storage);