#654 - add support for linear and radial gradients in SVG symbols. Thanks to Toby Collett for the coding & Paul Wicks for funding.
This commit is contained in:
parent
a79018d0a0
commit
386a4f5daf
28 changed files with 1938 additions and 729 deletions
|
@ -32,6 +32,10 @@
|
|||
#include <mapnik/label_collision_detector.hpp>
|
||||
#include <mapnik/placement_finder.hpp>
|
||||
#include <mapnik/map.hpp>
|
||||
#include <mapnik/marker.hpp>
|
||||
|
||||
// agg
|
||||
#include <agg_trans_affine.h>
|
||||
|
||||
// boost
|
||||
#include <boost/utility.hpp>
|
||||
|
@ -53,6 +57,8 @@ public:
|
|||
void end_map_processing(Map const& map);
|
||||
void start_layer_processing(layer const& lay);
|
||||
void end_layer_processing(layer const& lay);
|
||||
void render_marker(const int x, const int y, marker &marker, const agg::trans_affine & tr, double opacity);
|
||||
|
||||
void process(point_symbolizer const& sym,
|
||||
Feature const& feature,
|
||||
proj_transform const& prj_trans);
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include <mapnik/label_collision_detector.hpp>
|
||||
#include <mapnik/placement_finder.hpp>
|
||||
#include <mapnik/map.hpp>
|
||||
#include <mapnik/marker.hpp>
|
||||
|
||||
// cairo
|
||||
#include <cairomm/context.h>
|
||||
|
@ -115,6 +116,8 @@ public:
|
|||
return false;
|
||||
};
|
||||
protected:
|
||||
void render_marker(const int x, const int y, marker &marker, const agg::trans_affine & mtx, double opacity=1.0);
|
||||
|
||||
Map const& m_;
|
||||
Cairo::RefPtr<Cairo::Context> context_;
|
||||
CoordTransform t_;
|
||||
|
|
107
include/mapnik/gradient.hpp
Normal file
107
include/mapnik/gradient.hpp
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*****************************************************************************
|
||||
*
|
||||
* This file is part of Mapnik (c++ mapping toolkit)
|
||||
*
|
||||
* Copyright (C) 2010 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
|
||||
*
|
||||
*****************************************************************************/
|
||||
//$Id$
|
||||
|
||||
#ifndef GRADIENT_HPP
|
||||
#define GRADIENT_HPP
|
||||
|
||||
#include <agg_trans_affine.h>
|
||||
|
||||
// mapnik
|
||||
#include <mapnik/color.hpp>
|
||||
#include <mapnik/enumeration.hpp>
|
||||
|
||||
// stl
|
||||
#include <vector>
|
||||
|
||||
namespace mapnik
|
||||
{
|
||||
|
||||
typedef std::pair<double, mapnik::color> stop_pair;
|
||||
typedef std::vector<stop_pair > stop_array;
|
||||
|
||||
enum gradient_enum
|
||||
{
|
||||
NO_GRADIENT,
|
||||
LINEAR,
|
||||
RADIAL,
|
||||
gradient_enum_MAX
|
||||
};
|
||||
|
||||
DEFINE_ENUM( gradient_e, gradient_enum );
|
||||
|
||||
enum gradient_unit_enum
|
||||
{
|
||||
USER_SPACE_ON_USE,
|
||||
USER_SPACE_ON_USE_BOUNDING_BOX, // used to indicate % age values were specified. This are % of the svg image extent.
|
||||
OBJECT_BOUNDING_BOX, // (i.e., the abstract coordinate system where (0,0) is at the top/left of the object bounding box and (1,1) is at the bottom/right of the object bounding box)
|
||||
gradient_unit_enum_MAX
|
||||
};
|
||||
|
||||
DEFINE_ENUM( gradient_unit_e, gradient_unit_enum );
|
||||
|
||||
class MAPNIK_DECL gradient
|
||||
{
|
||||
gradient_e gradient_type_;
|
||||
stop_array stops_;
|
||||
// control points for the gradient, x1/y1 is the start point, x2/y2 the stop point.
|
||||
double x1_;
|
||||
double y1_;
|
||||
double x2_;
|
||||
double y2_;
|
||||
// for radial gradients r specifies the radius of the stop circle centered on x2/y2
|
||||
double r_;
|
||||
|
||||
// units for the coordinates
|
||||
gradient_unit_e units_;
|
||||
|
||||
// transform
|
||||
agg::trans_affine transform_;
|
||||
public:
|
||||
explicit gradient();
|
||||
gradient(gradient const& other);
|
||||
gradient& operator=(const gradient& rhs);
|
||||
|
||||
void set_gradient_type(gradient_e grad);
|
||||
gradient_e get_gradient_type() const;
|
||||
|
||||
void set_transform(agg::trans_affine transform);
|
||||
agg::trans_affine get_transform() const;
|
||||
|
||||
void set_units(gradient_unit_e units);
|
||||
gradient_unit_e get_units() const;
|
||||
|
||||
void add_stop(double offset, color const& c);
|
||||
bool has_stop() const;
|
||||
|
||||
stop_array const& get_stop_array() const;
|
||||
|
||||
void set_control_points(double x1, double y1, double x2, double y2, double r=0);
|
||||
void get_control_points(double &x1, double &y1, double &x2, double &y2, double &r) const;
|
||||
void get_control_points(double &x1, double &y1, double &x2, double &y2) const;
|
||||
|
||||
private:
|
||||
void swap(const gradient& other) throw();
|
||||
};
|
||||
}
|
||||
|
||||
#endif //GRADIENT_HPP
|
|
@ -1,63 +0,0 @@
|
|||
/*****************************************************************************
|
||||
*
|
||||
* This file is part of Mapnik (c++ mapping toolkit)
|
||||
*
|
||||
* Copyright (C) 2009 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
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
//$Id$
|
||||
|
||||
#ifndef MAPNIK_IMAGE_CACHE_HPP
|
||||
#define MAPNIK_IMAGE_CACHE_HPP
|
||||
|
||||
// mapnik
|
||||
#include <mapnik/utils.hpp>
|
||||
#include <mapnik/config.hpp>
|
||||
#include <mapnik/image_data.hpp>
|
||||
|
||||
// boost
|
||||
#include <boost/utility.hpp>
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#ifdef MAPNIK_THREADSAFE
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#endif
|
||||
|
||||
namespace mapnik
|
||||
{
|
||||
|
||||
typedef boost::shared_ptr<image_data_32> image_ptr;
|
||||
|
||||
struct MAPNIK_DECL image_cache :
|
||||
public singleton <image_cache, CreateStatic>,
|
||||
private boost::noncopyable
|
||||
{
|
||||
|
||||
friend class CreateStatic<image_cache>;
|
||||
#ifdef MAPNIK_THREADSAFE
|
||||
static boost::mutex mutex_;
|
||||
#endif
|
||||
static boost::unordered_map<std::string,image_ptr> cache_;
|
||||
static bool insert(std::string const& key, image_ptr);
|
||||
static boost::optional<image_ptr> find(std::string const& key, bool update_cache = false);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // MAPNIK_IMAGE_CACHE_HPP
|
127
include/mapnik/marker.hpp
Normal file
127
include/mapnik/marker.hpp
Normal file
|
@ -0,0 +1,127 @@
|
|||
/*****************************************************************************
|
||||
*
|
||||
* This file is part of Mapnik (c++ mapping toolkit)
|
||||
*
|
||||
* Copyright (C) 2006 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
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
//$Id: image_data.hpp 39 2005-04-10 20:39:53Z pavlenko $
|
||||
|
||||
#ifndef MARKER_HPP
|
||||
#define MARKER_HPP
|
||||
|
||||
#include <mapnik/global.hpp>
|
||||
#include <mapnik/image_data.hpp>
|
||||
#include <mapnik/svg/svg_path_attributes.hpp>
|
||||
#include <mapnik/svg/svg_storage.hpp>
|
||||
#include <mapnik/svg/svg_path_adapter.hpp>
|
||||
|
||||
#include "agg_path_storage.h"
|
||||
// boost
|
||||
#include <boost/utility.hpp>
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
namespace mapnik
|
||||
{
|
||||
|
||||
typedef agg::pod_bvector<mapnik::svg::path_attributes> attr_storage;
|
||||
typedef mapnik::svg::svg_storage<mapnik::svg::svg_path_storage,attr_storage> svg_storage_type;
|
||||
typedef boost::shared_ptr<svg_storage_type> path_ptr;
|
||||
typedef boost::shared_ptr<image_data_32> image_ptr;
|
||||
/**
|
||||
* A class to hold either vector or bitmap marker data. This allows these to be treated equally
|
||||
* in the image caches and most of the render paths.
|
||||
*/
|
||||
class marker
|
||||
{
|
||||
public:
|
||||
marker()
|
||||
{
|
||||
// create default OGC 4x4 black pixel
|
||||
bitmap_data_ = boost::optional<mapnik::image_ptr>(new image_data_32(4,4));
|
||||
(*bitmap_data_)->set(0xff000000);
|
||||
}
|
||||
|
||||
marker(const boost::optional<mapnik::image_ptr> &data) : bitmap_data_(data)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
marker(const boost::optional<mapnik::path_ptr> &data) : vector_data_(data)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
marker(const marker& rhs) : bitmap_data_(rhs.bitmap_data_), vector_data_(rhs.vector_data_)
|
||||
{
|
||||
}
|
||||
|
||||
inline unsigned width() const
|
||||
{
|
||||
if (is_bitmap())
|
||||
return (*bitmap_data_)->width();
|
||||
else if (is_vector())
|
||||
return (*vector_data_)->bounding_box().width();
|
||||
return 0;
|
||||
}
|
||||
inline unsigned height() const
|
||||
{
|
||||
if (is_bitmap())
|
||||
return (*bitmap_data_)->height();
|
||||
else if (is_vector())
|
||||
return (*vector_data_)->bounding_box().height();
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline bool is_bitmap() const
|
||||
{
|
||||
return bitmap_data_;
|
||||
}
|
||||
|
||||
inline bool is_vector() const
|
||||
{
|
||||
return vector_data_;
|
||||
}
|
||||
|
||||
boost::optional<mapnik::image_ptr> get_bitmap_data()
|
||||
{
|
||||
return bitmap_data_;
|
||||
}
|
||||
|
||||
boost::optional<mapnik::path_ptr> get_vector_data()
|
||||
{
|
||||
return vector_data_;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
marker& operator=(const marker&);
|
||||
|
||||
boost::optional<mapnik::image_ptr> bitmap_data_;
|
||||
boost::optional<mapnik::path_ptr> vector_data_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //MARKER_HPP
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
// mapnik
|
||||
#include <mapnik/utils.hpp>
|
||||
#include <mapnik/marker.hpp>
|
||||
#include <mapnik/config.hpp>
|
||||
#include <mapnik/svg/svg_path_attributes.hpp>
|
||||
#include <mapnik/svg/svg_storage.hpp>
|
||||
|
@ -47,9 +48,8 @@ namespace mapnik
|
|||
|
||||
using namespace mapnik::svg;
|
||||
|
||||
typedef agg::pod_bvector<path_attributes> attr_storage;
|
||||
typedef svg_storage<svg_path_storage,attr_storage> svg_storage_type;
|
||||
typedef boost::shared_ptr<svg_storage_type> path_ptr;
|
||||
typedef boost::shared_ptr<marker> marker_ptr;
|
||||
|
||||
|
||||
struct MAPNIK_DECL marker_cache :
|
||||
public singleton <marker_cache, CreateStatic>,
|
||||
|
@ -60,9 +60,9 @@ struct MAPNIK_DECL marker_cache :
|
|||
#ifdef MAPNIK_THREADSAFE
|
||||
static boost::mutex mutex_;
|
||||
#endif
|
||||
static boost::unordered_map<std::string,path_ptr> cache_;
|
||||
static bool insert(std::string const& key, path_ptr);
|
||||
static boost::optional<path_ptr> find(std::string const& key, bool update_cache = false);
|
||||
static boost::unordered_map<std::string,marker_ptr> cache_;
|
||||
static bool insert(std::string const& key, marker_ptr);
|
||||
static boost::optional<marker_ptr> find(std::string const& key, bool update_cache = false);
|
||||
};
|
||||
|
||||
}
|
|
@ -201,7 +201,19 @@ public:
|
|||
attr.fill_color.opacity(a * f.opacity());
|
||||
attr.fill_flag = true;
|
||||
}
|
||||
|
||||
|
||||
void add_fill_gradient(mapnik::gradient& grad)
|
||||
{
|
||||
path_attributes& attr = cur_attr();
|
||||
attr.fill_gradient = grad;
|
||||
}
|
||||
|
||||
void add_stroke_gradient(mapnik::gradient& grad)
|
||||
{
|
||||
path_attributes& attr = cur_attr();
|
||||
attr.stroke_gradient = grad;
|
||||
}
|
||||
|
||||
void stroke(const agg::rgba8& s)
|
||||
{
|
||||
path_attributes& attr = cur_attr();
|
||||
|
@ -215,6 +227,12 @@ public:
|
|||
{
|
||||
cur_attr().even_odd_flag = flag;
|
||||
}
|
||||
|
||||
void visibility(bool flag)
|
||||
{
|
||||
cur_attr().visibility_flag = flag;
|
||||
}
|
||||
|
||||
|
||||
void stroke_width(double w)
|
||||
{
|
||||
|
|
|
@ -27,10 +27,14 @@
|
|||
#include <mapnik/svg/svg_path_attributes.hpp>
|
||||
#include <mapnik/svg/svg_converter.hpp>
|
||||
#include <mapnik/svg/svg_path_adapter.hpp>
|
||||
#include <mapnik/gradient.hpp>
|
||||
|
||||
// boost
|
||||
#include <boost/utility.hpp>
|
||||
#include <libxml/xmlreader.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace mapnik { namespace svg {
|
||||
|
||||
class svg_parser : private boost::noncopyable
|
||||
|
@ -51,12 +55,20 @@ private:
|
|||
void parse_rect(xmlTextReaderPtr reader);
|
||||
void parse_circle(xmlTextReaderPtr reader);
|
||||
void parse_ellipse(xmlTextReaderPtr reader);
|
||||
void parse_linear_gradient(xmlTextReaderPtr reader);
|
||||
void parse_radial_gradient(xmlTextReaderPtr reader);
|
||||
bool parse_common_gradient(xmlTextReaderPtr reader);
|
||||
void parse_gradient_stop(xmlTextReaderPtr reader);
|
||||
void parse_pattern(xmlTextReaderPtr reader);
|
||||
void parse_attr(xmlTextReaderPtr reader);
|
||||
void parse_attr(const xmlChar * name, const xmlChar * value );
|
||||
|
||||
|
||||
private:
|
||||
svg_converter_type & path_;
|
||||
bool is_defs_;
|
||||
std::map<std::string, gradient> gradient_map_;
|
||||
std::pair<std::string, gradient> temporary_gradient_;
|
||||
};
|
||||
|
||||
}}
|
||||
|
|
|
@ -30,6 +30,8 @@
|
|||
#include "agg_pixfmt_rgba.h"
|
||||
#include "agg_trans_affine.h"
|
||||
|
||||
#include <mapnik/gradient.hpp>
|
||||
|
||||
namespace mapnik {
|
||||
namespace svg {
|
||||
|
||||
|
@ -42,11 +44,14 @@ struct path_attributes
|
|||
bool fill_flag;
|
||||
bool stroke_flag;
|
||||
bool even_odd_flag;
|
||||
bool visibility_flag;
|
||||
agg::line_join_e line_join;
|
||||
agg::line_cap_e line_cap;
|
||||
double miter_limit;
|
||||
double stroke_width;
|
||||
agg::trans_affine transform;
|
||||
mapnik::gradient fill_gradient;
|
||||
mapnik::gradient stroke_gradient;
|
||||
|
||||
// Empty constructor
|
||||
path_attributes() :
|
||||
|
@ -57,11 +62,14 @@ struct path_attributes
|
|||
fill_flag(true),
|
||||
stroke_flag(false),
|
||||
even_odd_flag(false),
|
||||
visibility_flag(true),
|
||||
line_join(agg::miter_join),
|
||||
line_cap(agg::butt_cap),
|
||||
miter_limit(4.0),
|
||||
stroke_width(1.0),
|
||||
transform()
|
||||
transform(),
|
||||
fill_gradient(),
|
||||
stroke_gradient()
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -74,11 +82,15 @@ struct path_attributes
|
|||
fill_flag(attr.fill_flag),
|
||||
stroke_flag(attr.stroke_flag),
|
||||
even_odd_flag(attr.even_odd_flag),
|
||||
visibility_flag(attr.visibility_flag),
|
||||
line_join(attr.line_join),
|
||||
line_cap(attr.line_cap),
|
||||
miter_limit(attr.miter_limit),
|
||||
stroke_width(attr.stroke_width),
|
||||
transform(attr.transform) {}
|
||||
transform(attr.transform),
|
||||
fill_gradient(attr.fill_gradient),
|
||||
stroke_gradient(attr.stroke_gradient)
|
||||
{}
|
||||
|
||||
// Copy constructor with new index value
|
||||
path_attributes(path_attributes const& attr, unsigned idx)
|
||||
|
@ -89,11 +101,15 @@ struct path_attributes
|
|||
fill_flag(attr.fill_flag),
|
||||
stroke_flag(attr.stroke_flag),
|
||||
even_odd_flag(attr.even_odd_flag),
|
||||
visibility_flag(attr.visibility_flag),
|
||||
line_join(attr.line_join),
|
||||
line_cap(attr.line_cap),
|
||||
miter_limit(attr.miter_limit),
|
||||
stroke_width(attr.stroke_width),
|
||||
transform(attr.transform) {}
|
||||
transform(attr.transform),
|
||||
fill_gradient(attr.fill_gradient),
|
||||
stroke_gradient(attr.stroke_gradient)
|
||||
{}
|
||||
};
|
||||
|
||||
}}
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
#define MAPNIK_SVG_RENDERER_HPP
|
||||
|
||||
#include <mapnik/svg/svg_path_attributes.hpp>
|
||||
#include <mapnik/gradient.hpp>
|
||||
#include <mapnik/box2d.hpp>
|
||||
|
||||
#include <boost/utility.hpp>
|
||||
|
||||
|
@ -37,9 +39,67 @@
|
|||
#include "agg_bounding_rect.h"
|
||||
#include "agg_rasterizer_scanline_aa.h"
|
||||
|
||||
|
||||
#include "agg_rendering_buffer.h"
|
||||
#include "agg_rasterizer_scanline_aa.h"
|
||||
#include "agg_scanline_u.h"
|
||||
#include "agg_scanline_p.h"
|
||||
#include "agg_renderer_scanline.h"
|
||||
#include "agg_span_allocator.h"
|
||||
#include "agg_span_gradient.h"
|
||||
#include "agg_gradient_lut.h"
|
||||
#include "agg_gamma_lut.h"
|
||||
#include "agg_span_interpolator_linear.h"
|
||||
#include "agg_pixfmt_rgba.h"
|
||||
#include "agg_path_storage.h"
|
||||
#include "agg_ellipse.h"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
namespace mapnik {
|
||||
namespace svg {
|
||||
|
||||
|
||||
/**
|
||||
* Arbitrary linear gradient specified by two control points. Gradient
|
||||
* value is taken as the normalised distance along the line segment
|
||||
* represented by the two points.
|
||||
*/
|
||||
class linear_gradient_from_segment
|
||||
{
|
||||
public:
|
||||
linear_gradient_from_segment(double x1, double y1, double x2, double y2) :
|
||||
x1_(x1*agg::gradient_subpixel_scale),
|
||||
y1_(y1*agg::gradient_subpixel_scale),
|
||||
x2_(x2*agg::gradient_subpixel_scale),
|
||||
y2_(y2*agg::gradient_subpixel_scale)
|
||||
{
|
||||
double dx = x2_-x1_;
|
||||
double dy = y2_-y1_;
|
||||
length_sqr_ = dx*dx+dy*dy;
|
||||
}
|
||||
|
||||
int calculate(int x, int y, int d) const
|
||||
{
|
||||
if (length_sqr_ <= 0)
|
||||
return 0;
|
||||
double u = ((x-x1_)*(x2_-x1_) + (y-y1_)*(y2_-y1_))/length_sqr_;
|
||||
if (u < 0)
|
||||
u = 0;
|
||||
else if (u > 1)
|
||||
u = 1;
|
||||
return u*d;
|
||||
}
|
||||
private:
|
||||
double x1_;
|
||||
double y1_;
|
||||
double x2_;
|
||||
double y2_;
|
||||
|
||||
double length_sqr_;
|
||||
|
||||
};
|
||||
|
||||
template <typename VertexSource, typename AttributeSource>
|
||||
class svg_renderer : boost::noncopyable
|
||||
{
|
||||
|
@ -48,6 +108,9 @@ class svg_renderer : boost::noncopyable
|
|||
typedef agg::conv_transform<curved_stroked_type> curved_stroked_trans_type;
|
||||
typedef agg::conv_transform<curved_type> curved_trans_type;
|
||||
typedef agg::conv_contour<curved_trans_type> curved_trans_contour_type;
|
||||
typedef agg::pixfmt_rgba32_plain pixfmt;
|
||||
typedef agg::renderer_base<pixfmt> renderer_base;
|
||||
typedef agg::renderer_scanline_aa_solid<renderer_base> renderer_solid;
|
||||
|
||||
public:
|
||||
svg_renderer(VertexSource & source, AttributeSource const& attributes)
|
||||
|
@ -56,12 +119,134 @@ public:
|
|||
curved_stroked_(curved_),
|
||||
attributes_(attributes) {}
|
||||
|
||||
template <typename Rasterizer, typename Scanline, typename Renderer>
|
||||
void render_gradient(Rasterizer& ras,
|
||||
Scanline& sl,
|
||||
Renderer& ren,
|
||||
const gradient &grad,
|
||||
agg::trans_affine const& mtx,
|
||||
double opacity,
|
||||
const box2d<double> &symbol_bbox,
|
||||
const box2d<double> &path_bbox)
|
||||
{
|
||||
typedef agg::gamma_lut<agg::int8u, agg::int8u> gamma_lut_type;
|
||||
typedef agg::gradient_lut<agg::color_interpolator<agg::rgba8>, 1024> color_func_type;
|
||||
typedef agg::span_interpolator_linear<> interpolator_type;
|
||||
typedef agg::span_allocator<agg::rgba8> span_allocator_type;
|
||||
|
||||
span_allocator_type m_alloc;
|
||||
color_func_type m_gradient_lut;
|
||||
gamma_lut_type m_gamma_lut;
|
||||
|
||||
double x1,x2,y1,y2,radius;
|
||||
grad.get_control_points(x1,y1,x2,y2,radius);
|
||||
|
||||
m_gradient_lut.remove_all();
|
||||
BOOST_FOREACH ( mapnik::stop_pair const& st, grad.get_stop_array() )
|
||||
{
|
||||
mapnik::color const& stop_color = st.second;
|
||||
unsigned r= stop_color.red();
|
||||
unsigned g= stop_color.green();
|
||||
unsigned b= stop_color.blue();
|
||||
unsigned a= stop_color.alpha();
|
||||
//std::clog << "r: " << r << " g: " << g << " b: " << b << "a: " << a << "\n";
|
||||
m_gradient_lut.add_color(st.first, agg::rgba8(r, g, b, int(a * opacity)));
|
||||
}
|
||||
m_gradient_lut.build_lut();
|
||||
|
||||
agg::trans_affine transform = mtx;
|
||||
transform.invert();
|
||||
agg::trans_affine tr;
|
||||
tr = grad.get_transform();
|
||||
tr.invert();
|
||||
transform *= tr;
|
||||
|
||||
if (grad.get_units() != USER_SPACE_ON_USE)
|
||||
{
|
||||
double bx1=symbol_bbox.minx();
|
||||
double by1=symbol_bbox.miny();
|
||||
double bx2=symbol_bbox.maxx();
|
||||
double by2=symbol_bbox.maxy();
|
||||
|
||||
if (grad.get_units() == OBJECT_BOUNDING_BOX)
|
||||
{
|
||||
bx1=path_bbox.minx();
|
||||
by1=path_bbox.miny();
|
||||
bx2=path_bbox.maxx();
|
||||
by2=path_bbox.maxy();
|
||||
}
|
||||
|
||||
transform.translate(-bx1,-by1);
|
||||
transform.scale(1.0/(bx2-bx1),1.0/(by2-by1));
|
||||
}
|
||||
|
||||
|
||||
if (grad.get_gradient_type() == RADIAL)
|
||||
{
|
||||
typedef agg::gradient_radial_focus gradient_adaptor_type;
|
||||
typedef agg::span_gradient<agg::rgba8,
|
||||
interpolator_type,
|
||||
gradient_adaptor_type,
|
||||
color_func_type> span_gradient_type;
|
||||
|
||||
// the agg radial gradient assumes it is centred on 0
|
||||
transform.translate(-x2,-y2);
|
||||
|
||||
// scale everything up since agg turns things into integers a bit too soon
|
||||
int scaleup=255;
|
||||
radius*=scaleup;
|
||||
x1*=scaleup;
|
||||
y1*=scaleup;
|
||||
x2*=scaleup;
|
||||
y2*=scaleup;
|
||||
|
||||
transform.scale(scaleup,scaleup);
|
||||
interpolator_type span_interpolator(transform);
|
||||
gradient_adaptor_type gradient_adaptor(radius,(x1-x2),(y1-y2));
|
||||
|
||||
span_gradient_type span_gradient(span_interpolator,
|
||||
gradient_adaptor,
|
||||
m_gradient_lut,
|
||||
0, radius);
|
||||
|
||||
render_scanlines_aa(ras, sl, ren, m_alloc, span_gradient);
|
||||
}
|
||||
else
|
||||
{
|
||||
typedef linear_gradient_from_segment gradient_adaptor_type;
|
||||
typedef agg::span_gradient<agg::rgba8,
|
||||
interpolator_type,
|
||||
gradient_adaptor_type,
|
||||
color_func_type> span_gradient_type;
|
||||
|
||||
// scale everything up since agg turns things into integers a bit too soon
|
||||
int scaleup=255;
|
||||
x1*=scaleup;
|
||||
y1*=scaleup;
|
||||
x2*=scaleup;
|
||||
y2*=scaleup;
|
||||
|
||||
transform.scale(scaleup,scaleup);
|
||||
|
||||
interpolator_type span_interpolator(transform);
|
||||
gradient_adaptor_type gradient_adaptor(x1,y1,x2,y2);
|
||||
|
||||
span_gradient_type span_gradient(span_interpolator,
|
||||
gradient_adaptor,
|
||||
m_gradient_lut,
|
||||
0, scaleup);
|
||||
|
||||
render_scanlines_aa(ras, sl, ren, m_alloc, span_gradient);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Rasterizer, typename Scanline, typename Renderer>
|
||||
void render(Rasterizer& ras,
|
||||
Scanline& sl,
|
||||
Renderer& ren,
|
||||
agg::trans_affine const& mtx,
|
||||
double opacity=1.0)
|
||||
double opacity,
|
||||
const box2d<double> &symbol_bbox)
|
||||
|
||||
{
|
||||
using namespace agg;
|
||||
|
@ -76,7 +261,15 @@ public:
|
|||
for(unsigned i = 0; i < attributes_.size(); ++i)
|
||||
{
|
||||
mapnik::svg::path_attributes const& attr = attributes_[i];
|
||||
if (!attr.visibility_flag)
|
||||
continue;
|
||||
|
||||
transform = attr.transform;
|
||||
|
||||
double bx1,by1,bx2,by2;
|
||||
bounding_rect_single(curved_trans, attr.index, &bx1, &by1, &bx2, &by2);
|
||||
box2d<double> path_bbox(bx1,by1,bx2,by2);
|
||||
|
||||
transform *= mtx;
|
||||
double scl = transform.scale();
|
||||
//curved_.approximation_method(curve_inc);
|
||||
|
@ -84,11 +277,11 @@ public:
|
|||
curved_.angle_tolerance(0.0);
|
||||
|
||||
rgba8 color;
|
||||
|
||||
if(attr.fill_flag)
|
||||
|
||||
if (attr.fill_flag || attr.fill_gradient.get_gradient_type() != NO_GRADIENT)
|
||||
{
|
||||
ras.reset();
|
||||
ras.filling_rule(attr.even_odd_flag ? fill_even_odd : fill_non_zero);
|
||||
|
||||
if(fabs(curved_trans_contour.width()) < 0.0001)
|
||||
{
|
||||
ras.add_path(curved_trans, attr.index);
|
||||
|
@ -99,14 +292,24 @@ public:
|
|||
ras.add_path(curved_trans_contour, attr.index);
|
||||
}
|
||||
|
||||
color = attr.fill_color;
|
||||
color.opacity(color.opacity() * attr.opacity * opacity);
|
||||
ren.color(color);
|
||||
render_scanlines(ras, sl, ren);
|
||||
if(attr.fill_gradient.get_gradient_type() != NO_GRADIENT)
|
||||
{
|
||||
render_gradient(ras, sl, ren, attr.fill_gradient, transform, attr.opacity * opacity, symbol_bbox, path_bbox);
|
||||
}
|
||||
else
|
||||
{
|
||||
ras.filling_rule(attr.even_odd_flag ? fill_even_odd : fill_non_zero);
|
||||
color = attr.fill_color;
|
||||
color.opacity(color.opacity() * attr.opacity * opacity);
|
||||
renderer_solid ren_s(ren);
|
||||
ren_s.color(color);
|
||||
render_scanlines(ras, sl, ren_s);
|
||||
}
|
||||
}
|
||||
|
||||
if(attr.stroke_flag)
|
||||
if (attr.stroke_flag || attr.stroke_gradient.get_gradient_type() != NO_GRADIENT)
|
||||
{
|
||||
std::clog << "stroking\n";
|
||||
curved_stroked_.width(attr.stroke_width);
|
||||
//m_curved_stroked.line_join((attr.line_join == miter_join) ? miter_join_round : attr.line_join);
|
||||
curved_stroked_.line_join(attr.line_join);
|
||||
|
@ -123,12 +326,21 @@ public:
|
|||
curved_.angle_tolerance(0.2);
|
||||
}
|
||||
ras.reset();
|
||||
ras.filling_rule(fill_non_zero);
|
||||
ras.add_path(curved_stroked_trans, attr.index);
|
||||
color = attr.stroke_color;
|
||||
color.opacity(color.opacity() * attr.opacity * opacity);
|
||||
ren.color(color);
|
||||
render_scanlines(ras, sl, ren);
|
||||
|
||||
if(attr.stroke_gradient.get_gradient_type() != NO_GRADIENT)
|
||||
{
|
||||
render_gradient(ras, sl, ren, attr.stroke_gradient, transform, attr.opacity * opacity, symbol_bbox, path_bbox);
|
||||
}
|
||||
else
|
||||
{
|
||||
ras.filling_rule(fill_non_zero);
|
||||
color = attr.stroke_color;
|
||||
color.opacity(color.opacity() * attr.opacity * opacity);
|
||||
renderer_solid ren_s(ren);
|
||||
ren_s.color(color);
|
||||
render_scanlines(ras, sl, ren_s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,8 +104,8 @@ source = Split(
|
|||
filter_factory.cpp
|
||||
font_engine_freetype.cpp
|
||||
font_set.cpp
|
||||
gradient.cpp
|
||||
graphics.cpp
|
||||
image_cache.cpp
|
||||
image_reader.cpp
|
||||
image_util.cpp
|
||||
layer.cpp
|
||||
|
|
|
@ -24,8 +24,7 @@
|
|||
// mapnik
|
||||
#include <mapnik/agg_renderer.hpp>
|
||||
#include <mapnik/agg_rasterizer.hpp>
|
||||
#include <mapnik/image_util.hpp>
|
||||
#include <mapnik/image_cache.hpp>
|
||||
#include <mapnik/marker_cache.hpp>
|
||||
#include <mapnik/unicode.hpp>
|
||||
#include <mapnik/placement_finder.hpp>
|
||||
#include <mapnik/config_error.hpp>
|
||||
|
@ -126,11 +125,12 @@ agg_renderer<T>::agg_renderer(Map const& m, T & pixmap, double scale_factor, uns
|
|||
boost::optional<std::string> const& image_filename = m.background_image();
|
||||
if (image_filename)
|
||||
{
|
||||
boost::optional<mapnik::image_ptr> bg_image = mapnik::image_cache::instance()->find(*image_filename,true);
|
||||
if (bg_image)
|
||||
boost::optional<mapnik::marker_ptr> bg_marker = mapnik::marker_cache::instance()->find(*image_filename,true);
|
||||
if (bg_marker && (*bg_marker)->is_bitmap())
|
||||
{
|
||||
int w = (*bg_image)->width();
|
||||
int h = (*bg_image)->height();
|
||||
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
|
||||
|
@ -140,7 +140,7 @@ agg_renderer<T>::agg_renderer(Map const& m, T & pixmap, double scale_factor, uns
|
|||
{
|
||||
for (unsigned y=0;y<y_steps;++y)
|
||||
{
|
||||
pixmap_.set_rectangle_alpha2(*(*bg_image), x*w, y*h, 1.0f);
|
||||
pixmap_.set_rectangle_alpha2(*bg_image, x*w, y*h, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
#include <mapnik/agg_renderer.hpp>
|
||||
#include <mapnik/agg_rasterizer.hpp>
|
||||
#include <mapnik/agg_pattern_source.hpp>
|
||||
#include <mapnik/image_cache.hpp>
|
||||
#include <mapnik/marker_cache.hpp>
|
||||
|
||||
// agg
|
||||
#include "agg_basics.h"
|
||||
|
@ -58,7 +58,11 @@ void agg_renderer<T>::process(line_pattern_symbolizer const& sym,
|
|||
agg::pixfmt_rgba32_plain pixf(buf);
|
||||
|
||||
std::string filename = path_processor_type::evaluate( *sym.get_filename(), feature);
|
||||
boost::optional<image_ptr> pat = image_cache::instance()->find(filename,true);
|
||||
|
||||
boost::optional<marker_ptr> mark = marker_cache::instance()->find(filename,true);
|
||||
if (!mark || !(*mark)->is_bitmap()) return;
|
||||
|
||||
boost::optional<image_ptr> pat = (*mark)->get_bitmap_data();
|
||||
|
||||
if (!pat) return;
|
||||
|
||||
|
|
|
@ -24,8 +24,7 @@
|
|||
#include <mapnik/agg_renderer.hpp>
|
||||
#include <mapnik/agg_rasterizer.hpp>
|
||||
#include <mapnik/image_util.hpp>
|
||||
#include <mapnik/image_cache.hpp>
|
||||
#include <mapnik/svg/marker_cache.hpp>
|
||||
#include <mapnik/marker_cache.hpp>
|
||||
#include <mapnik/svg/svg_renderer.hpp>
|
||||
#include <mapnik/svg/svg_path_adapter.hpp>
|
||||
#include <mapnik/markers_placement.hpp>
|
||||
|
@ -50,7 +49,7 @@ void agg_renderer<T>::process(markers_symbolizer const& sym,
|
|||
proj_transform const& prj_trans)
|
||||
{
|
||||
typedef coord_transform2<CoordTransform,geometry_type> path_type;
|
||||
typedef agg::pixfmt_rgba32 pixfmt;
|
||||
typedef agg::pixfmt_rgba32_plain pixfmt;
|
||||
typedef agg::renderer_base<pixfmt> renderer_base;
|
||||
typedef agg::renderer_scanline_aa_solid<renderer_base> renderer_solid;
|
||||
|
||||
|
@ -73,9 +72,10 @@ void agg_renderer<T>::process(markers_symbolizer const& sym,
|
|||
|
||||
if (!filename.empty())
|
||||
{
|
||||
boost::optional<path_ptr> marker = mapnik::marker_cache::instance()->find(filename, true);
|
||||
if (marker && *marker)
|
||||
boost::optional<marker_ptr> mark = mapnik::marker_cache::instance()->find(filename, true);
|
||||
if (mark && *mark && (*mark)->is_vector())
|
||||
{
|
||||
boost::optional<path_ptr> marker = (*mark)->get_vector_data();
|
||||
box2d<double> const& bbox = (*marker)->bounding_box();
|
||||
double x1 = bbox.minx();
|
||||
double y1 = bbox.miny();
|
||||
|
@ -111,7 +111,7 @@ void agg_renderer<T>::process(markers_symbolizer const& sym,
|
|||
while (placement.get_point(&x, &y, &angle))
|
||||
{
|
||||
agg::trans_affine matrix = recenter * tr *agg::trans_affine_rotation(angle) * agg::trans_affine_translation(x, y);
|
||||
svg_renderer.render(*ras_ptr, sl, ren, matrix, sym.get_opacity());
|
||||
svg_renderer.render(*ras_ptr, sl, renb, matrix, sym.get_opacity(),bbox);
|
||||
if (writer.first)
|
||||
//writer.first->add_box(label_ext, feature, t_, writer.second);
|
||||
std::clog << "### Warning metawriter not yet supported for LINE placement\n";
|
||||
|
|
|
@ -25,9 +25,8 @@
|
|||
#include <mapnik/agg_renderer.hpp>
|
||||
#include <mapnik/agg_rasterizer.hpp>
|
||||
#include <mapnik/image_util.hpp>
|
||||
#include <mapnik/image_cache.hpp>
|
||||
#include <mapnik/metawriter.hpp>
|
||||
#include <mapnik/svg/marker_cache.hpp>
|
||||
#include <mapnik/marker_cache.hpp>
|
||||
#include <mapnik/svg/svg_converter.hpp>
|
||||
#include <mapnik/svg/svg_renderer.hpp>
|
||||
#include <mapnik/svg/svg_path_adapter.hpp>
|
||||
|
@ -44,126 +43,104 @@
|
|||
namespace mapnik {
|
||||
|
||||
template <typename T>
|
||||
void agg_renderer<T>::process(point_symbolizer const& sym,
|
||||
Feature const& feature,
|
||||
proj_transform const& prj_trans)
|
||||
void agg_renderer<T>::render_marker(const int x, const int y, marker &marker, const agg::trans_affine & tr, double opacity)
|
||||
{
|
||||
typedef agg::pixfmt_rgba32 pixfmt;
|
||||
typedef agg::renderer_base<pixfmt> renderer_base;
|
||||
typedef agg::renderer_scanline_aa_solid<renderer_base> renderer_solid;
|
||||
|
||||
double x;
|
||||
double y;
|
||||
double z=0;
|
||||
|
||||
std::string filename = path_processor_type::evaluate(*sym.get_filename(), feature);
|
||||
boost::optional<mapnik::image_ptr> data;
|
||||
|
||||
if (is_svg(filename))
|
||||
if (marker.is_vector())
|
||||
{
|
||||
// SVG
|
||||
using namespace mapnik::svg;
|
||||
boost::optional<path_ptr> marker;
|
||||
typedef agg::pixfmt_rgba32_plain pixfmt;
|
||||
typedef agg::renderer_base<pixfmt> renderer_base;
|
||||
|
||||
ras_ptr->reset();
|
||||
ras_ptr->gamma(agg::gamma_linear());
|
||||
agg::scanline_u8 sl;
|
||||
agg::rendering_buffer buf(pixmap_.raw_data(), width_, height_, width_ * 4);
|
||||
pixfmt pixf(buf);
|
||||
renderer_base renb(pixf);
|
||||
renderer_solid ren(renb);
|
||||
box2d<double> extent;
|
||||
|
||||
marker = marker_cache::instance()->find(filename, true);
|
||||
|
||||
if (marker && *marker)
|
||||
{
|
||||
|
||||
box2d<double> const& bbox = (*marker)->bounding_box();
|
||||
double x1 = bbox.minx();
|
||||
double y1 = bbox.miny();
|
||||
double x2 = bbox.maxx();
|
||||
double y2 = bbox.maxy();
|
||||
|
||||
agg::trans_affine recenter = agg::trans_affine_translation(-0.5*(x1+x2),-0.5*(y1+y2));
|
||||
|
||||
vertex_stl_adapter<svg_path_storage> stl_storage((*marker)->source());
|
||||
svg_path_adapter svg_path(stl_storage);
|
||||
svg_renderer<svg_path_adapter,
|
||||
agg::pod_bvector<path_attributes> > svg_renderer(svg_path,
|
||||
(*marker)->attributes());
|
||||
|
||||
for (unsigned i=0; i<feature.num_geometries(); ++i)
|
||||
{
|
||||
geometry_type const& geom = feature.get_geometry(i);
|
||||
geom.label_position(&x,&y);
|
||||
prj_trans.backward(x,y,z);
|
||||
t_.forward(&x,&y);
|
||||
|
||||
agg::trans_affine tr;
|
||||
boost::array<double,6> const& m = sym.get_transform();
|
||||
tr.load_from(&m[0]);
|
||||
tr = recenter * tr;
|
||||
tr *= agg::trans_affine_scaling(scale_factor_);
|
||||
tr *= agg::trans_affine_translation(x, y);
|
||||
|
||||
tr.transform(&x1,&y1);
|
||||
tr.transform(&x2,&y2);
|
||||
|
||||
extent.init(x1,y1,x2,y2);
|
||||
if (sym.get_allow_overlap() ||
|
||||
detector_.has_placement(extent))
|
||||
{
|
||||
svg_renderer.render(*ras_ptr, sl, ren, tr, sym.get_opacity());
|
||||
if (!sym.get_ignore_placement())
|
||||
detector_.insert(extent);
|
||||
metawriter_with_properties writer = sym.get_metawriter();
|
||||
if (writer.first)
|
||||
{
|
||||
writer.first->add_box(extent, feature, t_, writer.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
box2d<double> const& bbox = (*marker.get_vector_data())->bounding_box();
|
||||
double x1 = bbox.minx();
|
||||
double y1 = bbox.miny();
|
||||
double x2 = bbox.maxx();
|
||||
double y2 = bbox.maxy();
|
||||
|
||||
agg::trans_affine recenter = agg::trans_affine_translation(0.5*(marker.width()-(x1+x2)),0.5*(marker.height()-(y1+y2)));
|
||||
|
||||
vertex_stl_adapter<svg_path_storage> stl_storage((*marker.get_vector_data())->source());
|
||||
svg_path_adapter svg_path(stl_storage);
|
||||
svg_renderer<svg_path_adapter,
|
||||
agg::pod_bvector<path_attributes> > svg_renderer(svg_path,
|
||||
(*marker.get_vector_data())->attributes());
|
||||
agg::trans_affine mtx = recenter * tr;
|
||||
mtx *= agg::trans_affine_scaling(scale_factor_);
|
||||
mtx *= agg::trans_affine_translation(x, y);
|
||||
|
||||
svg_renderer.render(*ras_ptr, sl, renb, mtx, opacity, bbox);
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( filename.empty() )
|
||||
{
|
||||
// default OGC 4x4 black square
|
||||
data = boost::optional<mapnik::image_ptr>(new image_data_32(4,4));
|
||||
(*data)->set(0xff000000);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = mapnik::image_cache::instance()->find(filename,true);
|
||||
}
|
||||
//int px = int(floor(x - 0.5 * marker.width()));
|
||||
// int py = int(floor(y - 0.5 * marker.height()));
|
||||
|
||||
if ( data )
|
||||
pixmap_.set_rectangle_alpha2(**marker.get_bitmap_data(), x, y, opacity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
void agg_renderer<T>::process(point_symbolizer const& sym,
|
||||
Feature const& feature,
|
||||
proj_transform const& prj_trans)
|
||||
{
|
||||
std::string filename = path_processor_type::evaluate(*sym.get_filename(), feature);
|
||||
|
||||
boost::optional<mapnik::marker_ptr> marker;
|
||||
if ( !filename.empty() )
|
||||
{
|
||||
marker = marker_cache::instance()->find(filename, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
marker.reset(boost::shared_ptr<mapnik::marker> (new mapnik::marker()));
|
||||
}
|
||||
|
||||
if (marker)
|
||||
{
|
||||
for (unsigned i=0; i<feature.num_geometries(); ++i)
|
||||
{
|
||||
for (unsigned i=0; i<feature.num_geometries(); ++i)
|
||||
geometry_type const& geom = feature.get_geometry(i);
|
||||
double x;
|
||||
double y;
|
||||
double z=0;
|
||||
|
||||
geom.label_position(&x,&y);
|
||||
prj_trans.backward(x,y,z);
|
||||
t_.forward(&x,&y);
|
||||
|
||||
int w = (*marker)->width();
|
||||
int h = (*marker)->height();
|
||||
|
||||
int px = int(floor(x - 0.5 * w));
|
||||
int py = int(floor(y - 0.5 * h));
|
||||
box2d<double> label_ext (px, py, px + w, py + h);
|
||||
if (sym.get_allow_overlap() ||
|
||||
detector_.has_placement(label_ext))
|
||||
{
|
||||
geometry_type const& geom = feature.get_geometry(i);
|
||||
|
||||
geom.label_position(&x,&y);
|
||||
prj_trans.backward(x,y,z);
|
||||
t_.forward(&x,&y);
|
||||
int w = (*data)->width();
|
||||
int h = (*data)->height();
|
||||
int px = int(floor(x - 0.5 * w));
|
||||
int py = int(floor(y - 0.5 * h));
|
||||
box2d<double> label_ext (px, py, px + w, py + h);
|
||||
if (sym.get_allow_overlap() ||
|
||||
detector_.has_placement(label_ext))
|
||||
{
|
||||
pixmap_.set_rectangle_alpha2(*(*data), px, py, sym.get_opacity());
|
||||
if (!sym.get_ignore_placement())
|
||||
detector_.insert(label_ext);
|
||||
metawriter_with_properties writer = sym.get_metawriter();
|
||||
if (writer.first) writer.first->add_box(label_ext, feature, t_, writer.second);
|
||||
}
|
||||
agg::trans_affine tr;
|
||||
boost::array<double,6> const& m = sym.get_transform();
|
||||
tr.load_from(&m[0]);
|
||||
|
||||
render_marker(px,py,**marker,tr, sym.get_opacity());
|
||||
|
||||
if (!sym.get_ignore_placement())
|
||||
detector_.insert(label_ext);
|
||||
metawriter_with_properties writer = sym.get_metawriter();
|
||||
if (writer.first) writer.first->add_box(label_ext, feature, t_, writer.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template void agg_renderer<image_32>::process(point_symbolizer const&,
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
// mapnik
|
||||
#include <mapnik/agg_renderer.hpp>
|
||||
#include <mapnik/agg_rasterizer.hpp>
|
||||
#include <mapnik/image_cache.hpp>
|
||||
#include <mapnik/marker_cache.hpp>
|
||||
|
||||
// agg
|
||||
#include "agg_basics.h"
|
||||
|
@ -73,7 +73,15 @@ void agg_renderer<T>::process(polygon_pattern_symbolizer const& sym,
|
|||
ras_ptr->gamma(agg::gamma_linear());
|
||||
|
||||
std::string filename = path_processor_type::evaluate( *sym.get_filename(), feature);
|
||||
boost::optional<image_ptr> pat = image_cache::instance()->find(filename,true);
|
||||
boost::optional<mapnik::marker_ptr> marker;
|
||||
if ( !filename.empty() )
|
||||
{
|
||||
marker = marker_cache::instance()->find(filename, true);
|
||||
}
|
||||
|
||||
if (!marker || !(*marker)->is_bitmap()) return;
|
||||
|
||||
boost::optional<image_ptr> pat = (*marker)->get_bitmap_data();
|
||||
|
||||
if (!pat) return;
|
||||
|
||||
|
|
|
@ -24,16 +24,13 @@
|
|||
#include <mapnik/agg_renderer.hpp>
|
||||
#include <mapnik/agg_rasterizer.hpp>
|
||||
#include <mapnik/image_util.hpp>
|
||||
#include <mapnik/image_cache.hpp>
|
||||
#include <mapnik/svg/marker_cache.hpp>
|
||||
#include <mapnik/marker_cache.hpp>
|
||||
#include <mapnik/svg/svg_converter.hpp>
|
||||
#include <mapnik/svg/svg_renderer.hpp>
|
||||
#include <mapnik/svg/svg_path_adapter.hpp>
|
||||
|
||||
#include "agg_basics.h"
|
||||
#include "agg_rendering_buffer.h"
|
||||
#include "agg_pixfmt_rgba.h"
|
||||
#include "agg_rasterizer_scanline_aa.h"
|
||||
#include "agg_scanline_u.h"
|
||||
|
||||
namespace mapnik {
|
||||
|
@ -44,12 +41,6 @@ void agg_renderer<T>::process(shield_symbolizer const& sym,
|
|||
proj_transform const& prj_trans)
|
||||
{
|
||||
typedef coord_transform2<CoordTransform,geometry_type> path_type;
|
||||
typedef agg::pixfmt_rgba32 pixfmt;
|
||||
typedef agg::renderer_base<pixfmt> renderer_base;
|
||||
typedef agg::renderer_scanline_aa_solid<renderer_base> renderer_solid;
|
||||
|
||||
|
||||
metawriter_with_properties writer = sym.get_metawriter();
|
||||
|
||||
UnicodeString text;
|
||||
if( sym.get_no_text() )
|
||||
|
@ -77,310 +68,161 @@ void agg_renderer<T>::process(shield_symbolizer const& sym,
|
|||
tr = agg::trans_affine_scaling(scale_factor_) * tr;
|
||||
|
||||
std::string filename = path_processor_type::evaluate( *sym.get_filename(), feature);
|
||||
|
||||
if (is_svg(filename)) //SVG Label
|
||||
boost::optional<mapnik::marker_ptr> marker;
|
||||
if ( !filename.empty() )
|
||||
{
|
||||
using namespace mapnik::svg;
|
||||
boost::optional<path_ptr> marker = marker_cache::instance()->find(filename, true);
|
||||
ras_ptr->reset();
|
||||
ras_ptr->gamma(agg::gamma_linear());
|
||||
agg::scanline_u8 sl;
|
||||
agg::rendering_buffer buf(pixmap_.raw_data(), width_, height_, width_ * 4);
|
||||
pixfmt pixf(buf);
|
||||
renderer_base renb(pixf);
|
||||
renderer_solid ren(renb);
|
||||
|
||||
if (text.length() > 0 && marker && *marker)
|
||||
{
|
||||
face_set_ptr faces;
|
||||
|
||||
box2d<double> const& bbox = (*marker)->bounding_box();
|
||||
double x1 = bbox.minx();
|
||||
double y1 = bbox.miny();
|
||||
double x2 = bbox.maxx();
|
||||
double y2 = bbox.maxy();
|
||||
|
||||
agg::trans_affine recenter = agg::trans_affine_translation(-0.5*(x1+x2),-0.5*(y1+y2));
|
||||
tr.transform(&x1,&y1);
|
||||
tr.transform(&x2,&y2);
|
||||
|
||||
vertex_stl_adapter<svg_path_storage> stl_storage((*marker)->source());
|
||||
svg_path_adapter svg_path(stl_storage);
|
||||
svg_renderer<svg_path_adapter,
|
||||
agg::pod_bvector<path_attributes> > svg_renderer(svg_path,
|
||||
(*marker)->attributes());
|
||||
|
||||
int w = int(x2 - x1);
|
||||
int h = int(y2 - y1);
|
||||
|
||||
if (sym.get_fontset().size() > 0)
|
||||
{
|
||||
faces = font_manager_.get_face_set(sym.get_fontset());
|
||||
}
|
||||
else
|
||||
{
|
||||
faces = font_manager_.get_face_set(sym.get_face_name());
|
||||
}
|
||||
|
||||
stroker_ptr strk = font_manager_.get_stroker();
|
||||
if (strk && faces->size() > 0)
|
||||
{
|
||||
text_renderer<T> text_ren(pixmap_, faces, *strk);
|
||||
|
||||
text_ren.set_pixel_size(sym.get_text_size() * scale_factor_);
|
||||
text_ren.set_fill(sym.get_fill());
|
||||
text_ren.set_halo_fill(sym.get_halo_fill());
|
||||
text_ren.set_halo_radius(sym.get_halo_radius() * scale_factor_);
|
||||
text_ren.set_opacity(sym.get_text_opacity());
|
||||
|
||||
placement_finder<label_collision_detector4> finder(detector_);
|
||||
|
||||
string_info info(text);
|
||||
|
||||
faces->get_string_info(info);
|
||||
|
||||
unsigned num_geom = feature.num_geometries();
|
||||
|
||||
for (unsigned i=0;i<num_geom;++i)
|
||||
{
|
||||
geometry_type const& geom = feature.get_geometry(i);
|
||||
if (geom.num_points() > 0 )
|
||||
{
|
||||
path_type path(t_,geom,prj_trans);
|
||||
position const& shield_pos = sym.get_shield_displacement();
|
||||
label_placement_enum how_placed = sym.get_label_placement();
|
||||
|
||||
if (how_placed == POINT_PLACEMENT || how_placed == VERTEX_PLACEMENT)
|
||||
{
|
||||
// for every vertex, try and place a shield/text
|
||||
geom.rewind(0);
|
||||
for( unsigned jj = 0; jj < geom.num_points(); jj++ )
|
||||
{
|
||||
double label_x;
|
||||
double label_y;
|
||||
double z=0.0;
|
||||
placement text_placement(info, sym, scale_factor_, w, h, false);
|
||||
text_placement.avoid_edges = sym.get_avoid_edges();
|
||||
text_placement.allow_overlap = sym.get_allow_overlap();
|
||||
if( how_placed == VERTEX_PLACEMENT )
|
||||
geom.vertex(&label_x,&label_y); // by vertex
|
||||
else
|
||||
geom.middle_point(&label_x, &label_y); // by middle of line
|
||||
|
||||
prj_trans.backward(label_x,label_y, z);
|
||||
t_.forward(&label_x,&label_y);
|
||||
|
||||
// position SVG label at vertex + shield_displacement
|
||||
label_x = label_x + boost::get<0>(shield_pos) * scale_factor_;
|
||||
label_y = label_y + boost::get<1>(shield_pos) * scale_factor_;
|
||||
// position text relative to label position
|
||||
// NOTE: (text) displacement applied in placement_finder.cpp FIXME!
|
||||
finder.find_point_placement( text_placement,label_x,label_y,0.0,
|
||||
sym.get_vertical_alignment(),
|
||||
sym.get_line_spacing() * scale_factor_,
|
||||
sym.get_character_spacing() * scale_factor_,
|
||||
sym.get_horizontal_alignment(),
|
||||
sym.get_justify_alignment() );
|
||||
|
||||
// check to see if image overlaps anything too, there is only ever 1 placement found for points and verticies
|
||||
if( text_placement.placements.size() > 0)
|
||||
{
|
||||
|
||||
double x = text_placement.placements[0].starting_x;
|
||||
double y = text_placement.placements[0].starting_y;
|
||||
box2d<double> label_ext;
|
||||
label_ext.init( floor(label_x - 0.5 * w), floor(label_y - 0.5 * h),
|
||||
ceil (label_x + 0.5 * w), ceil (label_y + 0.5 * h) );
|
||||
if ( sym.get_allow_overlap() || detector_.has_placement(label_ext) )
|
||||
{
|
||||
agg::trans_affine matrix = recenter * tr * agg::trans_affine_translation(label_x, label_y);
|
||||
svg_renderer.render(*ras_ptr, sl, ren, matrix, sym.get_opacity());
|
||||
box2d<double> dim = text_ren.prepare_glyphs(&text_placement.placements[0]);
|
||||
text_ren.render(x,y);
|
||||
detector_.insert(label_ext);
|
||||
finder.update_detector(text_placement);
|
||||
if (writer.first) {
|
||||
writer.first->add_box(label_ext, feature, t_, writer.second);
|
||||
writer.first->add_text(text_placement, faces, feature, t_, writer.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (geom.num_points() > 1 && sym.get_label_placement() == LINE_PLACEMENT)
|
||||
{
|
||||
placement text_placement(info, sym, scale_factor_, w, h, true);
|
||||
text_placement.avoid_edges = sym.get_avoid_edges();
|
||||
finder.find_point_placements<path_type>(text_placement,path);
|
||||
|
||||
for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ ii)
|
||||
{
|
||||
|
||||
double x = floor(text_placement.placements[ii].starting_x);
|
||||
double y = floor(text_placement.placements[ii].starting_y);
|
||||
agg::trans_affine matrix = recenter * tr * agg::trans_affine_translation(x, y);
|
||||
svg_renderer.render(*ras_ptr, sl, ren, matrix, sym.get_opacity());
|
||||
if (writer.first) writer.first->add_box(box2d<double>(x,y,x+w,y+h), feature, t_, writer.second);
|
||||
box2d<double> dim = text_ren.prepare_glyphs(&text_placement.placements[ii]);
|
||||
text_ren.render(x,y);
|
||||
}
|
||||
finder.update_detector(text_placement);
|
||||
if (writer.first) writer.first->add_text(text_placement, faces, feature, t_, writer.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
marker = marker_cache::instance()->find(filename, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
boost::optional<image_ptr> data = image_cache::instance()->find(filename,true);
|
||||
marker.reset(boost::shared_ptr<mapnik::marker> (new mapnik::marker()));
|
||||
}
|
||||
|
||||
if (text.length() > 0 && data)
|
||||
if (text.length() > 0 && marker)
|
||||
{
|
||||
face_set_ptr faces;
|
||||
|
||||
if (sym.get_fontset().size() > 0)
|
||||
{
|
||||
face_set_ptr faces;
|
||||
faces = font_manager_.get_face_set(sym.get_fontset());
|
||||
}
|
||||
else
|
||||
{
|
||||
faces = font_manager_.get_face_set(sym.get_face_name());
|
||||
}
|
||||
|
||||
if (sym.get_fontset().size() > 0)
|
||||
stroker_ptr strk = font_manager_.get_stroker();
|
||||
if (strk && faces->size() > 0)
|
||||
{
|
||||
text_renderer<T> ren(pixmap_, faces, *strk);
|
||||
|
||||
ren.set_pixel_size(sym.get_text_size() * scale_factor_);
|
||||
ren.set_fill(sym.get_fill());
|
||||
ren.set_halo_fill(sym.get_halo_fill());
|
||||
ren.set_halo_radius(sym.get_halo_radius() * scale_factor_);
|
||||
ren.set_opacity(sym.get_text_opacity());
|
||||
|
||||
placement_finder<label_collision_detector4> finder(detector_);
|
||||
|
||||
string_info info(text);
|
||||
|
||||
faces->get_string_info(info);
|
||||
|
||||
int w = (*marker)->width();
|
||||
int h = (*marker)->height();
|
||||
|
||||
metawriter_with_properties writer = sym.get_metawriter();
|
||||
|
||||
for (unsigned i = 0; i < feature.num_geometries(); ++i)
|
||||
{
|
||||
faces = font_manager_.get_face_set(sym.get_fontset());
|
||||
}
|
||||
else
|
||||
{
|
||||
faces = font_manager_.get_face_set(sym.get_face_name());
|
||||
}
|
||||
|
||||
stroker_ptr strk = font_manager_.get_stroker();
|
||||
if (strk && faces->size() > 0)
|
||||
{
|
||||
text_renderer<T> ren(pixmap_, faces, *strk);
|
||||
|
||||
ren.set_pixel_size(sym.get_text_size() * scale_factor_);
|
||||
ren.set_fill(sym.get_fill());
|
||||
ren.set_halo_fill(sym.get_halo_fill());
|
||||
ren.set_halo_radius(sym.get_halo_radius() * scale_factor_);
|
||||
ren.set_opacity(sym.get_text_opacity());
|
||||
|
||||
placement_finder<label_collision_detector4> finder(detector_);
|
||||
|
||||
string_info info(text);
|
||||
|
||||
faces->get_string_info(info);
|
||||
|
||||
|
||||
int w = (*data)->width();
|
||||
int h = (*data)->height();
|
||||
|
||||
unsigned num_geom = feature.num_geometries();
|
||||
for (unsigned i=0;i<num_geom;++i)
|
||||
geometry_type const& geom = feature.get_geometry(i);
|
||||
if (geom.num_points() > 0 )
|
||||
{
|
||||
geometry_type const& geom = feature.get_geometry(i);
|
||||
if (geom.num_points() > 0 )
|
||||
path_type path(t_,geom,prj_trans);
|
||||
|
||||
label_placement_enum how_placed = sym.get_label_placement();
|
||||
if (how_placed == POINT_PLACEMENT || how_placed == VERTEX_PLACEMENT)
|
||||
{
|
||||
path_type path(t_,geom,prj_trans);
|
||||
|
||||
label_placement_enum how_placed = sym.get_label_placement();
|
||||
if (how_placed == POINT_PLACEMENT || how_placed == VERTEX_PLACEMENT)
|
||||
// for every vertex, try and place a shield/text
|
||||
geom.rewind(0);
|
||||
placement text_placement(info, sym, scale_factor_, w, h, false);
|
||||
text_placement.avoid_edges = sym.get_avoid_edges();
|
||||
text_placement.allow_overlap = sym.get_allow_overlap();
|
||||
position const& pos = sym.get_displacement();
|
||||
position const& shield_pos = sym.get_shield_displacement();
|
||||
for( unsigned jj = 0; jj < geom.num_points(); jj++ )
|
||||
{
|
||||
// for every vertex, try and place a shield/text
|
||||
geom.rewind(0);
|
||||
placement text_placement(info, sym, scale_factor_, w, h, false);
|
||||
text_placement.avoid_edges = sym.get_avoid_edges();
|
||||
text_placement.allow_overlap = sym.get_allow_overlap();
|
||||
position const& pos = sym.get_displacement();
|
||||
position const& shield_pos = sym.get_shield_displacement();
|
||||
for( unsigned jj = 0; jj < geom.num_points(); jj++ )
|
||||
double label_x;
|
||||
double label_y;
|
||||
double z=0.0;
|
||||
|
||||
if( how_placed == VERTEX_PLACEMENT )
|
||||
geom.vertex(&label_x,&label_y); // by vertex
|
||||
else
|
||||
geom.label_position(&label_x, &label_y); // by middle of line or by point
|
||||
prj_trans.backward(label_x,label_y, z);
|
||||
t_.forward(&label_x,&label_y);
|
||||
|
||||
label_x += boost::get<0>(shield_pos);
|
||||
label_y += boost::get<1>(shield_pos);
|
||||
|
||||
finder.find_point_placement( text_placement,label_x,label_y,0.0,
|
||||
sym.get_vertical_alignment(),
|
||||
sym.get_line_spacing(),
|
||||
sym.get_character_spacing(),
|
||||
sym.get_horizontal_alignment(),
|
||||
sym.get_justify_alignment() );
|
||||
|
||||
// check to see if image overlaps anything too, there is only ever 1 placement found for points and verticies
|
||||
if( text_placement.placements.size() > 0)
|
||||
{
|
||||
double label_x;
|
||||
double label_y;
|
||||
double z=0.0;
|
||||
|
||||
if( how_placed == VERTEX_PLACEMENT )
|
||||
geom.vertex(&label_x,&label_y); // by vertex
|
||||
else
|
||||
geom.label_position(&label_x, &label_y); // by middle of line or by point
|
||||
prj_trans.backward(label_x,label_y, z);
|
||||
t_.forward(&label_x,&label_y);
|
||||
|
||||
label_x += boost::get<0>(shield_pos);
|
||||
label_y += boost::get<1>(shield_pos);
|
||||
|
||||
finder.find_point_placement( text_placement,label_x,label_y,0.0,
|
||||
sym.get_vertical_alignment(),
|
||||
sym.get_line_spacing(),
|
||||
sym.get_character_spacing(),
|
||||
sym.get_horizontal_alignment(),
|
||||
sym.get_justify_alignment() );
|
||||
double x = floor(text_placement.placements[0].starting_x);
|
||||
double y = floor(text_placement.placements[0].starting_y);
|
||||
int px;
|
||||
int py;
|
||||
box2d<double> label_ext;
|
||||
|
||||
// check to see if image overlaps anything too, there is only ever 1 placement found for points and verticies
|
||||
if( text_placement.placements.size() > 0)
|
||||
if( !sym.get_unlock_image() )
|
||||
{
|
||||
double x = floor(text_placement.placements[0].starting_x);
|
||||
double y = floor(text_placement.placements[0].starting_y);
|
||||
int px;
|
||||
int py;
|
||||
box2d<double> label_ext;
|
||||
// center image at text center position
|
||||
// remove displacement from image label
|
||||
double lx = x - boost::get<0>(pos);
|
||||
double ly = y - boost::get<1>(pos);
|
||||
px=int(floor(lx - (0.5 * w)));
|
||||
py=int(floor(ly - (0.5 * h)));
|
||||
label_ext.init( floor(lx - 0.5 * w), floor(ly - 0.5 * h), ceil (lx + 0.5 * w), ceil (ly + 0.5 * h) );
|
||||
}
|
||||
else
|
||||
{ // center image at reference location
|
||||
px=int(floor(label_x - 0.5 * w));
|
||||
py=int(floor(label_y - 0.5 * h));
|
||||
label_ext.init( floor(label_x - 0.5 * w), floor(label_y - 0.5 * h), ceil (label_x + 0.5 * w), ceil (label_y + 0.5 * h));
|
||||
}
|
||||
|
||||
if( !sym.get_unlock_image() )
|
||||
{
|
||||
// center image at text center position
|
||||
// remove displacement from image label
|
||||
double lx = x - boost::get<0>(pos);
|
||||
double ly = y - boost::get<1>(pos);
|
||||
px=int(floor(lx - (0.5 * w)));
|
||||
py=int(floor(ly - (0.5 * h)));
|
||||
label_ext.init( floor(lx - 0.5 * w), floor(ly - 0.5 * h), ceil (lx + 0.5 * w), ceil (ly + 0.5 * h) );
|
||||
}
|
||||
else
|
||||
{ // center image at reference location
|
||||
px=int(floor(label_x - 0.5 * w));
|
||||
py=int(floor(label_y - 0.5 * h));
|
||||
label_ext.init( floor(label_x - 0.5 * w), floor(label_y - 0.5 * h), ceil (label_x + 0.5 * w), ceil (label_y + 0.5 * h));
|
||||
}
|
||||
if ( sym.get_allow_overlap() || detector_.has_placement(label_ext) )
|
||||
{
|
||||
render_marker(px,py,**marker,tr,sym.get_opacity());
|
||||
|
||||
if ( sym.get_allow_overlap() || detector_.has_placement(label_ext) )
|
||||
{
|
||||
//pixmap_.set_rectangle_alpha(px,py,*data);
|
||||
pixmap_.set_rectangle_alpha2(*(*data),px,py,sym.get_opacity());
|
||||
box2d<double> dim = ren.prepare_glyphs(&text_placement.placements[0]);
|
||||
ren.render(x,y);
|
||||
detector_.insert(label_ext);
|
||||
finder.update_detector(text_placement);
|
||||
if (writer.first) {
|
||||
writer.first->add_box(box2d<double>(px,py,px+w,py+h), feature, t_, writer.second);
|
||||
writer.first->add_text(text_placement, faces, feature, t_, writer.second);
|
||||
}
|
||||
box2d<double> dim = ren.prepare_glyphs(&text_placement.placements[0]);
|
||||
ren.render(x,y);
|
||||
detector_.insert(label_ext);
|
||||
finder.update_detector(text_placement);
|
||||
if (writer.first) {
|
||||
writer.first->add_box(box2d<double>(px,py,px+w,py+h), feature, t_, writer.second);
|
||||
writer.first->add_text(text_placement, faces, feature, t_, writer.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (geom.num_points() > 1 && sym.get_label_placement() == LINE_PLACEMENT)
|
||||
else if (geom.num_points() > 1 && how_placed == LINE_PLACEMENT)
|
||||
{
|
||||
placement text_placement(info, sym, scale_factor_, w, h, true);
|
||||
|
||||
text_placement.avoid_edges = sym.get_avoid_edges();
|
||||
finder.find_point_placements<path_type>(text_placement,path);
|
||||
|
||||
position const& pos = sym.get_displacement();
|
||||
for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ ii)
|
||||
{
|
||||
placement text_placement(info, sym, scale_factor_, w, h, true);
|
||||
text_placement.avoid_edges = sym.get_avoid_edges();
|
||||
finder.find_point_placements<path_type>(text_placement,path);
|
||||
position const& pos = sym.get_displacement();
|
||||
for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ ii)
|
||||
{
|
||||
double x = floor(text_placement.placements[ii].starting_x);
|
||||
double y = floor(text_placement.placements[ii].starting_y);
|
||||
|
||||
double lx = x - boost::get<0>(pos);
|
||||
double ly = y - boost::get<1>(pos);
|
||||
int px=int(floor(lx - (0.5*w)));
|
||||
int py=int(floor(ly - (0.5*h)));
|
||||
|
||||
//pixmap_.set_rectangle_alpha(px,py,*data);
|
||||
pixmap_.set_rectangle_alpha2(*(*data),px,py,sym.get_opacity());
|
||||
if (writer.first) writer.first->add_box(box2d<double>(px,py,px+w,py+h), feature, t_, writer.second);
|
||||
|
||||
box2d<double> dim = ren.prepare_glyphs(&text_placement.placements[ii]);
|
||||
ren.render(x,y);
|
||||
}
|
||||
finder.update_detector(text_placement);
|
||||
if (writer.first) writer.first->add_text(text_placement, faces, feature, t_, writer.second);
|
||||
double x = floor(text_placement.placements[ii].starting_x);
|
||||
double y = floor(text_placement.placements[ii].starting_y);
|
||||
|
||||
double lx = x - boost::get<0>(pos);
|
||||
double ly = y - boost::get<1>(pos);
|
||||
int px=int(floor(lx - (0.5*w)));
|
||||
int py=int(floor(ly - (0.5*h)));
|
||||
|
||||
render_marker(px,py,**marker,tr,sym.get_opacity());
|
||||
|
||||
if (writer.first) writer.first->add_box(box2d<double>(px,py,px+w,py+h), feature, t_, writer.second);
|
||||
|
||||
box2d<double> dim = ren.prepare_glyphs(&text_placement.placements[ii]);
|
||||
ren.render(x,y);
|
||||
}
|
||||
finder.update_detector(text_placement);
|
||||
if (writer.first) writer.first->add_text(text_placement, faces, feature, t_, writer.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,10 @@
|
|||
#include <mapnik/arrow.hpp>
|
||||
#include <mapnik/config_error.hpp>
|
||||
#include <mapnik/parse_path.hpp>
|
||||
#include <mapnik/image_cache.hpp>
|
||||
#include <mapnik/marker_cache.hpp>
|
||||
#include <mapnik/svg/svg_path_adapter.hpp>
|
||||
#include <mapnik/svg/svg_path_attributes.hpp>
|
||||
|
||||
// cairo
|
||||
#include <cairomm/context.h>
|
||||
#include <cairomm/surface.h>
|
||||
|
@ -123,6 +126,62 @@ private:
|
|||
Cairo::RefPtr<Cairo::SurfacePattern> pattern_;
|
||||
};
|
||||
|
||||
class cairo_gradient : private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
cairo_gradient(const mapnik::gradient &grad, double opacity=1.0)
|
||||
{
|
||||
double x1,x2,y1,y2,r;
|
||||
grad.get_control_points(x1,y1,x2,y2,r);
|
||||
if (grad.get_gradient_type() == LINEAR)
|
||||
{
|
||||
pattern_ = Cairo::LinearGradient::create(x1, y1, x2, y2);
|
||||
}
|
||||
else if (grad.get_gradient_type() == RADIAL)
|
||||
{
|
||||
pattern_ = Cairo::RadialGradient::create(x1, y1, 0, x2, y2, r);
|
||||
}
|
||||
|
||||
units_ = grad.get_units();
|
||||
|
||||
BOOST_FOREACH ( mapnik::stop_pair const& st, grad.get_stop_array() )
|
||||
{
|
||||
mapnik::color const& stop_color = st.second;
|
||||
double r= static_cast<double> (stop_color.red())/255.0;
|
||||
double g= static_cast<double> (stop_color.green())/255.0;
|
||||
double b= static_cast<double> (stop_color.blue())/255.0;
|
||||
double a= static_cast<double> (stop_color.alpha())/255.0;
|
||||
pattern_->add_color_stop_rgba(st.first, r, g, b, a*opacity);
|
||||
}
|
||||
|
||||
double m[6];
|
||||
agg::trans_affine tr = grad.get_transform();
|
||||
tr.invert();
|
||||
tr.store_to(m);
|
||||
pattern_->set_matrix(Cairo::Matrix(m[0],m[1],m[2],m[3],m[4],m[5]));
|
||||
}
|
||||
|
||||
~cairo_gradient(void)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Cairo::RefPtr<Cairo::Gradient> const& gradient(void) const
|
||||
{
|
||||
return pattern_;
|
||||
}
|
||||
|
||||
gradient_unit_e units() const
|
||||
{
|
||||
return units_;
|
||||
}
|
||||
|
||||
private:
|
||||
Cairo::RefPtr<Cairo::Gradient> pattern_;
|
||||
gradient_unit_e units_;
|
||||
|
||||
};
|
||||
|
||||
class cairo_face : private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
|
@ -265,6 +324,11 @@ public:
|
|||
context_->set_dash(d, 0.0);
|
||||
}
|
||||
|
||||
void set_fill_rule(Cairo::FillRule fill_rule)
|
||||
{
|
||||
context_->set_fill_rule(fill_rule);
|
||||
}
|
||||
|
||||
void move_to(double x, double y)
|
||||
{
|
||||
#if CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 6, 0)
|
||||
|
@ -277,6 +341,15 @@ public:
|
|||
context_->move_to(x, y);
|
||||
}
|
||||
|
||||
void curve_to(double ct1_x, double ct1_y, double ct2_x, double ct2_y, double end_x, double end_y)
|
||||
{
|
||||
context_->curve_to(ct1_x,ct1_y,ct2_x,ct2_y,end_x,end_y);
|
||||
}
|
||||
|
||||
void close_path()
|
||||
{
|
||||
context_->close_path();
|
||||
}
|
||||
|
||||
void line_to(double x, double y)
|
||||
{
|
||||
|
@ -291,11 +364,11 @@ public:
|
|||
}
|
||||
|
||||
template <typename T>
|
||||
void add_path(T& path)
|
||||
void add_path(T& path, unsigned start_index = 0)
|
||||
{
|
||||
double x, y;
|
||||
|
||||
path.rewind(0);
|
||||
path.rewind(start_index);
|
||||
|
||||
for (unsigned cm = path.vertex(&x, &y); cm != SEG_END; cm = path.vertex(&x, &y))
|
||||
{
|
||||
|
@ -310,6 +383,63 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void add_agg_path(T& path, unsigned start_index = 0)
|
||||
{
|
||||
double x, y;
|
||||
|
||||
path.rewind(start_index);
|
||||
|
||||
for (unsigned cm = path.vertex(&x, &y); !agg::is_stop(cm); cm = path.vertex(&x, &y))
|
||||
{
|
||||
if (agg::is_move_to(cm))
|
||||
{
|
||||
move_to(x, y);
|
||||
}
|
||||
else if (agg::is_drawing(cm))
|
||||
{
|
||||
if (agg::is_curve3(cm))
|
||||
{
|
||||
double end_x;
|
||||
double end_y;
|
||||
std::cerr << "Curve 3 not implemented" << std::endl;
|
||||
path.vertex(&end_x, &end_y);
|
||||
|
||||
curve_to(x,y,x,y,end_x,end_y);
|
||||
}
|
||||
else if (agg::is_curve4(cm))
|
||||
{
|
||||
double ct2_x;
|
||||
double ct2_y;
|
||||
double end_x;
|
||||
double end_y;
|
||||
|
||||
path.vertex(&ct2_x, &ct2_y);
|
||||
path.vertex(&end_x, &end_y);
|
||||
|
||||
curve_to(x,y,ct2_x,ct2_y,end_x,end_y);
|
||||
}
|
||||
else if (agg::is_line_to(cm))
|
||||
{
|
||||
line_to(x, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Unimplemented drawing command: " << cm << std::endl;
|
||||
move_to(x, y);
|
||||
}
|
||||
}
|
||||
else if (agg::is_close(cm))
|
||||
{
|
||||
close_path();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Unimplemented path command: " << cm << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rectangle(double x, double y, double w, double h)
|
||||
{
|
||||
context_->rectangle(x, y, w, h);
|
||||
|
@ -335,6 +465,29 @@ public:
|
|||
context_->set_source(pattern.pattern());
|
||||
}
|
||||
|
||||
void set_gradient(cairo_gradient const& pattern, const box2d<double> &bbox)
|
||||
{
|
||||
Cairo::RefPtr<Cairo::Gradient> p = pattern.gradient();
|
||||
|
||||
double bx1=bbox.minx();
|
||||
double by1=bbox.miny();
|
||||
double bx2=bbox.maxx();
|
||||
double by2=bbox.maxy();
|
||||
if (pattern.units() != USER_SPACE_ON_USE)
|
||||
{
|
||||
if (pattern.units() == OBJECT_BOUNDING_BOX)
|
||||
{
|
||||
context_->get_path_extents (bx1, by1, bx2, by2);
|
||||
}
|
||||
Cairo::Matrix m = p->get_matrix();
|
||||
m.scale(1.0/(bx2-bx1),1.0/(by2-by1));
|
||||
m.translate(-bx1,-by1);
|
||||
p->set_matrix(m);
|
||||
}
|
||||
|
||||
context_->set_source(p);
|
||||
}
|
||||
|
||||
void add_image(double x, double y, image_data_32 & data, double opacity = 1.0)
|
||||
{
|
||||
cairo_pattern pattern(data);
|
||||
|
@ -362,6 +515,26 @@ public:
|
|||
context_->set_matrix(matrix);
|
||||
}
|
||||
|
||||
void transform(Cairo::Matrix const& matrix)
|
||||
{
|
||||
context_->transform(matrix);
|
||||
}
|
||||
|
||||
void translate(double x, double y)
|
||||
{
|
||||
context_->translate(x,y);
|
||||
}
|
||||
|
||||
void save()
|
||||
{
|
||||
context_->save();
|
||||
}
|
||||
|
||||
void restore()
|
||||
{
|
||||
context_->restore();
|
||||
}
|
||||
|
||||
void show_glyph(unsigned long index, double x, double y)
|
||||
{
|
||||
Cairo::Glyph glyph;
|
||||
|
@ -734,25 +907,123 @@ void cairo_renderer_base::process(line_symbolizer const& sym,
|
|||
}
|
||||
}
|
||||
|
||||
void cairo_renderer_base::render_marker(const int x, const int y, marker &marker, const agg::trans_affine & tr, double opacity)
|
||||
|
||||
{
|
||||
cairo_context context(context_);
|
||||
if (marker.is_vector())
|
||||
{
|
||||
box2d<double> bbox;
|
||||
bbox = (*marker.get_vector_data())->bounding_box();
|
||||
|
||||
coord<double,2> c = bbox.center();
|
||||
agg::trans_affine recenter = agg::trans_affine_translation(0.5 * marker.width()-c.x,0.5 * marker.height()-c.y);
|
||||
|
||||
agg::trans_affine mtx = tr;
|
||||
mtx = recenter * mtx;
|
||||
mtx *= agg::trans_affine_translation(x, y);
|
||||
|
||||
typedef coord_transform2<CoordTransform,geometry_type> path_type;
|
||||
mapnik::path_ptr vmarker = *marker.get_vector_data();
|
||||
|
||||
agg::pod_bvector<path_attributes> const & attributes_ = vmarker->attributes();
|
||||
for(unsigned i = 0; i < attributes_.size(); ++i)
|
||||
{
|
||||
mapnik::svg::path_attributes const& attr = attributes_[i];
|
||||
if (!attr.visibility_flag)
|
||||
continue;
|
||||
|
||||
context.save();
|
||||
|
||||
agg::trans_affine transform = attr.transform;
|
||||
transform *= mtx;
|
||||
|
||||
if (transform.is_valid() && !transform.is_identity())
|
||||
{
|
||||
double m[6];
|
||||
transform.store_to(m);
|
||||
context.transform(Cairo::Matrix(m[0],m[1],m[2],m[3],m[4],m[5]));
|
||||
}
|
||||
|
||||
vertex_stl_adapter<svg_path_storage> stl_storage(vmarker->source());
|
||||
svg_path_adapter svg_path(stl_storage);
|
||||
|
||||
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.opacity*opacity);
|
||||
|
||||
context.set_gradient(g,bbox);
|
||||
context.fill();
|
||||
}
|
||||
else if(attr.fill_flag)
|
||||
{
|
||||
context.set_color(attr.fill_color.r,attr.fill_color.g,attr.fill_color.b,attr.opacity*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.opacity*opacity);
|
||||
context.set_gradient(g,bbox);
|
||||
context.stroke();
|
||||
}
|
||||
else if(attr.stroke_flag)
|
||||
{
|
||||
context.set_color(attr.stroke_color.r,attr.stroke_color.g,attr.stroke_color.b,attr.opacity*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();
|
||||
}
|
||||
}
|
||||
|
||||
context.restore();
|
||||
}
|
||||
}
|
||||
else if (marker.is_bitmap())
|
||||
{
|
||||
context.add_image(x, y, **marker.get_bitmap_data(), opacity);
|
||||
}
|
||||
}
|
||||
|
||||
void cairo_renderer_base::process(point_symbolizer const& sym,
|
||||
Feature const& feature,
|
||||
proj_transform const& prj_trans)
|
||||
{
|
||||
std::string filename = path_processor_type::evaluate( *sym.get_filename(), feature);
|
||||
boost::optional<mapnik::image_ptr> data;
|
||||
|
||||
if ( filename.empty() )
|
||||
|
||||
boost::optional<marker_ptr> marker;
|
||||
if ( !filename.empty() )
|
||||
{
|
||||
// default OGC 4x4 black pixel
|
||||
data = boost::optional<mapnik::image_ptr>(new image_data_32(4,4));
|
||||
(*data)->set(0xff000000);
|
||||
marker = marker_cache::instance()->find(filename, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = mapnik::image_cache::instance()->find(filename,true);
|
||||
marker.reset(boost::shared_ptr<mapnik::marker> (new mapnik::marker()));
|
||||
}
|
||||
|
||||
if (data)
|
||||
|
||||
if (marker)
|
||||
{
|
||||
for (unsigned i = 0; i < feature.num_geometries(); ++i)
|
||||
{
|
||||
|
@ -760,23 +1031,28 @@ void cairo_renderer_base::process(point_symbolizer const& sym,
|
|||
double x;
|
||||
double y;
|
||||
double z = 0;
|
||||
|
||||
|
||||
geom.label_position(&x, &y);
|
||||
prj_trans.backward(x, y, z);
|
||||
t_.forward(&x, &y);
|
||||
|
||||
int w = (*data)->width();
|
||||
int h = (*data)->height();
|
||||
|
||||
int w = (*marker)->width();
|
||||
int h = (*marker)->height();
|
||||
|
||||
int px = int(floor(x - 0.5 * w));
|
||||
int py = int(floor(y - 0.5 * h));
|
||||
box2d<double> label_ext (px, py, px + w, py + h);
|
||||
if (sym.get_allow_overlap() ||
|
||||
detector_.has_placement(label_ext))
|
||||
detector_.has_placement(label_ext))
|
||||
{
|
||||
cairo_context context(context_);
|
||||
agg::trans_affine mtx;
|
||||
boost::array<double,6> const& m = sym.get_transform();
|
||||
mtx.load_from(&m[0]);
|
||||
|
||||
context.add_image(px, py, *(*data), sym.get_opacity());
|
||||
detector_.insert(label_ext);
|
||||
render_marker(px,py,**marker, mtx, sym.get_opacity());
|
||||
|
||||
if (!sym.get_ignore_placement())
|
||||
detector_.insert(label_ext);
|
||||
metawriter_with_properties writer = sym.get_metawriter();
|
||||
if (writer.first)
|
||||
{
|
||||
|
@ -793,15 +1069,42 @@ void cairo_renderer_base::process(shield_symbolizer const& sym,
|
|||
{
|
||||
typedef coord_transform2<CoordTransform,geometry_type> path_type;
|
||||
|
||||
expression_ptr name_expr = sym.get_name();
|
||||
if (!name_expr) return;
|
||||
value_type result = boost::apply_visitor(evaluate<Feature,value_type>(feature),*name_expr);
|
||||
UnicodeString text = result.to_unicode();
|
||||
UnicodeString text;
|
||||
if( sym.get_no_text() )
|
||||
text = UnicodeString( " " ); // TODO: fix->use 'space' as the text to render
|
||||
else
|
||||
{
|
||||
expression_ptr name_expr = sym.get_name();
|
||||
if (!name_expr) return;
|
||||
value_type result = boost::apply_visitor(evaluate<Feature,value_type>(feature),*name_expr);
|
||||
text = result.to_unicode();
|
||||
}
|
||||
|
||||
if ( sym.get_text_transform() == UPPERCASE)
|
||||
{
|
||||
text = text.toUpper();
|
||||
}
|
||||
else if ( sym.get_text_transform() == LOWERCASE)
|
||||
{
|
||||
text = text.toLower();
|
||||
}
|
||||
|
||||
agg::trans_affine tr;
|
||||
boost::array<double,6> const& m = sym.get_transform();
|
||||
tr.load_from(&m[0]);
|
||||
|
||||
std::string filename = path_processor_type::evaluate( *sym.get_filename(), feature);
|
||||
boost::optional<mapnik::image_ptr> data = mapnik::image_cache::instance()->find(filename,true);
|
||||
|
||||
if (text.length() > 0 && data)
|
||||
boost::optional<marker_ptr> marker;
|
||||
if ( !filename.empty() )
|
||||
{
|
||||
marker = marker_cache::instance()->find(filename, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
marker.reset(boost::shared_ptr<mapnik::marker> (new mapnik::marker()));
|
||||
}
|
||||
|
||||
if (text.length() > 0 && marker)
|
||||
{
|
||||
face_set_ptr faces;
|
||||
|
||||
|
@ -819,85 +1122,124 @@ void cairo_renderer_base::process(shield_symbolizer const& sym,
|
|||
cairo_context context(context_);
|
||||
string_info info(text);
|
||||
|
||||
placement_finder<label_collision_detector4> finder(detector_);
|
||||
|
||||
faces->set_pixel_sizes(sym.get_text_size());
|
||||
faces->get_string_info(info);
|
||||
|
||||
placement_finder<label_collision_detector4> finder(detector_);
|
||||
|
||||
int w = (*data)->width();
|
||||
int h = (*data)->height();
|
||||
int w = (*marker)->width();
|
||||
int h = (*marker)->height();
|
||||
|
||||
metawriter_with_properties writer = sym.get_metawriter();
|
||||
|
||||
for (unsigned i = 0; i < feature.num_geometries(); ++i)
|
||||
{
|
||||
geometry_type const& geom = feature.get_geometry(i);
|
||||
|
||||
if (geom.num_points() > 0) // don't bother with empty geometries
|
||||
{
|
||||
path_type path(t_, geom, prj_trans);
|
||||
|
||||
if (sym.get_label_placement() == POINT_PLACEMENT)
|
||||
label_placement_enum how_placed = sym.get_label_placement();
|
||||
if (how_placed == POINT_PLACEMENT || how_placed == VERTEX_PLACEMENT)
|
||||
{
|
||||
double label_x;
|
||||
double label_y;
|
||||
double z = 0.0;
|
||||
// for every vertex, try and place a shield/text
|
||||
geom.rewind(0);
|
||||
placement text_placement(info, sym, 1.0, w, h, false);
|
||||
|
||||
text_placement.avoid_edges = sym.get_avoid_edges();
|
||||
geom.label_position(&label_x, &label_y);
|
||||
prj_trans.backward(label_x, label_y, z);
|
||||
t_.forward(&label_x, &label_y);
|
||||
finder.find_point_placement(text_placement, label_x, label_y);
|
||||
text_placement.allow_overlap = sym.get_allow_overlap();
|
||||
position const& pos = sym.get_displacement();
|
||||
position const& shield_pos = sym.get_shield_displacement();
|
||||
for( unsigned jj = 0; jj < geom.num_points(); jj++ )
|
||||
{
|
||||
double label_x;
|
||||
double label_y;
|
||||
double z=0.0;
|
||||
|
||||
for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ ii)
|
||||
{
|
||||
double x = text_placement.placements[ii].starting_x;
|
||||
double y = text_placement.placements[ii].starting_y;
|
||||
// remove displacement from image label
|
||||
position pos = sym.get_displacement();
|
||||
double lx = x - boost::get<0>(pos);
|
||||
double ly = y - boost::get<1>(pos);
|
||||
int px = int(lx - (0.5 * w)) ;
|
||||
int py = int(ly - (0.5 * h)) ;
|
||||
box2d<double> label_ext (floor(lx - 0.5 * w), floor(ly - 0.5 * h), ceil (lx + 0.5 * w), ceil (ly + 0.5 * h));
|
||||
if( how_placed == VERTEX_PLACEMENT )
|
||||
geom.vertex(&label_x,&label_y); // by vertex
|
||||
else
|
||||
geom.label_position(&label_x, &label_y); // by middle of line or by point
|
||||
prj_trans.backward(label_x,label_y, z);
|
||||
t_.forward(&label_x,&label_y);
|
||||
|
||||
if (detector_.has_placement(label_ext))
|
||||
{
|
||||
context.add_image(px, py, *(*data));
|
||||
context.add_text(text_placement.placements[ii],
|
||||
face_manager_,
|
||||
faces,
|
||||
sym.get_text_size(),
|
||||
sym.get_fill(),
|
||||
sym.get_halo_radius(),
|
||||
sym.get_halo_fill()
|
||||
);
|
||||
if (writer.first) {
|
||||
writer.first->add_box(box2d<double>(px,py,px+w,py+h), feature, t_, writer.second);
|
||||
writer.first->add_text(text_placement, faces, feature, t_, writer.second); //Only 1 placement
|
||||
label_x += boost::get<0>(shield_pos);
|
||||
label_y += boost::get<1>(shield_pos);
|
||||
|
||||
finder.find_point_placement( text_placement,label_x,label_y,0.0,
|
||||
sym.get_vertical_alignment(),
|
||||
sym.get_line_spacing(),
|
||||
sym.get_character_spacing(),
|
||||
sym.get_horizontal_alignment(),
|
||||
sym.get_justify_alignment() );
|
||||
|
||||
for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ ii)
|
||||
{
|
||||
double x = text_placement.placements[ii].starting_x;
|
||||
double y = text_placement.placements[ii].starting_y;
|
||||
|
||||
int px;
|
||||
int py;
|
||||
box2d<double> label_ext;
|
||||
|
||||
if( !sym.get_unlock_image() )
|
||||
{
|
||||
// center image at text center position
|
||||
// remove displacement from image label
|
||||
double lx = x - boost::get<0>(pos);
|
||||
double ly = y - boost::get<1>(pos);
|
||||
px=int(floor(lx - (0.5 * w)));
|
||||
py=int(floor(ly - (0.5 * h)));
|
||||
label_ext.init( floor(lx - 0.5 * w), floor(ly - 0.5 * h), ceil (lx + 0.5 * w), ceil (ly + 0.5 * h) );
|
||||
}
|
||||
else
|
||||
{ // center image at reference location
|
||||
px=int(floor(label_x - 0.5 * w));
|
||||
py=int(floor(label_y - 0.5 * h));
|
||||
label_ext.init( floor(label_x - 0.5 * w), floor(label_y - 0.5 * h), ceil (label_x + 0.5 * w), ceil (label_y + 0.5 * h));
|
||||
}
|
||||
detector_.insert(label_ext);
|
||||
}
|
||||
}
|
||||
|
||||
finder.update_detector(text_placement);
|
||||
if ( sym.get_allow_overlap() || detector_.has_placement(label_ext) )
|
||||
{
|
||||
render_marker(px,py,**marker, tr, sym.get_opacity());
|
||||
|
||||
context.add_text(text_placement.placements[ii],
|
||||
face_manager_,
|
||||
faces,
|
||||
sym.get_text_size(),
|
||||
sym.get_fill(),
|
||||
sym.get_halo_radius(),
|
||||
sym.get_halo_fill()
|
||||
);
|
||||
if (writer.first) {
|
||||
writer.first->add_box(box2d<double>(px,py,px+w,py+h), feature, t_, writer.second);
|
||||
writer.first->add_text(text_placement, faces, feature, t_, writer.second); //Only 1 placement
|
||||
}
|
||||
detector_.insert(label_ext);
|
||||
}
|
||||
}
|
||||
|
||||
finder.update_detector(text_placement);
|
||||
}
|
||||
}
|
||||
else if (geom.num_points() > 1 && sym.get_label_placement() == LINE_PLACEMENT)
|
||||
else if (geom.num_points() > 1 && how_placed == LINE_PLACEMENT)
|
||||
{
|
||||
placement text_placement(info, sym, 1.0, w, h, true);
|
||||
|
||||
text_placement.avoid_edges = sym.get_avoid_edges();
|
||||
finder.find_point_placements<path_type>(text_placement, path);
|
||||
|
||||
position const& pos = sym.get_displacement();
|
||||
for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ ii)
|
||||
{
|
||||
double x = text_placement.placements[ii].starting_x;
|
||||
double y = text_placement.placements[ii].starting_y;
|
||||
int px = int(x - (w/2));
|
||||
int py = int(y - (h/2));
|
||||
double lx = x - boost::get<0>(pos);
|
||||
double ly = y - boost::get<1>(pos);
|
||||
int px=int(floor(lx - (0.5*w)));
|
||||
int py=int(floor(ly - (0.5*h)));
|
||||
|
||||
render_marker(x,y,**marker, tr, sym.get_opacity());
|
||||
|
||||
context.add_image(px, py, *(*data));
|
||||
context.add_text(text_placement.placements[ii],
|
||||
face_manager_,
|
||||
faces,
|
||||
|
@ -924,14 +1266,14 @@ void cairo_renderer_base::process(line_pattern_symbolizer const& sym,
|
|||
typedef coord_transform2<CoordTransform,geometry_type> path_type;
|
||||
|
||||
std::string filename = path_processor_type::evaluate( *sym.get_filename(), feature);
|
||||
boost::optional<mapnik::image_ptr> image = mapnik::image_cache::instance()->find(filename,true);
|
||||
if (!image) return;
|
||||
boost::optional<mapnik::marker_ptr> marker = mapnik::marker_cache::instance()->find(filename,true);
|
||||
if (!marker && !(*marker)->is_bitmap()) return;
|
||||
|
||||
unsigned width((*image)->width());
|
||||
unsigned height((*image)->height());
|
||||
unsigned width((*marker)->width());
|
||||
unsigned height((*marker)->height());
|
||||
|
||||
cairo_context context(context_);
|
||||
cairo_pattern pattern(*(*image));
|
||||
cairo_pattern pattern(**((*marker)->get_bitmap_data()));
|
||||
|
||||
pattern.set_extend(Cairo::EXTEND_REPEAT);
|
||||
pattern.set_filter(Cairo::FILTER_BILINEAR);
|
||||
|
@ -994,11 +1336,10 @@ void cairo_renderer_base::process(polygon_pattern_symbolizer const& sym,
|
|||
|
||||
cairo_context context(context_);
|
||||
std::string filename = path_processor_type::evaluate( *sym.get_filename(), feature);
|
||||
boost::optional<mapnik::image_ptr> image = mapnik::image_cache::instance()->find(filename,true);
|
||||
boost::optional<mapnik::marker_ptr> marker = mapnik::marker_cache::instance()->find(filename,true);
|
||||
if (!marker && !(*marker)->is_bitmap()) return;
|
||||
|
||||
if (!image) return;
|
||||
|
||||
cairo_pattern pattern(*(*image));
|
||||
cairo_pattern pattern(**((*marker)->get_bitmap_data()));
|
||||
|
||||
pattern.set_extend(Cairo::EXTEND_REPEAT);
|
||||
|
||||
|
|
143
src/gradient.cpp
Normal file
143
src/gradient.cpp
Normal file
|
@ -0,0 +1,143 @@
|
|||
/*****************************************************************************
|
||||
*
|
||||
* This file is part of Mapnik (c++ mapping toolkit)
|
||||
*
|
||||
* Copyright (C) 2010 Artem Pavelenko
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
#include <mapnik/gradient.hpp>
|
||||
#include <iostream>
|
||||
|
||||
namespace mapnik
|
||||
{
|
||||
|
||||
static const char * gradient_strings[] = {
|
||||
"no-gradient",
|
||||
"linear",
|
||||
"radial",
|
||||
""
|
||||
};
|
||||
|
||||
IMPLEMENT_ENUM( gradient_e, gradient_strings );
|
||||
|
||||
|
||||
gradient::gradient()
|
||||
: gradient_type_(NO_GRADIENT),
|
||||
stops_(),
|
||||
x1_(0),
|
||||
y1_(0),
|
||||
x2_(0),
|
||||
y2_(0),
|
||||
r_(0),
|
||||
units_(OBJECT_BOUNDING_BOX)
|
||||
{
|
||||
}
|
||||
|
||||
gradient::gradient(gradient const& other)
|
||||
: gradient_type_(other.gradient_type_),
|
||||
stops_(other.stops_),
|
||||
x1_(other.x1_),
|
||||
y1_(other.y1_),
|
||||
x2_(other.x2_),
|
||||
y2_(other.y2_),
|
||||
r_(other.r_),
|
||||
units_(other.units_),
|
||||
transform_(other.transform_)
|
||||
{}
|
||||
|
||||
gradient & gradient::operator=(const gradient& rhs)
|
||||
{
|
||||
gradient tmp(rhs);
|
||||
swap(tmp);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void gradient::set_gradient_type(gradient_e grad)
|
||||
{
|
||||
gradient_type_=grad;
|
||||
}
|
||||
|
||||
gradient_e gradient::get_gradient_type() const
|
||||
{
|
||||
return gradient_type_;
|
||||
}
|
||||
|
||||
void gradient::set_transform(agg::trans_affine transform)
|
||||
{
|
||||
transform_ = transform;
|
||||
}
|
||||
agg::trans_affine gradient::get_transform() const
|
||||
{
|
||||
return transform_;
|
||||
}
|
||||
|
||||
void gradient::set_units(gradient_unit_e units)
|
||||
{
|
||||
units_ = units;
|
||||
}
|
||||
gradient_unit_e gradient::get_units() const
|
||||
{
|
||||
return units_;
|
||||
}
|
||||
|
||||
void gradient::add_stop(double offset,mapnik::color const& c)
|
||||
{
|
||||
stops_.push_back(mapnik::stop_pair(offset,c));
|
||||
}
|
||||
|
||||
bool gradient::has_stop() const
|
||||
{
|
||||
return ! stops_.empty();
|
||||
}
|
||||
|
||||
stop_array const& gradient::get_stop_array() const
|
||||
{
|
||||
return stops_;
|
||||
}
|
||||
|
||||
void gradient::swap(const gradient& other) throw()
|
||||
{
|
||||
gradient_type_=other.gradient_type_;
|
||||
stops_=other.stops_;
|
||||
units_=other.units_;
|
||||
transform_=other.transform_;
|
||||
other.get_control_points(x1_,y1_,x2_,y2_,r_);
|
||||
}
|
||||
|
||||
void gradient::set_control_points(double x1, double y1, double x2, double y2, double r)
|
||||
{
|
||||
x1_ = x1;
|
||||
y1_ = y1;
|
||||
x2_ = x2;
|
||||
y2_ = y2;
|
||||
r_ = r;
|
||||
}
|
||||
void gradient::get_control_points(double &x1, double &y1, double &x2, double &y2, double &r) const
|
||||
{
|
||||
get_control_points(x1,y1,x2,y2);
|
||||
r=r_;
|
||||
}
|
||||
void gradient::get_control_points(double &x1, double &y1, double &x2, double &y2) const
|
||||
{
|
||||
x1 = x1_;
|
||||
y1 = y1_;
|
||||
x2 = x2_;
|
||||
y2 = y2_;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
/*****************************************************************************
|
||||
*
|
||||
* This file is part of Mapnik (c++ mapping toolkit)
|
||||
*
|
||||
* Copyright (C) 2009 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
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
//$Id$
|
||||
|
||||
// mapnik
|
||||
#include <mapnik/image_cache.hpp>
|
||||
#include <mapnik/image_reader.hpp>
|
||||
|
||||
// boost
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
namespace mapnik
|
||||
{
|
||||
|
||||
boost::unordered_map<std::string, image_ptr> image_cache::cache_;
|
||||
|
||||
bool image_cache::insert (std::string const& uri, image_ptr img)
|
||||
{
|
||||
#ifdef MAPNIK_THREADSAFE
|
||||
mutex::scoped_lock lock(mutex_);
|
||||
#endif
|
||||
return cache_.insert(std::make_pair(uri,img)).second;
|
||||
}
|
||||
|
||||
boost::optional<image_ptr> image_cache::find(std::string const& uri, bool update_cache)
|
||||
{
|
||||
#ifdef MAPNIK_THREADSAFE
|
||||
mutex::scoped_lock lock(mutex_);
|
||||
#endif
|
||||
typedef boost::unordered_map<std::string, image_ptr>::const_iterator iterator_type;
|
||||
boost::optional<image_ptr> result;
|
||||
iterator_type itr = cache_.find(uri);
|
||||
if (itr != cache_.end())
|
||||
{
|
||||
result.reset(itr->second);
|
||||
return result;
|
||||
}
|
||||
|
||||
// we can't find image in cache, lets try to load it from filesystem
|
||||
boost::filesystem::path path(uri);
|
||||
if (exists(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
std::auto_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(uri));
|
||||
if (reader.get())
|
||||
{
|
||||
unsigned width = reader->width();
|
||||
unsigned height = reader->height();
|
||||
BOOST_ASSERT(width > 0 && height > 0);
|
||||
mapnik::image_ptr image(new mapnik::image_data_32(width,height));
|
||||
reader->read(0,0,*image);
|
||||
result.reset(image);
|
||||
if (update_cache)
|
||||
{
|
||||
cache_.insert(std::make_pair(uri,image));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catch (...)
|
||||
{
|
||||
std::cerr << "Exception caught while loading image: " << uri << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "### WARNING image does not exist: " << uri << std::endl;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef MAPNIK_THREADSAFE
|
||||
boost::mutex image_cache::mutex_;
|
||||
#endif
|
||||
|
||||
}
|
|
@ -23,11 +23,13 @@
|
|||
//$Id$
|
||||
|
||||
// mapnik
|
||||
#include <mapnik/svg/marker_cache.hpp>
|
||||
#include <mapnik/marker_cache.hpp>
|
||||
#include <mapnik/svg/svg_parser.hpp>
|
||||
#include <mapnik/svg/svg_storage.hpp>
|
||||
#include <mapnik/svg/svg_path_adapter.hpp>
|
||||
#include <mapnik/svg/svg_converter.hpp>
|
||||
#include <mapnik/image_util.hpp>
|
||||
#include <mapnik/image_reader.hpp>
|
||||
|
||||
// boost
|
||||
#include <boost/assert.hpp>
|
||||
|
@ -37,9 +39,9 @@
|
|||
namespace mapnik
|
||||
{
|
||||
|
||||
boost::unordered_map<std::string, path_ptr> marker_cache::cache_;
|
||||
boost::unordered_map<std::string, marker_ptr> marker_cache::cache_;
|
||||
|
||||
bool marker_cache::insert (std::string const& uri, path_ptr path)
|
||||
bool marker_cache::insert (std::string const& uri, marker_ptr path)
|
||||
{
|
||||
#ifdef MAPNIK_THREADSAFE
|
||||
mutex::scoped_lock lock(mutex_);
|
||||
|
@ -47,13 +49,13 @@ bool marker_cache::insert (std::string const& uri, path_ptr path)
|
|||
return cache_.insert(std::make_pair(uri,path)).second;
|
||||
}
|
||||
|
||||
boost::optional<path_ptr> marker_cache::find(std::string const& uri, bool update_cache)
|
||||
boost::optional<marker_ptr> marker_cache::find(std::string const& uri, bool update_cache)
|
||||
{
|
||||
#ifdef MAPNIK_THREADSAFE
|
||||
mutex::scoped_lock lock(mutex_);
|
||||
#endif
|
||||
typedef boost::unordered_map<std::string, path_ptr>::const_iterator iterator_type;
|
||||
boost::optional<path_ptr> result;
|
||||
typedef boost::unordered_map<std::string, marker_ptr>::const_iterator iterator_type;
|
||||
boost::optional<marker_ptr> result;
|
||||
iterator_type itr = cache_.find(uri);
|
||||
if (itr != cache_.end())
|
||||
{
|
||||
|
@ -65,34 +67,66 @@ boost::optional<path_ptr> marker_cache::find(std::string const& uri, bool update
|
|||
boost::filesystem::path path(uri);
|
||||
if (exists(path))
|
||||
{
|
||||
using namespace mapnik::svg;
|
||||
try
|
||||
if (is_svg(uri))
|
||||
{
|
||||
path_ptr marker(new svg_storage_type);
|
||||
vertex_stl_adapter<svg_path_storage> stl_storage(marker->source());
|
||||
svg_path_adapter svg_path(stl_storage);
|
||||
svg_converter_type svg(svg_path, marker->attributes());
|
||||
|
||||
svg_parser p(svg);
|
||||
p.parse(uri);
|
||||
//svg.arrange_orientations();
|
||||
double lox,loy,hix,hiy;
|
||||
svg.bounding_rect(&lox, &loy, &hix, &hiy);
|
||||
marker->set_bounding_box(lox,loy,hix,hiy);
|
||||
if (update_cache)
|
||||
using namespace mapnik::svg;
|
||||
try
|
||||
{
|
||||
cache_.insert(std::make_pair(uri,marker));
|
||||
path_ptr marker_path(new svg_storage_type);
|
||||
vertex_stl_adapter<svg_path_storage> stl_storage(marker_path->source());
|
||||
svg_path_adapter svg_path(stl_storage);
|
||||
svg_converter_type svg(svg_path, marker_path->attributes());
|
||||
|
||||
svg_parser p(svg);
|
||||
p.parse(uri);
|
||||
//svg.arrange_orientations();
|
||||
double lox,loy,hix,hiy;
|
||||
svg.bounding_rect(&lox, &loy, &hix, &hiy);
|
||||
marker_path->set_bounding_box(lox,loy,hix,hiy);
|
||||
|
||||
marker_ptr mark(new marker(marker_path));
|
||||
result.reset(mark);
|
||||
if (update_cache)
|
||||
{
|
||||
cache_.insert(std::make_pair(uri,*result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::cerr << "Exception caught while loading SVG: " << uri << std::endl;
|
||||
}
|
||||
return marker;
|
||||
}
|
||||
catch (...)
|
||||
else
|
||||
{
|
||||
std::cerr << "Exception caught while loading SVG: " << uri << std::endl;
|
||||
try
|
||||
{
|
||||
std::auto_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(uri));
|
||||
if (reader.get())
|
||||
{
|
||||
unsigned width = reader->width();
|
||||
unsigned height = reader->height();
|
||||
BOOST_ASSERT(width > 0 && height > 0);
|
||||
mapnik::image_ptr image(new mapnik::image_data_32(width,height));
|
||||
reader->read(0,0,*image);
|
||||
marker_ptr mark(new marker(image));
|
||||
result.reset(mark);
|
||||
if (update_cache)
|
||||
{
|
||||
cache_.insert(std::make_pair(uri,*result));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catch (...)
|
||||
{
|
||||
std::cerr << "Exception caught while loading image: " << uri << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "### WARNING SVG does not exist: " << uri << std::endl;
|
||||
std::cerr << "### WARNING Marker does not exist: " << uri << std::endl;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -27,10 +27,15 @@
|
|||
|
||||
#include "agg_ellipse.h"
|
||||
#include "agg_rounded_rect.h"
|
||||
#include "agg_span_gradient.h"
|
||||
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
#include <boost/spirit/include/phoenix_core.hpp>
|
||||
#include <boost/spirit/include/phoenix_operator.hpp>
|
||||
#include <boost/fusion/include/std_pair.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
@ -40,6 +45,8 @@
|
|||
namespace mapnik { namespace svg {
|
||||
|
||||
|
||||
typedef std::vector<std::pair<double, agg::rgba8> > color_lookup_type;
|
||||
|
||||
namespace qi = boost::spirit::qi;
|
||||
|
||||
typedef std::vector<std::pair<std::string, std::string> > pairs_type;
|
||||
|
@ -84,6 +91,30 @@ double parse_double(const char* str)
|
|||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* parse a double that might end with a %
|
||||
* if it does then set the ref bool true and divide the result by 100
|
||||
*/
|
||||
double parse_double_optional_percent(const char* str, bool &percent)
|
||||
{
|
||||
using namespace boost::spirit::qi;
|
||||
using boost::phoenix::ref;
|
||||
|
||||
double val = 0.0;
|
||||
char unit='\0';
|
||||
parse(str, str+ strlen(str),double_[ref(val)=_1] >> *char_('%')[ref(unit)=_1]);
|
||||
if (unit =='%')
|
||||
{
|
||||
percent = true;
|
||||
val/=100.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
percent = false;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
bool parse_style (const char* str, pairs_type & v)
|
||||
{
|
||||
using namespace boost::spirit::qi;
|
||||
|
@ -179,6 +210,24 @@ void svg_parser::start_element(xmlTextReaderPtr reader)
|
|||
{
|
||||
parse_ellipse(reader);
|
||||
}
|
||||
// the gradient tags *should* be in defs, but illustrator seems not to put them in there so
|
||||
// accept them anywhere
|
||||
else if (xmlStrEqual(name, BAD_CAST "linearGradient"))
|
||||
{
|
||||
parse_linear_gradient(reader);
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "radialGradient"))
|
||||
{
|
||||
parse_radial_gradient(reader);
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "stop"))
|
||||
{
|
||||
parse_gradient_stop(reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::clog << "unhandled element: " << name << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void svg_parser::end_element(xmlTextReaderPtr reader)
|
||||
|
@ -193,6 +242,10 @@ void svg_parser::end_element(xmlTextReaderPtr reader)
|
|||
{
|
||||
is_defs_ = false;
|
||||
}
|
||||
else if ((xmlStrEqual(name, BAD_CAST "linearGradient")) || (xmlStrEqual(name, BAD_CAST "radialGradient")))
|
||||
{
|
||||
gradient_map_[temporary_gradient_.first] = temporary_gradient_.second;
|
||||
}
|
||||
}
|
||||
|
||||
void svg_parser::parse_attr(const xmlChar * name, const xmlChar * value )
|
||||
|
@ -209,6 +262,21 @@ void svg_parser::parse_attr(const xmlChar * name, const xmlChar * value )
|
|||
{
|
||||
path_.fill_none();
|
||||
}
|
||||
else if (boost::starts_with((const char*)value, "url(#"))
|
||||
{
|
||||
// see if we have a known gradient fill
|
||||
std::string id = std::string((const char*)&value[5]);
|
||||
// get rid of the trailing )
|
||||
id.erase(id.end()-1);
|
||||
if (gradient_map_.count(id) > 0)
|
||||
{
|
||||
path_.add_fill_gradient(gradient_map_[id]);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Failed to find gradient fill: " << id << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
path_.fill(parse_color((const char*) value));
|
||||
|
@ -231,6 +299,21 @@ void svg_parser::parse_attr(const xmlChar * name, const xmlChar * value )
|
|||
{
|
||||
path_.stroke_none();
|
||||
}
|
||||
else if (boost::starts_with((const char*)value, "url(#"))
|
||||
{
|
||||
// see if we have a known gradient fill
|
||||
std::string id = std::string((const char*)&value[5]);
|
||||
// get rid of the trailing )
|
||||
id.erase(id.end()-1);
|
||||
if (gradient_map_.count(id) > 0)
|
||||
{
|
||||
path_.add_stroke_gradient(gradient_map_[id]);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Failed to find gradient fill: " << id << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
path_.stroke(parse_color((const char*) value));
|
||||
|
@ -276,6 +359,10 @@ void svg_parser::parse_attr(const xmlChar * name, const xmlChar * value )
|
|||
double opacity = parse_double((const char*)value);
|
||||
path_.opacity(opacity);
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "visibility"))
|
||||
{
|
||||
path_.visibility(!xmlStrEqual(value, BAD_CAST "hidden"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -401,9 +488,6 @@ void svg_parser::parse_circle(xmlTextReaderPtr reader)
|
|||
if(r != 0.0)
|
||||
{
|
||||
if(r < 0.0) throw std::runtime_error("parse_circle: Invalid radius");
|
||||
//path_.move_to(cx+r,cy);
|
||||
//path_.arc_to(r,r,0,1,0,cx-r,cy);
|
||||
//path_.arc_to(r,r,0,1,0,cx+r,cy);
|
||||
agg::ellipse c(cx, cy, r, r);
|
||||
path_.storage().concat_path(c);
|
||||
}
|
||||
|
@ -435,9 +519,6 @@ void svg_parser::parse_ellipse(xmlTextReaderPtr reader)
|
|||
{
|
||||
if(rx < 0.0) throw std::runtime_error("parse_ellipse: Invalid rx");
|
||||
if(ry < 0.0) throw std::runtime_error("parse_ellipse: Invalid ry");
|
||||
//path_.move_to(cx+rx,cy);
|
||||
//path_.arc_to(rx,ry,0,1,0,cx-rx,cy);
|
||||
//path_.arc_to(rx,ry,0,1,0,cx+rx,cy);
|
||||
agg::ellipse c(cx, cy, rx, ry);
|
||||
path_.storage().concat_path(c);
|
||||
}
|
||||
|
@ -524,4 +605,237 @@ void svg_parser::parse_rect(xmlTextReaderPtr reader)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* <stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop3763" />
|
||||
*/
|
||||
void svg_parser::parse_gradient_stop(xmlTextReaderPtr reader)
|
||||
{
|
||||
const xmlChar *value;
|
||||
|
||||
double offset = 0.0;
|
||||
mapnik::color stop_color;
|
||||
double opacity = 1.0;
|
||||
|
||||
value = xmlTextReaderGetAttribute(reader, BAD_CAST "offset");
|
||||
if (value) offset = parse_double((const char*)value);
|
||||
|
||||
value = xmlTextReaderGetAttribute(reader, BAD_CAST "style");
|
||||
if (value)
|
||||
{
|
||||
typedef std::vector<std::pair<std::string,std::string> > cont_type;
|
||||
typedef cont_type::value_type value_type;
|
||||
cont_type vec;
|
||||
parse_style((const char*)value, vec);
|
||||
|
||||
BOOST_FOREACH(value_type kv , vec )
|
||||
{
|
||||
if (kv.first == "stop-color")
|
||||
{
|
||||
try
|
||||
{
|
||||
mapnik::color_factory::init_from_string(stop_color,kv.second.c_str());
|
||||
}
|
||||
catch (mapnik::config_error & ex)
|
||||
{
|
||||
std::cerr << ex.what() << std::endl;
|
||||
}
|
||||
}
|
||||
else if (kv.first == "stop-opacity")
|
||||
{
|
||||
opacity = parse_double(kv.second.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value = xmlTextReaderGetAttribute(reader, BAD_CAST "stop-color");
|
||||
if (value)
|
||||
{
|
||||
try
|
||||
{
|
||||
mapnik::color_factory::init_from_string(stop_color,(const char *) value);
|
||||
}
|
||||
catch (mapnik::config_error & ex)
|
||||
{
|
||||
std::cerr << ex.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
value = xmlTextReaderGetAttribute(reader, BAD_CAST "stop-opacity");
|
||||
if (value)
|
||||
{
|
||||
opacity = parse_double((const char *) value);
|
||||
}
|
||||
|
||||
|
||||
stop_color.set_alpha(opacity*255);
|
||||
|
||||
temporary_gradient_.second.add_stop(offset, stop_color);
|
||||
|
||||
std::cerr << "\tFound Stop: " << offset << " " << (unsigned)stop_color.red() << " " << (unsigned)stop_color.green() << " " << (unsigned)stop_color.blue() << " " << (unsigned)stop_color.alpha() << std::endl;
|
||||
|
||||
}
|
||||
|
||||
bool svg_parser::parse_common_gradient(xmlTextReaderPtr reader)
|
||||
{
|
||||
const xmlChar *value;
|
||||
|
||||
std::string id;
|
||||
value = xmlTextReaderGetAttribute(reader, BAD_CAST "id");
|
||||
if (value)
|
||||
{
|
||||
// start a new gradient
|
||||
gradient new_grad;
|
||||
id = std::string((const char *) value);
|
||||
temporary_gradient_ = std::make_pair(id, new_grad);
|
||||
}
|
||||
else
|
||||
{
|
||||
// no point without an ID
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if we should inherit from another tag
|
||||
value = xmlTextReaderGetAttribute(reader, BAD_CAST "xlink:href");
|
||||
if (value && value[0] == '#')
|
||||
{
|
||||
std::string linkid = (const char *) &value[1];
|
||||
if (gradient_map_.count(linkid))
|
||||
{
|
||||
std::cerr << "\tLoading linked gradient properties from " << linkid << std::endl;
|
||||
temporary_gradient_.second = gradient_map_[linkid];
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Failed to find linked gradient " << linkid << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
value = xmlTextReaderGetAttribute(reader, BAD_CAST "gradientUnits");
|
||||
if (value && std::string((const char*) value) == "userSpaceOnUse")
|
||||
{
|
||||
temporary_gradient_.second.set_units(USER_SPACE_ON_USE);
|
||||
}
|
||||
else
|
||||
{
|
||||
temporary_gradient_.second.set_units(OBJECT_BOUNDING_BOX);
|
||||
}
|
||||
|
||||
value = xmlTextReaderGetAttribute(reader, BAD_CAST "gradientTransform");
|
||||
if (value)
|
||||
{
|
||||
agg::trans_affine tr;
|
||||
mapnik::svg::parse_transform((const char*) value,tr);
|
||||
temporary_gradient_.second.set_transform(tr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <radialGradient
|
||||
collect="always"
|
||||
xlink:href="#linearGradient3759"
|
||||
id="radialGradient3765"
|
||||
cx="-1.2957155"
|
||||
cy="-21.425594"
|
||||
fx="-1.2957155"
|
||||
fy="-21.425594"
|
||||
r="5.1999998"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
*/
|
||||
void svg_parser::parse_radial_gradient(xmlTextReaderPtr reader)
|
||||
{
|
||||
if (!parse_common_gradient(reader))
|
||||
return;
|
||||
|
||||
const xmlChar *value;
|
||||
double cx = 0.5;
|
||||
double cy = 0.5;
|
||||
double fx = 0.0;
|
||||
double fy = 0.0;
|
||||
double r = 0.5;
|
||||
bool has_percent=true;
|
||||
|
||||
value = xmlTextReaderGetAttribute(reader, BAD_CAST "cx");
|
||||
if (value) cx = parse_double_optional_percent((const char*)value, has_percent);
|
||||
|
||||
value = xmlTextReaderGetAttribute(reader, BAD_CAST "cy");
|
||||
if (value) cy = parse_double_optional_percent((const char*)value, has_percent);
|
||||
|
||||
value = xmlTextReaderGetAttribute(reader, BAD_CAST "fx");
|
||||
if (value)
|
||||
fx = parse_double_optional_percent((const char*)value, has_percent);
|
||||
else
|
||||
fx = cx;
|
||||
|
||||
value = xmlTextReaderGetAttribute(reader, BAD_CAST "fy");
|
||||
if (value)
|
||||
fy = parse_double_optional_percent((const char*)value, has_percent);
|
||||
else
|
||||
fy = cy;
|
||||
|
||||
value = xmlTextReaderGetAttribute(reader, BAD_CAST "r");
|
||||
if (value) r = parse_double_optional_percent((const char*)value, has_percent);
|
||||
|
||||
// this logic for detecting %'s will not support mixed coordinates.
|
||||
if (has_percent && temporary_gradient_.second.get_units() == USER_SPACE_ON_USE)
|
||||
{
|
||||
temporary_gradient_.second.set_units(USER_SPACE_ON_USE_BOUNDING_BOX);
|
||||
}
|
||||
|
||||
temporary_gradient_.second.set_gradient_type(RADIAL);
|
||||
temporary_gradient_.second.set_control_points(fx,fy,cx,cy,r);
|
||||
// add this here in case we have no end tag, will be replaced if we do
|
||||
gradient_map_[temporary_gradient_.first] = temporary_gradient_.second;
|
||||
|
||||
std::cerr << "Found Radial Gradient: " << " " << cx << " " << cy << " " << fx << " " << fy << " " << r << std::endl;
|
||||
}
|
||||
|
||||
void svg_parser::parse_linear_gradient(xmlTextReaderPtr reader)
|
||||
{
|
||||
if (!parse_common_gradient(reader))
|
||||
return;
|
||||
|
||||
const xmlChar *value;
|
||||
double x1 = 0.0;
|
||||
double x2 = 1.0;
|
||||
double y1 = 0.0;
|
||||
double y2 = 1.0;
|
||||
|
||||
bool has_percent=true;
|
||||
value = xmlTextReaderGetAttribute(reader, BAD_CAST "x1");
|
||||
if (value) x1 = parse_double_optional_percent((const char*)value, has_percent);
|
||||
|
||||
value = xmlTextReaderGetAttribute(reader, BAD_CAST "x2");
|
||||
if (value) x2 = parse_double_optional_percent((const char*)value, has_percent);
|
||||
|
||||
value = xmlTextReaderGetAttribute(reader, BAD_CAST "y1");
|
||||
if (value) y1 = parse_double_optional_percent((const char*)value, has_percent);
|
||||
|
||||
value = xmlTextReaderGetAttribute(reader, BAD_CAST "y2");
|
||||
if (value) y2 = parse_double_optional_percent((const char*)value, has_percent);
|
||||
|
||||
// this logic for detecting %'s will not support mixed coordinates.
|
||||
if (has_percent && temporary_gradient_.second.get_units() == USER_SPACE_ON_USE)
|
||||
{
|
||||
temporary_gradient_.second.set_units(USER_SPACE_ON_USE_BOUNDING_BOX);
|
||||
}
|
||||
|
||||
temporary_gradient_.second.set_gradient_type(LINEAR);
|
||||
temporary_gradient_.second.set_control_points(x1,y1,x2,y2);
|
||||
// add this here in case we have no end tag, will be replaced if we do
|
||||
gradient_map_[temporary_gradient_.first] = temporary_gradient_.second;
|
||||
|
||||
std::cerr << "Found Linear Gradient: " << "(" << x1 << " " << y1 << "),(" << x2 << " " << y2 << ")" << std::endl;
|
||||
}
|
||||
|
||||
void svg_parser::parse_pattern(xmlTextReaderPtr reader)
|
||||
{
|
||||
//const xmlChar *value;
|
||||
}
|
||||
|
||||
}}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<Rule>
|
||||
<ShieldSymbolizer
|
||||
name="[label]"
|
||||
file="../svg/rect.svg"
|
||||
file="../images/dummy.png"
|
||||
face_name="DejaVu Sans Book"
|
||||
size="10"
|
||||
transform="scale(5) translate(15, 15) rotate(20) skewX(20) skewY(5)"
|
||||
|
@ -17,10 +17,44 @@
|
|||
no_text="false"
|
||||
opacity=".5"
|
||||
text-opacity=".3"
|
||||
allow_overlap="true"
|
||||
avoid_edges="false"
|
||||
/>
|
||||
</Rule>
|
||||
|
||||
<Rule>
|
||||
<PointSymbolizer
|
||||
allow_overlap="true" />
|
||||
</Rule>
|
||||
</Style>
|
||||
<Style name="2">
|
||||
<Rule>
|
||||
<LineSymbolizer></LineSymbolizer>
|
||||
</Rule>
|
||||
<Rule>
|
||||
<ShieldSymbolizer
|
||||
name="[label]"
|
||||
file="../svg/ellipses.svg"
|
||||
allow_overlap="true"
|
||||
face_name="DejaVu Sans Book"
|
||||
size="10"
|
||||
no_text="false"
|
||||
opacity=".5"
|
||||
text-opacity=".3"
|
||||
spacing="50"
|
||||
line_spacing="10"
|
||||
placement="line"
|
||||
/>
|
||||
</Rule>
|
||||
</Style>
|
||||
|
||||
<Layer name="line" srs="+init=epsg:4326">
|
||||
<StyleName>2</StyleName>
|
||||
<Datasource>
|
||||
<Parameter name="file">../json/lines.json</Parameter>
|
||||
<Parameter name="layer_by_index">0</Parameter>
|
||||
<Parameter name="type">ogr</Parameter>
|
||||
</Datasource>
|
||||
</Layer>
|
||||
|
||||
<Layer name="point" srs="+init=epsg:4326">
|
||||
<StyleName>1</StyleName>
|
||||
|
|
21
tests/data/json/lines.json
Normal file
21
tests/data/json/lines.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
|
||||
"type": "FeatureCollection",
|
||||
|
||||
/* comment */
|
||||
|
||||
"features": [
|
||||
|
||||
{ "type": "Feature",
|
||||
|
||||
"properties": { "x": 0, "y": 0, "label": "D" },
|
||||
"geometry" : { "type": "LineString", "coordinates": [ [1, 0], [2, 1], [3, 2], [4, 3], [5, 4], [6, 5] ] }
|
||||
},
|
||||
{ "type": "Feature",
|
||||
|
||||
"properties": { "x": 2, "y": 0, "label": "V" },
|
||||
"geometry" : { "type": "LineString", "coordinates": [ [2, 0], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5] ] }
|
||||
},
|
||||
|
||||
]
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
},
|
||||
{ "type": "Feature",
|
||||
|
||||
"properties": { "x": 0, "y": 0, "label": "0,5" },
|
||||
"properties": { "x": 0, "y": 5, "label": "0,5" },
|
||||
"geometry" : { "type": "Point", "coordinates": [ 0, 5 ] }
|
||||
},
|
||||
{ "type": "Feature",
|
||||
|
|
|
@ -1,13 +1,118 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="100%" height="100%" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<ellipse cy="-20" rx="10" ry="4" fill="green"/>
|
||||
<ellipse cy="-20" rx="4" ry="10" fill="blue"/>
|
||||
<circle cy="-20" r="5" stroke="green" stroke-width=".4" fill="red"/>
|
||||
|
||||
</svg>
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="100%"
|
||||
height="100%"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
inkscape:version="0.48.0 r9654"
|
||||
sodipodi:docname="ellipses.svg">
|
||||
<metadata
|
||||
id="metadata16">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1280"
|
||||
inkscape:window-height="758"
|
||||
id="namedview14"
|
||||
showgrid="false"
|
||||
inkscape:zoom="21.841736"
|
||||
inkscape:cx="-11.26864"
|
||||
inkscape:cy="1072.9165"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<defs
|
||||
id="defs12">
|
||||
<linearGradient
|
||||
collect="always"
|
||||
id="linearGradient3775"
|
||||
x1="0"
|
||||
y1="-30"
|
||||
x2="0"
|
||||
y2="-10"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
style="stop-color:#008000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3771" />
|
||||
<stop
|
||||
style="stop-color:#008000;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3773" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
collect="always"
|
||||
id="radialGradient3765"
|
||||
cx="-2.5"
|
||||
cy="-22.5"
|
||||
fx="0.5"
|
||||
fy="-20.5"
|
||||
r="5"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
style="stop-color:#ff0000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3761" />
|
||||
<stop
|
||||
id="stop3764"
|
||||
offset="0.5"
|
||||
style="stop-color:#ff4040;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop3767"
|
||||
offset="0.6"
|
||||
style="stop-color:#80ff80;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#ffacac;stop-opacity:1;"
|
||||
offset="0.75"
|
||||
id="stop3762" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop3763" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<ellipse
|
||||
cy="-20"
|
||||
rx="10"
|
||||
ry="4"
|
||||
fill="green"
|
||||
id="ellipse4"
|
||||
style="fill-opacity:1;fill:url(#linearGradient3775)" />
|
||||
<ellipse
|
||||
cy="-20"
|
||||
rx="4"
|
||||
ry="10"
|
||||
fill="blue"
|
||||
id="ellipse6" />
|
||||
<circle
|
||||
cy="-20"
|
||||
r="5"
|
||||
stroke="green"
|
||||
stroke-width=".4"
|
||||
fill="red"
|
||||
id="circle8"
|
||||
style="fill-opacity:1;fill:url(#radialGradient3765);stroke-width:0.40000000000000002;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 408 B After Width: | Height: | Size: 3 KiB |
15
tests/data/svg/radial.svg
Normal file
15
tests/data/svg/radial.svg
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="500"
|
||||
height="500"
|
||||
version="1.1" >
|
||||
|
||||
<radialGradient id="rad" >
|
||||
<stop style="stop-color:#000000;stop-opacity:1;" offset="0" />
|
||||
<stop style="stop-color:#000000;stop-opacity:0;" offset="1" />
|
||||
</radialGradient>
|
||||
|
||||
<circle fill="url(#rad)" cx="250" cy="250" r="250"/>
|
||||
</svg>
|
After Width: | Height: | Size: 449 B |
32
tests/data/svg/radial_combo.svg
Normal file
32
tests/data/svg/radial_combo.svg
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="500"
|
||||
height="500"
|
||||
id="svg2"
|
||||
version="1.1" >
|
||||
|
||||
<radialGradient id="rad1" cx="0%" cy="0%" r="100%">
|
||||
<stop style="stop-color:#888888;stop-opacity:1;" offset="0" />
|
||||
<stop style="stop-color:#000000;stop-opacity:0;" offset="1" />
|
||||
</radialGradient>
|
||||
<radialGradient id="rad2" gradientUnits="userSpaceOnUse" cx="0" cy="0" r="100">
|
||||
<stop style="stop-color:#FF0000;stop-opacity:1;" offset="0" />
|
||||
<stop style="stop-color:#000000;stop-opacity:0;" offset="1" />
|
||||
</radialGradient>
|
||||
<radialGradient id="rad3" cx="0" cy="1" r="1">
|
||||
<stop style="stop-color:#00FF00;stop-opacity:1;" offset="0" />
|
||||
<stop style="stop-color:#000000;stop-opacity:0;" offset="1" />
|
||||
</radialGradient>
|
||||
<radialGradient id="rad4" gradientUnits="userSpaceOnUse" cx="50%" cy="50%" r="50%">
|
||||
<stop style="stop-color:#0000FF;stop-opacity:1;" offset="0" />
|
||||
<stop style="stop-color:#000000;stop-opacity:0;" offset="1" />
|
||||
</radialGradient>
|
||||
|
||||
<circle fill="url(#rad1)" cx="50" cy="50" r="50"/>
|
||||
<circle fill="url(#rad2)" cx="-50" cy="50" r="50"/>
|
||||
<circle fill="url(#rad3)" cx="50" cy="-50" r="50"/>
|
||||
<circle fill="url(#rad4)" cx="-50" cy="-50" r="50"/>
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
Loading…
Add table
Reference in a new issue