/***************************************************************************** * * This file is part of Mapnik (c++ mapping toolkit) * * Copyright (C) 2011 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ #if defined(HAVE_CAIRO) // mapnik #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // todo - only need some of this expensive header #include #include #include #include #include #include #include #include #include // mapnik symbolizer generics #include #include #include #include #include #include // cairo #include #include #include // boost #include // agg #include "agg_conv_clip_polyline.h" #include "agg_conv_clip_polygon.h" #include "agg_conv_smooth_poly1.h" #include "agg_rendering_buffer.h" #include "agg_pixfmt_rgba.h" // markers #include "agg_path_storage.h" #include "agg_ellipse.h" // stl #include #include namespace mapnik { struct cairo_save_restore { cairo_save_restore(cairo_context & context) : context_(context) { context_.save(); } ~cairo_save_restore() { context_.restore(); } cairo_context & context_; }; cairo_renderer_base::cairo_renderer_base(Map const& m, cairo_ptr const& cairo, attributes const& vars, double scale_factor, unsigned offset_x, unsigned offset_y) : m_(m), context_(cairo), common_(m, vars, offset_x, offset_y, m.width(), m.height(), scale_factor), face_manager_(common_.shared_font_engine_) { setup(m); } cairo_renderer_base::cairo_renderer_base(Map const& m, request const& req, cairo_ptr const& cairo, attributes const& vars, double scale_factor, unsigned offset_x, unsigned offset_y) : m_(m), context_(cairo), common_(req, vars, offset_x, offset_y, req.width(), req.height(), scale_factor), face_manager_(common_.shared_font_engine_) { setup(m); } cairo_renderer_base::cairo_renderer_base(Map const& m, cairo_ptr const& cairo, attributes const& vars, std::shared_ptr detector, double scale_factor, unsigned offset_x, unsigned offset_y) : m_(m), context_(cairo), common_(m, vars, offset_x, offset_y, m.width(), m.height(), scale_factor, detector), face_manager_(common_.shared_font_engine_) { MAPNIK_LOG_DEBUG(cairo_renderer) << "cairo_renderer_base: Scale=" << m.scale(); } template <> cairo_renderer::cairo_renderer(Map const& m, cairo_ptr const& cairo, double scale_factor, unsigned offset_x, unsigned offset_y) : feature_style_processor(m,scale_factor), cairo_renderer_base(m,cairo,attributes(),scale_factor,offset_x,offset_y) {} template <> cairo_renderer::cairo_renderer(Map const& m, cairo_surface_ptr const& surface, double scale_factor, unsigned offset_x, unsigned offset_y) : feature_style_processor(m,scale_factor), cairo_renderer_base(m,create_context(surface),attributes(),scale_factor,offset_x,offset_y) {} template <> cairo_renderer::cairo_renderer(Map const& m, request const& req, attributes const& vars, cairo_ptr const& cairo, double scale_factor, unsigned offset_x, unsigned offset_y) : feature_style_processor(m,scale_factor), cairo_renderer_base(m,req,cairo,vars,scale_factor,offset_x,offset_y) {} template <> cairo_renderer::cairo_renderer(Map const& m, request const& req, attributes const& vars, cairo_surface_ptr const& surface, double scale_factor, unsigned offset_x, unsigned offset_y) : feature_style_processor(m,scale_factor), cairo_renderer_base(m,req,create_context(surface),attributes(),scale_factor,offset_x,offset_y) {} template <> cairo_renderer::cairo_renderer(Map const& m, cairo_ptr const& cairo, std::shared_ptr detector, double scale_factor, unsigned offset_x, unsigned offset_y) : feature_style_processor(m,scale_factor), cairo_renderer_base(m,cairo,attributes(),detector,scale_factor,offset_x,offset_y) {} template <> cairo_renderer::cairo_renderer(Map const& m, cairo_surface_ptr const& surface, std::shared_ptr detector, double scale_factor, unsigned offset_x, unsigned offset_y) : feature_style_processor(m,scale_factor), cairo_renderer_base(m,create_context(surface),attributes(),detector,scale_factor,offset_x,offset_y) {} cairo_renderer_base::~cairo_renderer_base() {} void cairo_renderer_base::setup(Map const& map) { boost::optional bg = m_.background(); if (bg) { cairo_save_restore guard(context_); context_.set_color(*bg); context_.paint(); } boost::optional const& image_filename = map.background_image(); if (image_filename) { // NOTE: marker_cache returns premultiplied image, if needed boost::optional bg_marker = mapnik::marker_cache::instance().find(*image_filename,true); if (bg_marker && (*bg_marker)->is_bitmap()) { mapnik::image_ptr bg_image = *(*bg_marker)->get_bitmap_data(); int w = bg_image->width(); int h = bg_image->height(); if ( w > 0 && h > 0) { // repeat background-image both vertically and horizontally unsigned x_steps = unsigned(std::ceil(common_.width_/double(w))); unsigned y_steps = unsigned(std::ceil(common_.height_/double(h))); for (unsigned x=0;x void cairo_renderer::end_map_processing(Map const& ) { MAPNIK_LOG_DEBUG(cairo_renderer) << "cairo_renderer_base: End map processing"; context_.show_page(); } void cairo_renderer_base::start_layer_processing(layer const& lay, box2d const& query_extent) { MAPNIK_LOG_DEBUG(cairo_renderer) << "cairo_renderer_base: Start processing layer=" << lay.name() ; MAPNIK_LOG_DEBUG(cairo_renderer) << "cairo_renderer_base: -- datasource=" << lay.datasource().get(); MAPNIK_LOG_DEBUG(cairo_renderer) << "cairo_renderer_base: -- query_extent=" << query_extent; if (lay.clear_label_cache()) { common_.detector_->clear(); } common_.query_extent_ = query_extent; } void cairo_renderer_base::end_layer_processing(layer const&) { MAPNIK_LOG_DEBUG(cairo_renderer) << "cairo_renderer_base: End layer processing"; } void cairo_renderer_base::start_style_processing(feature_type_style const& st) { MAPNIK_LOG_DEBUG(cairo_renderer) << "cairo_renderer:start style processing"; } void cairo_renderer_base::end_style_processing(feature_type_style const& st) { MAPNIK_LOG_DEBUG(cairo_renderer) << "cairo_renderer:end style processing"; } void cairo_renderer_base::process(polygon_symbolizer const& sym, mapnik::feature_impl & feature, proj_transform const& prj_trans) { using conv_types = boost::mpl::vector; using vertex_converter_type = vertex_converter, cairo_context, polygon_symbolizer, CoordTransform, proj_transform, agg::trans_affine, conv_types, feature_impl>; cairo_save_restore guard(context_); composite_mode_e comp_op = get(sym, keys::comp_op, feature, common_.vars_, src_over); context_.set_operator(comp_op); render_polygon_symbolizer( sym, feature, prj_trans, common_, common_.query_extent_, context_, [&](color const &fill, double opacity) { context_.set_color(fill, opacity); // fill polygon context_.set_fill_rule(CAIRO_FILL_RULE_EVEN_ODD); context_.fill(); }); } void cairo_renderer_base::process(building_symbolizer const& sym, mapnik::feature_impl & feature, proj_transform const& prj_trans) { using path_type = coord_transform; cairo_save_restore guard(context_); composite_mode_e comp_op = get(sym, keys::comp_op, feature, common_.vars_, src_over); mapnik::color fill = get(sym, keys::fill, feature, common_.vars_, mapnik::color(128,128,128)); double opacity = get(sym, keys::fill_opacity, feature, common_.vars_, 1.0); double height = get(sym, keys::height, feature, common_.vars_, 0.0); context_.set_operator(comp_op); render_building_symbolizer( feature, height, [&](geometry_type &faces) { path_type faces_path(common_.t_, faces, prj_trans); context_.set_color(fill.red() * 0.8 / 255.0, fill.green() * 0.8 / 255.0, fill.blue() * 0.8 / 255.0, fill.alpha() * opacity / 255.0); context_.add_path(faces_path); context_.fill(); }, [&](geometry_type &frame) { path_type path(common_.t_, frame, prj_trans); context_.set_color(fill.red() * 0.8 / 255.0, fill.green() * 0.8/255.0, fill.blue() * 0.8 / 255.0, fill.alpha() * opacity / 255.0); context_.set_line_width(common_.scale_factor_); context_.add_path(path); context_.stroke(); }, [&](geometry_type &roof) { path_type roof_path(common_.t_, roof, prj_trans); context_.set_color(fill, opacity); context_.add_path(roof_path); context_.fill(); }); } void cairo_renderer_base::process(line_symbolizer const& sym, mapnik::feature_impl & feature, proj_transform const& prj_trans) { using conv_types = boost::mpl::vector; cairo_save_restore guard(context_); composite_mode_e comp_op = get(sym, keys::comp_op, feature, common_.vars_, src_over); bool clip = get(sym, keys::clip, feature, common_.vars_, false); double offset = get(sym, keys::offset, feature, common_.vars_, 0.0); double simplify_tolerance = get(sym, keys::simplify_tolerance, feature, common_.vars_, 0.0); double smooth = get(sym, keys::smooth, feature, common_.vars_, 0.0); mapnik::color stroke = get(sym, keys::stroke, feature, common_.vars_, mapnik::color(0,0,0)); double stroke_opacity = get(sym, keys::stroke_opacity, feature, common_.vars_, 1.0); line_join_enum stroke_join = get(sym, keys::stroke_linejoin, feature, common_.vars_, MITER_JOIN); line_cap_enum stroke_cap = get(sym, keys::stroke_linecap, feature, common_.vars_, BUTT_CAP); auto dash = get_optional(sym, keys::stroke_dasharray, feature, common_.vars_); double miterlimit = get(sym, keys::stroke_miterlimit, feature, common_.vars_, 4.0); double width = get(sym, keys::stroke_width, feature, common_.vars_, 1.0); context_.set_operator(comp_op); context_.set_color(stroke, stroke_opacity); context_.set_line_join(stroke_join); context_.set_line_cap(stroke_cap); context_.set_miter_limit(miterlimit); context_.set_line_width(width * common_.scale_factor_); if (dash) { context_.set_dash(*dash, common_.scale_factor_); } agg::trans_affine tr; auto geom_transform = get_optional(sym, keys::geometry_transform); if (geom_transform) { evaluate_transform(tr, feature, common_.vars_, *geom_transform, common_.scale_factor_); } box2d clipping_extent = common_.query_extent_; if (clip) { double padding = (double)(common_.query_extent_.width()/common_.width_); double half_stroke = width/2.0; if (half_stroke > 1) padding *= half_stroke; if (std::fabs(offset) > 0) padding *= std::fabs(offset) * 1.2; padding *= common_.scale_factor_; clipping_extent.pad(padding); } vertex_converter, cairo_context, line_symbolizer, CoordTransform, proj_transform, agg::trans_affine, conv_types, feature_impl> converter(clipping_extent,context_,sym,common_.t_,prj_trans,tr,feature,common_.vars_,common_.scale_factor_); if (clip) converter.set(); // optional clip (default: true) converter.set(); // always transform if (std::fabs(offset) > 0.0) converter.set(); // parallel offset converter.set(); // optional affine transform if (simplify_tolerance > 0.0) converter.set(); // optional simplify converter if (smooth > 0.0) converter.set(); // optional smooth converter for (geometry_type & geom : feature.paths()) { if (geom.size() > 1) { converter.apply(geom); } } // stroke context_.set_fill_rule(CAIRO_FILL_RULE_WINDING); context_.stroke(); } void cairo_renderer_base::render_box(box2d const& b) { cairo_save_restore guard(context_); context_.move_to(b.minx(), b.miny()); context_.line_to(b.minx(), b.maxy()); context_.line_to(b.maxx(), b.maxy()); context_.line_to(b.maxx(), b.miny()); context_.close_path(); context_.stroke(); } void render_vector_marker(cairo_context & context, pixel_position const& pos, svg::svg_path_adapter & svg_path, box2d const& bbox, agg::pod_bvector const & attributes, agg::trans_affine const& tr, double opacity, bool recenter) { using namespace mapnik::svg; agg::trans_affine mtx = tr; if (recenter) { coord c = bbox.center(); mtx = agg::trans_affine_translation(-c.x,-c.y); mtx *= tr; mtx.translate(pos.x, pos.y); } agg::trans_affine transform; for(unsigned i = 0; i < attributes.size(); ++i) { mapnik::svg::path_attributes const& attr = attributes[i]; if (!attr.visibility_flag) continue; cairo_save_restore guard(context); transform = attr.transform; transform *= mtx; // TODO - this 'is_valid' check is not used in the AGG renderer and also // appears to lead to bogus results with // tests/data/good_maps/markers_symbolizer_lines_file.xml if (/*transform.is_valid() && */ !transform.is_identity()) { double m[6]; 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); } if (attr.fill_flag || attr.fill_gradient.get_gradient_type() != NO_GRADIENT) { context.add_agg_path(svg_path,attr.index); if (attr.even_odd_flag) { context.set_fill_rule(CAIRO_FILL_RULE_EVEN_ODD); } else { 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); 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(); } } if (attr.stroke_gradient.get_gradient_type() != NO_GRADIENT || attr.stroke_flag) { 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(); } 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(); } } } } void cairo_renderer_base::render_marker(pixel_position const& pos, marker const& marker, agg::trans_affine const& tr, double opacity, bool recenter) { cairo_save_restore guard(context_); if (marker.is_vector()) { mapnik::svg_path_ptr vmarker = *marker.get_vector_data(); if (vmarker) { agg::trans_affine marker_tr = tr; marker_tr *=agg::trans_affine_scaling(common_.scale_factor_); box2d bbox = vmarker->bounding_box(); agg::pod_bvector const & attributes = vmarker->attributes(); svg::vertex_stl_adapter stl_storage(vmarker->source()); svg::svg_path_adapter svg_path(stl_storage); render_vector_marker(context_, pos, svg_path, bbox, attributes, marker_tr, opacity, recenter); } } else if (marker.is_bitmap()) { double width = (*marker.get_bitmap_data())->width(); double height = (*marker.get_bitmap_data())->height(); double cx = 0.5 * width; double cy = 0.5 * height; agg::trans_affine marker_tr; marker_tr *= agg::trans_affine_translation(-cx,-cy); marker_tr *= tr; marker_tr *= agg::trans_affine_scaling(common_.scale_factor_); marker_tr *= agg::trans_affine_translation(pos.x,pos.y); context_.add_image(marker_tr, **marker.get_bitmap_data(), opacity); } } void cairo_renderer_base::process(point_symbolizer const& sym, mapnik::feature_impl & feature, proj_transform const& prj_trans) { composite_mode_e comp_op = get(sym, keys::comp_op, feature, common_.vars_, src_over); cairo_save_restore guard(context_); context_.set_operator(comp_op); render_point_symbolizer( sym, feature, prj_trans, common_, [this](pixel_position const& pos, marker const& marker, agg::trans_affine const& tr, double opacity) { render_marker(pos, marker, tr, opacity); }); } void cairo_renderer_base::process(shield_symbolizer const& sym, mapnik::feature_impl & feature, proj_transform const& prj_trans) { text_symbolizer_helper helper( sym, feature, common_.vars_, prj_trans, common_.width_, common_.height_, common_.scale_factor_, common_.t_, common_.font_manager_, *common_.detector_, common_.query_extent_); cairo_save_restore guard(context_); composite_mode_e comp_op = get(sym, keys::comp_op, feature, common_.vars_, src_over); composite_mode_e halo_comp_op = get(sym, keys::halo_comp_op, feature, common_.vars_, src_over); double opacity = get(sym,keys::opacity,feature, common_.vars_, 1.0); placements_list const &placements = helper.get(); for (glyph_positions_ptr glyphs : placements) { if (glyphs->marker()) { pixel_position pos = glyphs->marker_pos(); render_marker(pos, *(glyphs->marker()->marker), glyphs->marker()->transform, opacity); } context_.add_text(glyphs, face_manager_, common_.font_manager_, comp_op, halo_comp_op, common_.scale_factor_); } } void cairo_renderer_base::process(line_pattern_symbolizer const& sym, mapnik::feature_impl & feature, proj_transform const& prj_trans) { using clipped_geometry_type = agg::conv_clip_polyline; using path_type = coord_transform; std::string filename = get(sym, keys::file, feature, common_.vars_); composite_mode_e comp_op = get(sym, keys::comp_op, feature, common_.vars_, src_over); boost::optional marker; if ( !filename.empty() ) { marker = marker_cache::instance().find(filename, true); } if (!marker || !(*marker)) return; unsigned width = (*marker)->width(); unsigned height = (*marker)->height(); cairo_save_restore guard(context_); context_.set_operator(comp_op); std::shared_ptr pattern; image_ptr image = nullptr; if ((*marker)->is_bitmap()) { pattern = std::make_unique(**((*marker)->get_bitmap_data())); context_.set_line_width(height* common_.scale_factor_); // FIXME: do we need to scale height here? } else { mapnik::rasterizer ras; agg::trans_affine image_tr = agg::trans_affine_scaling(common_.scale_factor_); auto image_transform = get_optional(sym, keys::image_transform); if (image_transform) evaluate_transform(image_tr, feature, common_.vars_, *image_transform); image = render_pattern(ras, **marker, image_tr); pattern = std::make_unique(*image); width = image->width(); height = image->height(); context_.set_line_width(height); } pattern->set_extend(CAIRO_EXTEND_REPEAT); pattern->set_filter(CAIRO_FILTER_BILINEAR); for (std::size_t i = 0; i < feature.num_geometries(); ++i) { geometry_type & geom = feature.get_geometry(i); if (geom.size() > 1) { clipped_geometry_type clipped(geom); clipped.clip_box(common_.query_extent_.minx(),common_.query_extent_.miny(),common_.query_extent_.maxx(),common_.query_extent_.maxy()); path_type path(common_.t_,clipped,prj_trans); double length(0); double x0(0), y0(0); double x, y; for (unsigned cm = path.vertex(&x, &y); cm != SEG_END; cm = path.vertex(&x, &y)) { if (cm == SEG_MOVETO) { length = 0.0; } else if (cm == SEG_LINETO) { double dx = x - x0; double dy = y - y0; double angle = std::atan2(dy, dx); double offset = std::fmod(length, width); cairo_matrix_t matrix; cairo_matrix_init_identity(&matrix); cairo_matrix_translate(&matrix,x0,y0); cairo_matrix_rotate(&matrix,angle); cairo_matrix_translate(&matrix,-offset,0.5*height); cairo_matrix_invert(&matrix); pattern->set_matrix(matrix); context_.set_pattern(*pattern); context_.move_to(x0, y0); context_.line_to(x, y); context_.stroke(); length = length + hypot(x - x0, y - y0); } x0 = x; y0 = y; } } } } void cairo_renderer_base::process(polygon_pattern_symbolizer const& sym, mapnik::feature_impl & feature, proj_transform const& prj_trans) { //using clipped_geometry_type = agg::conv_clip_polygon; //using path_type = coord_transform; cairo_save_restore guard(context_); composite_mode_e comp_op = get(sym, keys::comp_op, feature, common_.vars_, src_over); std::string filename = get(sym, keys::file, feature, common_.vars_); bool clip = get(sym, keys::clip, feature, common_.vars_, false); double simplify_tolerance = get(sym, keys::simplify_tolerance, feature, common_.vars_, 0.0); double smooth = get(sym, keys::smooth, feature, common_.vars_, 0.0); agg::trans_affine image_tr = agg::trans_affine_scaling(common_.scale_factor_); auto image_transform = get_optional(sym, keys::image_transform); if (image_transform) evaluate_transform(image_tr, feature, common_.vars_, *image_transform); context_.set_operator(comp_op); boost::optional marker = mapnik::marker_cache::instance().find(filename,true); if (!marker || !(*marker)) return; if ((*marker)->is_bitmap()) { cairo_pattern pattern(**((*marker)->get_bitmap_data())); pattern.set_extend(CAIRO_EXTEND_REPEAT); context_.set_pattern(pattern); } else { mapnik::rasterizer ras; image_ptr image = render_pattern(ras, **marker, image_tr); cairo_pattern pattern(*image); pattern.set_extend(CAIRO_EXTEND_REPEAT); context_.set_pattern(pattern); } pattern_alignment_enum alignment = get(sym, keys::alignment, feature, common_.vars_, GLOBAL_ALIGNMENT); //unsigned offset_x=0; //unsigned offset_y=0; //if (align == LOCAL_ALIGNMENT) //{ // double x0 = 0; // double y0 = 0; // if (feature.num_geometries() > 0) // { // clipped_geometry_type clipped(feature.get_geometry(0)); // clipped.clip_box(query_extent_.minx(),query_extent_.miny(),query_extent_.maxx(),query_extent_.maxy()); // path_type path(t_,clipped,prj_trans); // path.vertex(&x0,&y0); // } // offset_x = unsigned(width_ - x0); // offset_y = unsigned(height_ - y0); //} agg::trans_affine tr; auto geom_transform = get_optional(sym, keys::geometry_transform); if (geom_transform) { evaluate_transform(tr, feature, common_.vars_, *geom_transform, common_.scale_factor_); } using conv_types = boost::mpl::vector; vertex_converter, cairo_context, polygon_pattern_symbolizer, CoordTransform, proj_transform, agg::trans_affine, conv_types, feature_impl> converter(common_.query_extent_,context_,sym,common_.t_,prj_trans,tr,feature,common_.vars_,common_.scale_factor_); if (prj_trans.equal() && clip) converter.set(); //optional clip (default: true) converter.set(); //always transform converter.set(); if (simplify_tolerance > 0.0) converter.set(); // optional simplify converter if (smooth > 0.0) converter.set(); // optional smooth converter for ( geometry_type & geom : feature.paths()) { if (geom.size() > 2) { converter.apply(geom); } } // fill polygon context_.set_fill_rule(CAIRO_FILL_RULE_EVEN_ODD); context_.fill(); } void cairo_renderer_base::process(raster_symbolizer const& sym, mapnik::feature_impl & feature, proj_transform const& prj_trans) { cairo_save_restore guard(context_); render_raster_symbolizer( sym, feature, prj_trans, common_, [&](image_data_32 &target, composite_mode_e comp_op, double opacity, int start_x, int start_y) { context_.set_operator(comp_op); context_.add_image(start_x, start_y, target, opacity); } ); } namespace detail { template struct markers_dispatch : mapnik::noncopyable { markers_dispatch(SvgPath & marker, Attributes const& attributes, box2d const& bbox, agg::trans_affine const& marker_trans, markers_symbolizer const& sym, Detector & detector, double scale_factor, feature_impl const& feature, mapnik::attributes const& vars, bool snap_to_pixels, RendererContext const& renderer_context) :marker_(marker), attributes_(attributes), bbox_(bbox), marker_trans_(marker_trans), sym_(sym), detector_(detector), scale_factor_(scale_factor), feature_(feature), vars_(vars), ctx_(std::get<0>(renderer_context)) {} template void add_path(T & path) { marker_placement_enum placement_method = get( sym_, keys::markers_placement_type, feature_, vars_, MARKER_POINT_PLACEMENT); bool ignore_placement = get(sym_, keys::ignore_placement, feature_, vars_, false); bool allow_overlap = get(sym_, keys::allow_overlap, feature_, vars_, false); double opacity = get(sym_, keys::opacity, feature_, vars_, 1.0); double spacing = get(sym_, keys::spacing, feature_, vars_, 100.0); double max_error = get(sym_, keys::max_error, feature_, vars_, 0.2); coord2d center = bbox_.center(); agg::trans_affine_translation recenter(-center.x, -center.y); agg::trans_affine tr = recenter * marker_trans_; markers_placement_finder placement_finder( placement_method, path, bbox_, tr, detector_, spacing * scale_factor_, max_error, allow_overlap); double x, y, angle = .0; while (placement_finder.get_point(x, y, angle, ignore_placement)) { agg::trans_affine matrix = tr; matrix.rotate(angle); matrix.translate(x, y); render_vector_marker( ctx_, pixel_position(x, y), marker_, bbox_, attributes_, matrix, opacity, false); } } SvgPath & marker_; Attributes const& attributes_; box2d const& bbox_; agg::trans_affine const& marker_trans_; markers_symbolizer const& sym_; Detector & detector_; double scale_factor_; feature_impl const& feature_; attributes const& vars_; cairo_context & ctx_; }; template struct raster_markers_dispatch : mapnik::noncopyable { raster_markers_dispatch(mapnik::image_data_32 & src, agg::trans_affine const& marker_trans, markers_symbolizer const& sym, Detector & detector, double scale_factor, feature_impl const& feature, mapnik::attributes const& vars, RendererContext const& renderer_context) : src_(src), detector_(detector), sym_(sym), marker_trans_(marker_trans), scale_factor_(scale_factor), feature_(feature), vars_(vars), ctx_(std::get<0>(renderer_context)) {} template void add_path(T & path) { marker_placement_enum placement_method = get(sym_, keys::markers_placement_type, feature_, vars_, MARKER_POINT_PLACEMENT); double opacity = get(sym_, keys::opacity, feature_, vars_, 1.0); double spacing = get(sym_, keys::spacing, feature_, vars_, 100.0); double max_error = get(sym_, keys::max_error, feature_, vars_, 0.2); bool allow_overlap = get(sym_, keys::allow_overlap, feature_, vars_, false); bool ignore_placement = get(sym_, keys::ignore_placement, feature_, vars_, false); box2d bbox_(0,0, src_.width(),src_.height()); markers_placement_finder placement_finder( placement_method, path, bbox_, marker_trans_, detector_, spacing * scale_factor_, max_error, allow_overlap); double x, y, angle = .0; while (placement_finder.get_point(x, y, angle, ignore_placement)) { agg::trans_affine matrix = marker_trans_; matrix.rotate(angle); matrix.translate(x, y); ctx_.add_image(matrix, src_, opacity); } } image_data_32 & src_; Detector & detector_; markers_symbolizer const& sym_; agg::trans_affine const& marker_trans_; double scale_factor_; feature_impl const& feature_; attributes const& vars_; cairo_context & ctx_; }; } void cairo_renderer_base::process(markers_symbolizer const& sym, mapnik::feature_impl & feature, proj_transform const& prj_trans) { using svg_attribute_type = agg::pod_bvector; cairo_save_restore guard(context_); composite_mode_e comp_op = get(sym, keys::comp_op, feature, common_.vars_, src_over); context_.set_operator(comp_op); box2d clip_box = common_.query_extent_; auto renderer_context = std::tie(context_); using RendererContextType = decltype(renderer_context); using vector_dispatch_type = detail::markers_dispatch >, svg_attribute_type,label_collision_detector4>; using raster_dispatch_type = detail::raster_markers_dispatch; render_markers_symbolizer( sym, feature, prj_trans, common_, clip_box, renderer_context); } void cairo_renderer_base::process(text_symbolizer const& sym, mapnik::feature_impl & feature, proj_transform const& prj_trans) { text_symbolizer_helper helper( sym, feature, common_.vars_, prj_trans, common_.width_, common_.height_, common_.scale_factor_, common_.t_, common_.font_manager_, *common_.detector_, common_.query_extent_); cairo_save_restore guard(context_); composite_mode_e comp_op = get(sym, keys::comp_op, feature, common_.vars_, src_over); composite_mode_e halo_comp_op = get(sym, keys::halo_comp_op, feature, common_.vars_, src_over); placements_list const& placements = helper.get(); for (glyph_positions_ptr glyphs : placements) { context_.add_text(glyphs, face_manager_, common_.font_manager_, comp_op, halo_comp_op, common_.scale_factor_); } } namespace { /** * Render a thunk which was frozen from a previous call to * extract_bboxes. We should now have a new offset at which * to render it, and the boxes themselves should already be * in the detector from the placement_finder. */ struct thunk_renderer : public boost::static_visitor<> { using renderer_type = cairo_renderer_base; thunk_renderer(renderer_type &ren, cairo_context &context, cairo_face_manager &face_manager, renderer_common &common, pixel_position const &offset) : ren_(ren), context_(context), face_manager_(face_manager), common_(common), offset_(offset) {} void operator()(point_render_thunk const &thunk) const { pixel_position new_pos(thunk.pos_.x + offset_.x, thunk.pos_.y + offset_.y); ren_.render_marker(new_pos, *thunk.marker_, thunk.tr_, thunk.opacity_, thunk.comp_op_); } void operator()(text_render_thunk const &thunk) const { cairo_save_restore guard(context_); context_.set_operator(thunk.comp_op_); render_offset_placements( thunk.placements_, offset_, [&] (glyph_positions_ptr glyphs) { if (glyphs->marker()) { ren_.render_marker(glyphs->marker_pos(), *(glyphs->marker()->marker), glyphs->marker()->transform, thunk.opacity_, thunk.comp_op_); } context_.add_text(glyphs, face_manager_, common_.font_manager_, src_over, src_over, common_.scale_factor_); }); } template void operator()(T const &) const { // TODO: warning if unimplemented? } private: renderer_type &ren_; cairo_context &context_; cairo_face_manager &face_manager_; renderer_common &common_; pixel_position offset_; }; } // anonymous namespace void cairo_renderer_base::process(group_symbolizer const& sym, mapnik::feature_impl & feature, proj_transform const& prj_trans) { render_group_symbolizer( sym, feature, common_.vars_, prj_trans, common_.query_extent_, common_, [&](render_thunk_list const& thunks, pixel_position const& render_offset) { thunk_renderer ren(*this, context_, face_manager_, common_, render_offset); for (render_thunk_ptr const& thunk : thunks) { boost::apply_visitor(ren, *thunk); } }); } namespace { // special implementation of the box drawing so that it's pixel-aligned void render_debug_box(cairo_context &context, box2d const& b) { cairo_save_restore guard(context); double minx = std::floor(b.minx()) + 0.5; double miny = std::floor(b.miny()) + 0.5; double maxx = std::floor(b.maxx()) + 0.5; double maxy = std::floor(b.maxy()) + 0.5; context.move_to(minx, miny); context.line_to(minx, maxy); context.line_to(maxx, maxy); context.line_to(maxx, miny); context.close_path(); context.stroke(); } } // anonymous namespace void cairo_renderer_base::process(debug_symbolizer const& sym, mapnik::feature_impl & feature, proj_transform const& prj_trans) { using detector_type = label_collision_detector4; cairo_save_restore guard(context_); debug_symbolizer_mode_enum mode = get(sym, keys::mode, feature, common_.vars_, DEBUG_SYM_MODE_COLLISION); context_.set_operator(src_over); context_.set_color(mapnik::color(255, 0, 0), 1.0); context_.set_line_join(MITER_JOIN); context_.set_line_cap(BUTT_CAP); context_.set_miter_limit(4.0); context_.set_line_width(1.0); if (mode == DEBUG_SYM_MODE_COLLISION) { typename detector_type::query_iterator itr = common_.detector_->begin(); typename detector_type::query_iterator end = common_.detector_->end(); for ( ;itr!=end; ++itr) { render_debug_box(context_, itr->box); } } else if (mode == DEBUG_SYM_MODE_VERTEX) { for (auto const& geom : feature.paths()) { double x; double y; double z = 0; geom.rewind(0); unsigned cmd = 1; while ((cmd = geom.vertex(&x, &y)) != mapnik::SEG_END) { if (cmd == SEG_CLOSE) continue; prj_trans.backward(x,y,z); common_.t_.forward(&x,&y); context_.move_to(std::floor(x) - 0.5, std::floor(y) + 0.5); context_.line_to(std::floor(x) + 1.5, std::floor(y) + 0.5); context_.move_to(std::floor(x) + 0.5, std::floor(y) - 0.5); context_.line_to(std::floor(x) + 0.5, std::floor(y) + 1.5); context_.stroke(); } } } } template class cairo_renderer; template class cairo_renderer; } #endif // HAVE_CAIRO