Improved support for international text

- Implementation by @herm for GSOC 2012 (http://mapnik.org/news/2012/10/06/gsoc2012-status9/)
 - C++11 port, improvements, optimizations by @artemp
 - Testing and integration with master by @springmeyer
 - Thank you to all the support from @behdad along the way
 - Thanks for help testing @toton6868, @stephankn, @nirvn, @mfrasca, @simonsonc and many others

Refs: #2073,#2070,#2038,#2037,#1953,#1820,#1819,#1714,#1634,#1547,#1532,#1319,#1208,#1154,#1146
This commit is contained in:
Dane Springmeyer 2013-11-22 00:06:32 -08:00
parent 622f95ec28
commit 64d5153aea
68 changed files with 4067 additions and 2824 deletions

View file

@ -83,6 +83,7 @@ pretty_dep_names = {
'png':'PNG C library | configure with PNG_LIBS & PNG_INCLUDES',
'webp':'WEBP C library | configure with WEBP_LIBS & WEBP_INCLUDES',
'icuuc':'ICU C++ library | configure with ICU_LIBS & ICU_INCLUDES or use ICU_LIB_NAME to specify custom lib name | more info: http://site.icu-project.org/',
'harfbuzz':'HarfBuzz text shaping library | configure with HB_LIBS & HB_INCLUDES',
'z':'Z compression library | more info: http://www.zlib.net/',
'm':'Basic math library, part of C++ stlib',
'pkg-config':'pkg-config tool | more info: http://pkg-config.freedesktop.org',
@ -332,7 +333,8 @@ opts.AddVariables(
PathVariable('ICU_INCLUDES', 'Search path for ICU include files', ICU_INCLUDES_DEFAULT, PathVariable.PathAccept),
PathVariable('ICU_LIBS','Search path for ICU include files',ICU_LIBS_DEFAULT + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
('ICU_LIB_NAME', 'The library name for icu (such as icuuc, sicuuc, or icucore)', 'icuuc', PathVariable.PathAccept),
PathVariable('HB_INCLUDES', 'Search path for HarfBuzz include files', '/usr/include', PathVariable.PathAccept),
PathVariable('HB_LIBS','Search path for HarfBuzz include files','/usr/' + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
BoolVariable('PNG', 'Build Mapnik with PNG read and write support', 'True'),
PathVariable('PNG_INCLUDES', 'Search path for libpng include files', '/usr/include', PathVariable.PathAccept),
PathVariable('PNG_LIBS','Search path for libpng library files','/usr/' + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
@ -1192,7 +1194,7 @@ if not preconfigured:
# Adding the required prerequisite library directories to the include path for
# compiling and the library path for linking, respectively.
for required in ('ICU', 'SQLITE'):
for required in ('ICU', 'SQLITE', 'HB'):
inc_path = env['%s_INCLUDES' % required]
lib_path = env['%s_LIBS' % required]
env.AppendUnique(CPPPATH = os.path.realpath(inc_path))
@ -1220,6 +1222,7 @@ if not preconfigured:
REQUIRED_LIBSHEADERS = [
['z', 'zlib.h', True,'C'],
[env['ICU_LIB_NAME'],'unicode/unistr.h',True,'C++'],
['harfbuzz', 'harfbuzz/hb.h',True,'C++']
]
OPTIONAL_LIBSHEADERS = []

View file

@ -285,11 +285,6 @@ class _Color(Color,_injector):
def __repr__(self):
return "Color(R=%d,G=%d,B=%d,A=%d)" % (self.r,self.g,self.b,self.a)
class _ProcessedText(ProcessedText, _injector):
def append(self, properties, text):
#More pythonic name
self.push_back(properties, text)
class _Symbolizers(Symbolizers,_injector):
def __getitem__(self, idx):

View file

@ -46,7 +46,7 @@
#include <mapnik/expression_node.hpp>
#include <mapnik/value_error.hpp>
#include <mapnik/marker_cache.hpp> // for known_svg_prefix_
#include <mapnik/pixel_position.hpp>
using mapnik::symbolizer;
using mapnik::rule;
@ -66,28 +66,26 @@ using mapnik::path_expression_ptr;
using mapnik::guess_type;
using mapnik::expression_ptr;
using mapnik::parse_path;
using mapnik::position;
namespace {
using namespace boost::python;
tuple get_shield_displacement(const shield_symbolizer& s)
{
position const& pos = s.get_shield_displacement();
return boost::python::make_tuple(pos.first, pos.second);
mapnik::pixel_position const& pos = s.get_shield_displacement();
return boost::python::make_tuple(pos.x, pos.y);
}
void set_shield_displacement(shield_symbolizer & s, boost::python::tuple arg)
{
s.get_placement_options()->defaults.displacement.first = extract<double>(arg[0]);
s.get_placement_options()->defaults.displacement.second = extract<double>(arg[1]);
s.get_placement_options()->defaults.displacement.x = extract<double>(arg[0]);
s.get_placement_options()->defaults.displacement.y = extract<double>(arg[1]);
}
tuple get_text_displacement(const shield_symbolizer& t)
{
position const& pos = t.get_placement_options()->defaults.displacement;
return boost::python::make_tuple(pos.first, pos.second);
mapnik::pixel_position const& pos = t.get_placement_options()->defaults.displacement;
return boost::python::make_tuple(pos.x, pos.y);
}
void set_text_displacement(shield_symbolizer & t, boost::python::tuple arg)

View file

@ -27,11 +27,13 @@
#include <boost/noncopyable.hpp>
#include <mapnik/text/text_properties.hpp>
#include <mapnik/text/placements/simple.hpp>
#include <mapnik/text/placements/list.hpp>
#include <mapnik/text/formatting/text.hpp>
#include <mapnik/text/formatting/list.hpp>
#include <mapnik/text/formatting/format.hpp>
#include <mapnik/text/formatting/expression_format.hpp>
#include <mapnik/text/processed_text.hpp>
#include <mapnik/text/layout.hpp>
#include <mapnik/text_symbolizer.hpp>
#include "mapnik_enumeration.hpp"
@ -96,7 +98,7 @@ public:
boost::python::tuple get_displacement(text_symbolizer_properties const& t)
{
return boost::python::make_tuple(t.displacement.first, t.displacement.second);
return boost::python::make_tuple(t.displacement.x, t.displacement.y);
}
void set_displacement(text_symbolizer_properties &t, boost::python::tuple arg)
@ -112,7 +114,7 @@ void set_displacement(text_symbolizer_properties &t, boost::python::tuple arg)
double x = extract<double>(arg[0]);
double y = extract<double>(arg[1]);
t.displacement = std::make_pair(x, y);
t.displacement.set(x, y);
}
@ -123,7 +125,7 @@ struct NodeWrap: formatting::node, wrapper<formatting::node>
}
void apply(char_properties const& p, feature_impl const& feature, processed_text &output) const
void apply(char_properties_ptr p, feature_impl const& feature, text_layout &output) const
{
python_block_auto_unblock b;
this->get_override("apply")(ptr(&p), ptr(&feature), ptr(&output));
@ -161,7 +163,7 @@ struct TextNodeWrap: formatting::text_node, wrapper<formatting::text_node>
}
virtual void apply(char_properties const& p, feature_impl const& feature, processed_text &output) const
virtual void apply(char_properties_ptr p, feature_impl const& feature, text_layout &output) const
{
if(override o = this->get_override("apply"))
{
@ -174,7 +176,7 @@ struct TextNodeWrap: formatting::text_node, wrapper<formatting::text_node>
}
}
void default_apply(char_properties const& p, feature_impl const& feature, processed_text &output) const
void default_apply(char_properties_ptr p, feature_impl const& feature, text_layout &output) const
{
formatting::text_node::apply(p, feature, output);
}
@ -182,7 +184,7 @@ struct TextNodeWrap: formatting::text_node, wrapper<formatting::text_node>
struct FormatNodeWrap: formatting::format_node, wrapper<formatting::format_node>
{
virtual void apply(char_properties const& p, feature_impl const& feature, processed_text &output) const
virtual void apply(char_properties_ptr p, feature_impl const& feature, text_layout &output) const
{
if(override o = this->get_override("apply"))
{
@ -195,7 +197,7 @@ struct FormatNodeWrap: formatting::format_node, wrapper<formatting::format_node>
}
}
void default_apply(char_properties const& p, feature_impl const& feature, processed_text &output) const
void default_apply(char_properties_ptr p, feature_impl const& feature, text_layout &output) const
{
formatting::format_node::apply(p, feature, output);
}
@ -203,7 +205,7 @@ struct FormatNodeWrap: formatting::format_node, wrapper<formatting::format_node>
struct ExprFormatWrap: formatting::expression_format, wrapper<formatting::expression_format>
{
virtual void apply(char_properties const& p, feature_impl const& feature, processed_text &output) const
virtual void apply(char_properties_ptr p, feature_impl const& feature, text_layout &output) const
{
if(override o = this->get_override("apply"))
{
@ -216,7 +218,7 @@ struct ExprFormatWrap: formatting::expression_format, wrapper<formatting::expres
}
}
void default_apply(char_properties const& p, feature_impl const& feature, processed_text &output) const
void default_apply(char_properties_ptr p, feature_impl const& feature, text_layout &output) const
{
formatting::expression_format::apply(p, feature, output);
}
@ -243,7 +245,7 @@ struct ListNodeWrap: formatting::list_node, wrapper<formatting::list_node>
/* TODO: Add constructor taking variable number of arguments.
http://wiki.python.org/moin/boost.python/HowTo#A.22Raw.22_function */
virtual void apply(char_properties const& p, feature_impl const& feature, processed_text &output) const
virtual void apply(char_properties_ptr p, feature_impl const& feature, text_layout &output) const
{
if(override o = this->get_override("apply"))
{
@ -256,7 +258,7 @@ struct ListNodeWrap: formatting::list_node, wrapper<formatting::list_node>
}
}
void default_apply(char_properties const& p, feature_impl const& feature, processed_text &output) const
void default_apply(char_properties_ptr p, feature_impl const& feature, text_layout &output) const
{
formatting::list_node::apply(p, feature, output);
}
@ -322,12 +324,12 @@ void insert_expression(expression_set *set, expression_ptr p)
set->insert(p);
}
char_properties & get_format(text_symbolizer const& sym)
char_properties_ptr get_format(text_symbolizer const& sym)
{
return sym.get_placement_options()->defaults.format;
}
void set_format(text_symbolizer const& sym, char_properties & format)
void set_format(text_symbolizer const& sym, char_properties_ptr format)
{
sym.get_placement_options()->defaults.format = format;
}
@ -395,7 +397,7 @@ void export_text_placement()
&text_symbolizer::set_placement_options)
//TODO: Check return policy, is there a better way to do this?
.add_property("format",
make_function(&get_format, return_value_policy<reference_existing_object>()),
make_function(&get_format),
&set_format,
"Shortcut for placements.defaults.default_format")
.add_property("properties",
@ -439,6 +441,7 @@ void export_text_placement()
.def_readwrite("largest_bbox_only", &text_symbolizer_properties::largest_bbox_only)
.def_readwrite("text_ratio", &text_symbolizer_properties::text_ratio)
.def_readwrite("wrap_width", &text_symbolizer_properties::wrap_width)
.def_readwrite("wrap_before", &text_symbolizer_properties::wrap_before)
.def_readwrite("format", &text_symbolizer_properties::format)
.add_property ("format_tree",
&text_symbolizer_properties::format_tree,
@ -451,7 +454,8 @@ void export_text_placement()
;
class_with_converter<char_properties>
class_with_converter<char_properties,
std::shared_ptr<char_properties> >
("CharProperties")
.def_readwrite_convert("text_transform", &char_properties::text_transform)
.def_readwrite_convert("fontset", &char_properties::fontset)
@ -463,7 +467,6 @@ void export_text_placement()
.def_readwrite("text_opacity", &char_properties::text_opacity)
.def_readwrite("wrap_char", &char_properties::wrap_char)
.def_readwrite("wrap_character", &char_properties::wrap_char)
.def_readwrite("wrap_before", &char_properties::wrap_before)
.def_readwrite("fill", &char_properties::fill)
.def_readwrite("halo_fill", &char_properties::halo_fill)
.def_readwrite("halo_radius", &char_properties::halo_radius)
@ -486,24 +489,12 @@ void export_text_placement()
("TextPlacementInfo",
init<text_placements const*, double>())
.def("next", pure_virtual(&text_placement_info::next))
.def("get_actual_label_spacing", &text_placement_info::get_actual_label_spacing)
.def("get_actual_minimum_distance", &text_placement_info::get_actual_minimum_distance)
.def("get_actual_minimum_padding", &text_placement_info::get_actual_minimum_padding)
.def_readwrite("properties", &text_placement_info::properties)
.def_readwrite("scale_factor", &text_placement_info::scale_factor)
;
register_ptr_to_python<std::shared_ptr<text_placement_info> >();
class_<processed_text,
std::shared_ptr<processed_text>,
boost::noncopyable>
("ProcessedText", no_init)
.def("push_back", &processed_text::push_back)
.def("clear", &processed_text::clear)
;
class_<expression_set,
std::shared_ptr<expression_set>,
boost::noncopyable>
@ -551,7 +542,6 @@ void export_text_placement()
.def_readwrite_convert("text_opacity", &formatting::format_node::text_opacity)
.def_readwrite_convert("wrap_char", &formatting::format_node::wrap_char)
.def_readwrite_convert("wrap_character", &formatting::format_node::wrap_char)
.def_readwrite_convert("wrap_before", &formatting::format_node::wrap_before)
.def_readwrite_convert("text_transform", &formatting::format_node::text_transform)
.def_readwrite_convert("fill", &formatting::format_node::fill)
.def_readwrite_convert("halo_fill", &formatting::format_node::halo_fill)
@ -591,7 +581,6 @@ void export_text_placement()
.def_readwrite("text_opacity", &formatting::expression_format::text_opacity)
.def_readwrite("wrap_char", &formatting::expression_format::wrap_char)
.def_readwrite("wrap_character", &formatting::expression_format::wrap_char)
.def_readwrite("wrap_before", &formatting::expression_format::wrap_before)
.def_readwrite("fill", &formatting::expression_format::fill)
.def_readwrite("halo_fill", &formatting::expression_format::halo_fill)
.def_readwrite("halo_radius", &formatting::expression_format::halo_radius)

View file

@ -97,12 +97,15 @@ public:
void pad(T padding);
bool from_string(std::string const& str);
bool valid() const;
void move(T x, T y);
// define some operators
box2d_type& operator+=(box2d_type const& other);
box2d_type& operator*=(T);
box2d_type& operator/=(T);
T operator[](int index) const;
box2d_type operator +(T other) const; //enlarge box by given amount
box2d_type& operator +=(T other); //enlarge box by given amount
// compute the bounding box of this one transformed
box2d_type operator* (agg::trans_affine const& tr) const;

View file

@ -33,6 +33,9 @@
#include <mapnik/image_compositing.hpp>
#include <mapnik/font_engine_freetype.hpp>
#include <mapnik/gradient.hpp>
#include <mapnik/text/text_properties.hpp>
#include <mapnik/text/placements_list.hpp>
#include <mapnik/text/glyph_info.hpp>
#include <mapnik/vertex.hpp>
#include <mapnik/noncopyable.hpp>
@ -317,9 +320,9 @@ public:
void translate(double x, double y);
void save();
void restore();
void show_glyph(unsigned long index, double x, double y);
void glyph_path(unsigned long index, double x, double y);
void add_text(text_path const& path,
void show_glyph(unsigned long index, pixel_position const& pos);
void glyph_path(unsigned long index, pixel_position const& pos);
void add_text(glyph_positions_ptr pos,
cairo_face_manager & manager,
face_manager<freetype_engine> & font_manager,
double scale_factor = 1.0);

View file

@ -24,15 +24,9 @@
#define MAPNIK_FONT_ENGINE_FREETYPE_HPP
// mapnik
#include <mapnik/debug.hpp>
#include <mapnik/color.hpp>
#include <mapnik/utils.hpp>
#include <mapnik/config.hpp>
#include <mapnik/box2d.hpp>
#include <mapnik/ctrans.hpp>
#include <mapnik/geometry.hpp>
#include <mapnik/font_set.hpp>
#include <mapnik/text/char_info.hpp>
#include <mapnik/image_compositing.hpp>
#include <mapnik/text_symbolizer.hpp>
#include <mapnik/noncopyable.hpp>
#include <mapnik/value_types.hpp>
@ -40,90 +34,38 @@
// boost
#include <memory>
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/optional.hpp>
#ifdef MAPNIK_THREADSAFE
#include <thread>
#endif
// stl
#include <string>
//// stl
#include <vector>
#include <map>
struct FT_LibraryRec_;
namespace mapnik
{
class font_face;
class text_path;
class string_info;
struct char_properties;
class stroker;
struct glyph_t;
class stroker;
typedef std::shared_ptr<stroker> stroker_ptr;
class font_face_set;
typedef std::shared_ptr<font_face_set> face_set_ptr;
class font_face;
typedef std::shared_ptr<font_face> face_ptr;
class MAPNIK_DECL font_glyph : private mapnik::noncopyable
{
public:
font_glyph(face_ptr face, unsigned index)
: face_(face), index_(index) {}
face_ptr get_face() const
{
return face_;
}
unsigned get_index() const
{
return index_;
}
private:
face_ptr face_;
unsigned index_;
};
typedef std::shared_ptr<font_glyph> glyph_ptr;
class MAPNIK_DECL font_face_set : private mapnik::noncopyable
{
public:
typedef std::vector<face_ptr> container_type;
typedef container_type::size_type size_type;
font_face_set(void)
: faces_(),
dimension_cache_() {}
void add(face_ptr face);
size_type size() const;
glyph_ptr get_glyph(unsigned c) const;
char_info character_dimensions(unsigned c);
void get_string_info(string_info & info, mapnik::value_unicode_string const& ustr, char_properties *format);
void set_pixel_sizes(unsigned size);
void set_character_sizes(double size);
private:
container_type faces_;
std::map<unsigned, char_info> dimension_cache_;
};
typedef std::shared_ptr<font_face_set> face_set_ptr;
typedef std::shared_ptr<stroker> stroker_ptr;
class MAPNIK_DECL freetype_engine
{
public:
static bool is_font_file(std::string const& file_name);
/*! \brief register a font file
* @param file_name path to a font file.
* @return bool - true if at least one face was successfully registered in the file.
*/
static bool register_font(std::string const& file_name);
/*! \brief register a font file
* @param dir - path to a directory containing fonts or subdirectories.
* @param recurse - default false, whether to search for fonts in sub directories.
@ -137,11 +79,11 @@ public:
virtual ~freetype_engine();
freetype_engine();
private:
FT_LibraryRec_ * library_;
FT_LibraryRec_ *library_;
#ifdef MAPNIK_THREADSAFE
static std::mutex mutex_;
#endif
static std::map<std::string,std::pair<int,std::string> > name2file_;
static std::map<std::string, std::pair<int,std::string> > name2file_;
static std::map<std::string, std::string> memory_fonts_;
};
@ -149,7 +91,7 @@ template <typename T>
class MAPNIK_DECL face_manager : private mapnik::noncopyable
{
typedef T font_engine_type;
typedef std::map<std::string,face_ptr> face_ptr_cache_type;
typedef std::map<std::string, face_ptr> face_ptr_cache_type;
public:
face_manager(T & engine)
@ -157,74 +99,13 @@ public:
stroker_(engine_.create_stroker()),
face_ptr_cache_() {}
face_ptr get_face(std::string const& name)
{
face_ptr_cache_type::iterator itr;
itr = face_ptr_cache_.find(name);
if (itr != face_ptr_cache_.end())
{
return itr->second;
}
else
{
face_ptr face = engine_.create_face(name);
if (face)
{
face_ptr_cache_.insert(make_pair(name,face));
}
return face;
}
}
face_ptr get_face(std::string const& name);
face_set_ptr get_face_set(std::string const& name);
face_set_ptr get_face_set(font_set const& fset);
face_set_ptr get_face_set(std::string const& name, boost::optional<font_set> fset);
face_set_ptr get_face_set(std::string const& name)
{
face_set_ptr face_set = std::make_shared<font_face_set>();
if (face_ptr face = get_face(name))
{
face_set->add(face);
}
return face_set;
}
face_set_ptr get_face_set(font_set const& fset)
{
std::vector<std::string> const& names = fset.get_face_names();
face_set_ptr face_set = std::make_shared<font_face_set>();
for ( std::string const& name : names)
{
face_ptr face = get_face(name);
if (face)
{
face_set->add(face);
}
#ifdef MAPNIK_LOG
else
{
MAPNIK_LOG_DEBUG(font_engine_freetype)
<< "Failed to find face '" << name
<< "' in font set '" << fset.get_name() << "'\n";
}
#endif
}
return face_set;
}
face_set_ptr get_face_set(std::string const& name, boost::optional<font_set> fset)
{
if (fset && fset->size() > 0)
{
return get_face_set(*fset);
}
else
{
return get_face_set(name);
}
}
inline stroker_ptr get_stroker()
{
return stroker_;
}
inline stroker_ptr get_stroker() { return stroker_; }
private:
font_engine_type & engine_;
@ -232,31 +113,6 @@ private:
face_ptr_cache_type face_ptr_cache_;
};
template <typename T>
struct text_renderer : private mapnik::noncopyable
{
typedef boost::ptr_vector<glyph_t> glyphs_t;
typedef T pixmap_type;
text_renderer (pixmap_type & pixmap,
face_manager<freetype_engine> & font_manager,
halo_rasterizer_e rasterizer,
composite_mode_e comp_op = src_over,
double scale_factor=1.0);
box2d<double> prepare_glyphs(text_path const& path);
void render(pixel_position const& pos);
void render_id(mapnik::value_integer feature_id,
pixel_position const& pos);
private:
pixmap_type & pixmap_;
face_manager<freetype_engine> & font_manager_;
halo_rasterizer_e rasterizer_;
glyphs_t glyphs_;
composite_mode_e comp_op_;
double scale_factor_;
};
typedef face_manager<freetype_engine> face_manager_freetype;
}

View file

@ -22,13 +22,73 @@
#ifndef MAPNIK_PIXEL_POSITION_HPP
#define MAPNIK_PIXEL_POSITION_HPP
// Store a pixel position.
// stl
#include <iomanip>
namespace mapnik
{
struct rotation;
struct pixel_position
{
double x;
double y;
pixel_position(double x_, double y_) : x(x_), y(y_) { }
pixel_position() : x(0), y(0) { }
pixel_position operator+ (pixel_position const& other) const
{
return pixel_position(x + other.x, y + other.y);
}
pixel_position operator- (pixel_position const& other) const
{
return pixel_position(x - other.x, y - other.y);
}
pixel_position operator* (double other) const
{
return pixel_position(x * other, y * other);
}
void set(double x_, double y_)
{
x = x_;
y = y_;
}
void clear()
{
x = 0;
y = 0;
}
pixel_position rotate(rotation const& rot) const;
pixel_position operator~() const
{
return pixel_position(x, -y);
}
};
inline pixel_position operator* (double factor, pixel_position const& pos)
{
return pixel_position(factor * pos.x, factor * pos.y);
}
template <class charT, class traits>
inline std::basic_ostream<charT,traits>&
operator << (std::basic_ostream<charT,traits>& out,
const pixel_position& e)
{
std::basic_ostringstream<charT,traits> s;
s.copyfmt(out);
s.width(0);
s << '(' << std::fixed << std::setprecision(16)
<< e.x << ", " << e.y << ')';
out << s.str();
return out;
}
}
#endif // MAPNIK_PIXEL_POSITION_HPP

View file

@ -54,11 +54,11 @@ struct MAPNIK_DECL shield_symbolizer : public text_symbolizer,
bool get_unlock_image() const; // image is not locked to the text placement
void set_unlock_image(bool unlock_image);
void set_shield_displacement(double shield_dx, double shield_dy);
position const& get_shield_displacement() const;
pixel_position const& get_shield_displacement() const;
private:
bool unlock_image_;
position shield_displacement_;
pixel_position shield_displacement_;
};
}

View file

@ -0,0 +1,34 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2013 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
*
*****************************************************************************/
#ifndef CHAR_PROPERTIES_PTR_HPP
#define CHAR_PROPERTIES_PTR_HPP
#include <memory>
namespace mapnik
{
struct char_properties;
typedef std::shared_ptr<char_properties> char_properties_ptr;
}
#endif // CHAR_PROPERTIES_PTR_HPP

View file

@ -0,0 +1,92 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2013 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
*
*****************************************************************************/
#ifndef MAPNIK_DUMMY_SHAPER_HPP
#define MAPNIK_DUMMY_SHAPER_HPP
// mapnik
#include <mapnik/text/text_properties.hpp>
#include <mapnik/text/text_line.hpp>
#include <mapnik/text/face.hpp>
// stl
#include <list>
namespace mapnik
{
struct dummy_shaper
{
/* Dummy shaping limitations:
* - LTR text only
* - Only first font in fontset is used
* - No kerning
* - No complex scripts
*/
static void shape_text(text_line & line,
text_itemizer & itemizer,
std::map<unsigned,double> & width_map,
face_manager_freetype & font_manager,
double scale_factor )
{
unsigned start = line.first_char();
unsigned end = line.last_char();
mapnik::value_unicode_string const& text = itemizer.text();
size_t length = end - start;
if (!length) return;
line.reserve(length);
std::list<text_item> const& list = itemizer.itemize(start, end);
for (auto const& text_item : list)
{
face_set_ptr face_set = font_manager.get_face_set(text_item.format->face_name, text_item.format->fontset);
double size = text_item.format->text_size * scale_factor;
face_set->set_character_sizes(size);
if (face_set->begin() == face_set->end()) return; // Invalid face set
face_ptr face = *(face_set->begin());
FT_Face freetype_face = face->get_face();
for (unsigned i = text_item.start; i < text_item.end; ++i)
{
UChar32 c = text.char32At(i);
glyph_info tmp;
tmp.glyph_index = FT_Get_Char_Index(freetype_face, c);
if (tmp.glyph_index == 0) continue; // Skip unknown characters
tmp.char_index = i;
tmp.width = 0; // Filled in by glyph_dimensions
tmp.offset.clear();
tmp.face = face;
tmp.format = text_item.format;
face->glyph_dimensions(tmp);
width_map[i] += tmp.width;
line.add_glyph(tmp, scale_factor);
}
line.update_max_char_height(face->get_char_height());
}
}
};
} // namespace mapnik
#endif // MAPNIK_DUMMY_SHAPER_HPP

View file

@ -19,77 +19,35 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
#ifndef MAPNIK_FACE_HPP
#define MAPNIK_FACE_HPP
#ifndef MAPNIK_FONT_UTIL_HPP
#define MAPNIK_FONT_UTIL_HPP
// mapnik
//mapnik
#include <mapnik/text/glyph_info.hpp>
#include <mapnik/config.hpp>
#include <mapnik/noncopyable.hpp>
#include <string>
// freetype2
extern "C"
{
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_STROKER_H
}
//stl
#include <map>
#include <memory>
#include <string>
#include <vector>
namespace mapnik
{
struct char_properties;
struct glyph_t : mapnik::noncopyable
{
FT_Glyph image;
char_properties *properties;
glyph_t(FT_Glyph image_, char_properties *properties_)
: image(image_),
properties(properties_) {}
~glyph_t()
{
FT_Done_Glyph(image);
}
};
// FT_Stroker wrapper
class stroker : mapnik::noncopyable
{
public:
explicit stroker(FT_Stroker s)
: s_(s) {}
void init(double radius)
{
FT_Stroker_Set(s_, (FT_Fixed) (radius * (1<<6)),
FT_STROKER_LINECAP_ROUND,
FT_STROKER_LINEJOIN_ROUND,
0);
}
FT_Stroker const& get() const
{
return s_;
}
~stroker()
{
FT_Stroker_Done(s_);
}
private:
FT_Stroker s_;
};
class font_face : mapnik::noncopyable
{
public:
font_face(FT_Face face)
: face_(face) {}
font_face(FT_Face face);
std::string family_name() const
{
@ -101,45 +59,59 @@ public:
return std::string(face_->style_name);
}
FT_GlyphSlot glyph() const
{
return face_->glyph;
}
FT_Face get_face() const
{
return face_;
}
unsigned get_char(unsigned c) const
{
return FT_Get_Char_Index(face_, c);
}
double get_char_height() const;
bool set_pixel_sizes(unsigned size)
{
if (! FT_Set_Pixel_Sizes( face_, 0, size ))
return true;
return false;
}
bool set_character_sizes(double size);
bool set_character_sizes(double size)
{
if ( !FT_Set_Char_Size(face_,0,(FT_F26Dot6)(size * (1<<6)),0,0))
return true;
return false;
}
void glyph_dimensions(glyph_info &glyph) const;
~font_face()
{
FT_Done_Face(face_);
}
~font_face();
private:
FT_Face face_;
mutable std::map<glyph_index_t, glyph_info> dimension_cache_;
mutable double char_height_;
};
typedef std::shared_ptr<font_face> face_ptr;
class MAPNIK_DECL font_face_set : private mapnik::noncopyable
{
public:
typedef std::vector<face_ptr>::iterator iterator;
font_face_set(void) : faces_(){}
void add(face_ptr face);
void set_character_sizes(double size);
unsigned size() const { return faces_.size(); }
iterator begin() { return faces_.begin(); }
iterator end() { return faces_.end(); }
private:
std::vector<face_ptr> faces_;
};
typedef std::shared_ptr<font_face_set> face_set_ptr;
// FT_Stroker wrapper
class stroker : mapnik::noncopyable
{
public:
explicit stroker(FT_Stroker s)
: s_(s) {}
~stroker();
void init(double radius);
FT_Stroker const& get() const { return s_; }
private:
FT_Stroker s_;
};
}
} //ns mapnik
#endif // MAPNIK_FONT_UTIL_HPP
#endif // FACE_HPP

View file

@ -24,16 +24,16 @@
// mapnik
#include <mapnik/expression.hpp>
#include <mapnik/text/char_properties_ptr.hpp>
// boost
#include <boost/property_tree/ptree_fwd.hpp>
namespace mapnik {
class text_layout;
class feature_impl;
class processed_text;
class xml_node;
struct char_properties;
namespace formatting {
@ -46,7 +46,7 @@ public:
virtual ~node() {}
virtual void to_xml(boost::property_tree::ptree &xml) const;
static node_ptr from_xml(xml_node const& xml);
virtual void apply(char_properties const& p, feature_impl const& feature, processed_text &output) const = 0;
virtual void apply(char_properties_ptr p, feature_impl const& feature, text_layout &output) const = 0;
virtual void add_expressions(expression_set &output) const;
};
} //ns formatting

View file

@ -32,7 +32,6 @@
namespace mapnik {
class feature_impl;
class processed_text;
class xml_node;
struct char_properties;
@ -41,7 +40,7 @@ class MAPNIK_DECL expression_format: public node {
public:
void to_xml(boost::property_tree::ptree &xml) const;
static node_ptr from_xml(xml_node const& xml);
virtual void apply(char_properties const& p, feature_impl const& feature, processed_text &output) const;
virtual void apply(char_properties_ptr p, feature_impl const& feature, text_layout &output) const;
virtual void add_expressions(expression_set &output) const;
void set_child(node_ptr child);
@ -52,7 +51,6 @@ public:
expression_ptr character_spacing;
expression_ptr line_spacing;
expression_ptr text_opacity;
expression_ptr wrap_before;
expression_ptr wrap_char;
expression_ptr fill;
expression_ptr halo_fill;

View file

@ -36,7 +36,7 @@ class MAPNIK_DECL format_node: public node {
public:
void to_xml(boost::property_tree::ptree &xml) const;
static node_ptr from_xml(xml_node const& xml);
virtual void apply(char_properties const& p, feature_impl const& feature, processed_text &output) const;
virtual void apply(char_properties_ptr p, feature_impl const& feature, text_layout &output) const;
virtual void add_expressions(expression_set &output) const;
void set_child(node_ptr child);

View file

@ -35,7 +35,7 @@ class MAPNIK_DECL list_node: public node {
public:
list_node() : node(), children_() {}
virtual void to_xml(boost::property_tree::ptree &xml) const;
virtual void apply(char_properties const& p, feature_impl const& feature, processed_text &output) const;
virtual void apply(char_properties_ptr p, feature_impl const& feature, text_layout &output) const;
virtual void add_expressions(expression_set &output) const;
void push_back(node_ptr n);
@ -49,4 +49,3 @@ protected:
} //ns mapnik
#endif // FORMATTING_LIST_HPP

View file

@ -36,7 +36,7 @@ public:
text_node(std::string text): node(), text_(parse_expression(text)) {}
void to_xml(boost::property_tree::ptree &xml) const;
static node_ptr from_xml(xml_node const& xml);
virtual void apply(char_properties const& p, feature_impl const& feature, processed_text &output) const;
virtual void apply(char_properties_ptr p, feature_impl const& feature, text_layout &output) const;
virtual void add_expressions(expression_set &output) const;
void set_text(expression_ptr text);

View file

@ -2,7 +2,7 @@
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2011 Artem Pavlenko
* Copyright (C) 2013 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -19,51 +19,50 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
#ifndef MAPNIK_GLYPH_INFO_HPP
#define MAPNIK_GLYPH_INFO_HPP
#ifndef MAPNIK_CHAR_INFO_HPP
#define MAPNIK_CHAR_INFO_HPP
//mapnik
#include <mapnik/text/char_properties_ptr.hpp>
#include <mapnik/pixel_position.hpp>
#include <memory>
namespace mapnik {
struct char_properties;
class char_info
namespace mapnik
{
public:
char_info(unsigned c_, double width_, double ymax_, double ymin_, double line_height_)
: c(c_),
width(width_),
line_height(line_height_),
ymin(ymin_),
ymax(ymax_),
avg_height(ymax - ymin),
format()
{
}
char_info()
: c(0),
width(0),
line_height(0),
ymin(0),
ymax(0),
avg_height(0),
format()
{
}
class font_face;
typedef std::shared_ptr<font_face> face_ptr;
double height() const { return ymax-ymin; }
typedef unsigned glyph_index_t;
unsigned c;
struct glyph_info
{
glyph_info()
: glyph_index(0),
face(nullptr),
char_index(0),
width(0.0),
ymin(0.0),
ymax(0.0),
line_height(0.0),
offset(),
format() {}
glyph_index_t glyph_index;
face_ptr face;
// Position in the string of all characters i.e. before itemizing
unsigned char_index;
double width;
double line_height;
double ymin;
double ymax;
double avg_height;
char_properties *format;
// Line height returned by freetype, includes normal font
// line spacing, but not additional user defined spacing
double line_height;
pixel_position offset;
char_properties_ptr format;
double height() const { return ymax-ymin; }
};
}
#endif //MAPNIK_CHAR_INFO_HPP
} //ns mapnik
#endif // GLYPH_INFO_HPP

View file

@ -0,0 +1,123 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2013 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
*
*****************************************************************************/
#ifndef MAPNIK_HARFBUZZ_SHAPER_HPP
#define MAPNIK_HARFBUZZ_SHAPER_HPP
// mapnik
#include <mapnik/text/text_properties.hpp>
#include <mapnik/text/text_line.hpp>
#include <mapnik/text/face.hpp>
// stl
#include <list>
// harfbuzz
#include <harfbuzz/hb.h>
#include <harfbuzz/hb-ft.h>
#include <harfbuzz/hb-icu.h>
namespace mapnik
{
struct harfbuzz_shaper
{
static void shape_text(text_line & line,
text_itemizer & itemizer,
std::map<unsigned,double> & width_map,
face_manager_freetype & font_manager,
double scale_factor )
{
unsigned start = line.first_char();
unsigned end = line.last_char();
mapnik::value_unicode_string const& text = itemizer.text();
size_t length = end - start;
if (!length) return;
line.reserve(length);
std::list<text_item> const& list = itemizer.itemize(start, end);
auto hb_buffer_deleter = [](hb_buffer_t * buffer) { hb_buffer_destroy(buffer);};
const std::unique_ptr<hb_buffer_t, decltype(hb_buffer_deleter)> buffer(hb_buffer_create(),hb_buffer_deleter);
hb_buffer_set_unicode_funcs(buffer.get(), hb_icu_get_unicode_funcs());
hb_buffer_pre_allocate(buffer.get(), length);
const char* const shapers[] = { "ot", "fallback", NULL };
for (auto const& text_item : list)
{
face_set_ptr face_set = font_manager.get_face_set(text_item.format->face_name, text_item.format->fontset);
double size = text_item.format->text_size * scale_factor;
face_set->set_character_sizes(size);
font_face_set::iterator face_itr = face_set->begin(), face_end = face_set->end();
for (; face_itr != face_end; ++face_itr)
{
hb_buffer_clear_contents(buffer.get());
hb_buffer_add_utf16(buffer.get(), text.getBuffer(), text.length(), text_item.start, text_item.end - text_item.start);
hb_buffer_set_direction(buffer.get(), (text_item.rtl == UBIDI_RTL)?HB_DIRECTION_RTL:HB_DIRECTION_LTR);
hb_buffer_set_script(buffer.get(), hb_icu_script_to_script(text_item.script));
face_ptr const& face = *face_itr;
hb_font_t *font(hb_ft_font_create(face->get_face(), nullptr));
hb_shape_full(font, buffer.get(), NULL, 0, shapers);
hb_font_destroy(font);
unsigned num_glyphs = hb_buffer_get_length(buffer.get());
hb_glyph_info_t *glyphs = hb_buffer_get_glyph_infos(buffer.get(), nullptr);
hb_glyph_position_t *positions = hb_buffer_get_glyph_positions(buffer.get(), nullptr);
bool font_has_all_glyphs = true;
// Check if all glyphs are valid.
for (unsigned i=0; i<num_glyphs; ++i)
{
if (!glyphs[i].codepoint)
{
font_has_all_glyphs = false;
break;
}
}
if (!font_has_all_glyphs && face_itr+1 != face_end)
{
//Try next font in fontset
continue;
}
for (unsigned i=0; i<num_glyphs; ++i)
{
glyph_info tmp;
tmp.char_index = glyphs[i].cluster;
tmp.glyph_index = glyphs[i].codepoint;
tmp.face = face;
tmp.format = text_item.format;
face->glyph_dimensions(tmp);
//tmp.width = positions[i].x_advance / 64.0; //Overwrite default width with better value provided by HarfBuzz
tmp.width = positions[i].x_advance >> 6;
tmp.offset.set(positions[i].x_offset / 64.0, positions[i].y_offset / 64.0);
width_map[glyphs[i].cluster] += tmp.width;
line.add_glyph(tmp, scale_factor);
}
line.update_max_char_height(face->get_char_height());
break; //When we reach this point the current font had all glyphs.
}
}
}
};
} // namespace mapnik
#endif // MAPNIK_HARFBUZZ_SHAPER_HPP

View file

@ -0,0 +1,123 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2013 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
*
*****************************************************************************/
#ifndef MAPNIK_ICU_SHAPER_HPP
#define MAPNIK_ICU_SHAPER_HPP
// mapnik
#include <mapnik/text/text_properties.hpp>
#include <mapnik/text/text_line.hpp>
#include <mapnik/text/face.hpp>
#include <mapnik/debug.hpp>
// stl
#include <list>
// icu
#include <unicode/unistr.h>
#include <unicode/ushape.h>
#include <unicode/schriter.h>
namespace mapnik
{
struct icu_shaper
{
static void shape_text(text_line & line,
text_itemizer & itemizer,
std::map<unsigned,double> & width_map,
face_manager_freetype & font_manager,
double scale_factor )
{
unsigned start = line.first_char();
unsigned end = line.last_char();
mapnik::value_unicode_string const& text = itemizer.text();
size_t length = end - start;
if (!length) return;
line.reserve(length);
std::list<text_item> const& list = itemizer.itemize(start, end);
UErrorCode err = U_ZERO_ERROR;
mapnik::value_unicode_string shaped;
mapnik::value_unicode_string reordered;
for (auto const& text_item : list)
{
face_set_ptr face_set = font_manager.get_face_set(text_item.format->face_name, text_item.format->fontset);
double size = text_item.format->text_size * scale_factor;
face_set->set_character_sizes(size);
for (auto const& face : *face_set)
{
UBiDi *bidi = ubidi_openSized(length, 0, &err);
ubidi_setPara(bidi, text.getBuffer(), length, UBIDI_DEFAULT_LTR, 0, &err);
ubidi_writeReordered(bidi, reordered.getBuffer(length),
length, UBIDI_DO_MIRRORING, &err);
ubidi_close(bidi);
reordered.releaseBuffer(length);
int32_t num_char = u_shapeArabic(reordered.getBuffer(), length,
shaped.getBuffer(length), length,
U_SHAPE_LETTERS_SHAPE | U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
U_SHAPE_TEXT_DIRECTION_VISUAL_LTR, &err);
if (num_char < 0)
{
MAPNIK_LOG_ERROR(icu_shaper) << " u_shapeArabic returned negative num_char " << num_char;
}
std::size_t num_chars = static_cast<std::size_t>(num_char);
shaped.releaseBuffer(length);
bool shaped_status = true;
if (U_SUCCESS(err) && (num_chars == length))
{
U_NAMESPACE_QUALIFIER StringCharacterIterator iter(shaped);
unsigned i = 0;
for (iter.setToStart(); iter.hasNext();)
{
UChar ch = iter.nextPostInc();
glyph_info tmp;
tmp.offset.clear();
tmp.char_index = i;
tmp.glyph_index = FT_Get_Char_Index(face->get_face(), ch);
if (tmp.glyph_index == 0)
{
shaped_status = false;
break;
}
tmp.face = face;
tmp.format = text_item.format;
face->glyph_dimensions(tmp);
width_map[i] += tmp.width;
line.add_glyph(tmp, scale_factor);
++i;
}
}
if (!shaped_status) continue;
line.update_max_char_height(face->get_char_height());
return;
}
}
}
};
} //namespace mapnik
#endif // MAPNIK_ICU_SHAPER_HPP

View file

@ -0,0 +1,109 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2013 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
*
*****************************************************************************/
#ifndef MAPNIK_TEXT_ITEMIZER_HPP
#define MAPNIK_TEXT_ITEMIZER_HPP
//mapnik
#include <mapnik/text/char_properties_ptr.hpp>
#include <mapnik/value_types.hpp>
// stl
#include <string>
#include <list>
#include <vector>
// ICU
#include <unicode/unistr.h>
#include <unicode/uscript.h>
#include <unicode/ubidi.h>
namespace mapnik
{
struct text_item
{
/** First char (UTF16 offset) */
unsigned start;
/** Char _after_ the last char (UTF16 offset) */
unsigned end;
UScriptCode script;
char_properties_ptr format;
UBiDiDirection rtl;
text_item() :
start(0), end(0), script(), format(), rtl(UBIDI_LTR)
{
}
};
/** This class splits text into parts which all have the same
* - direction (LTR, RTL)
* - format
* - script (http://en.wikipedia.org/wiki/Scripts_in_Unicode)
**/
class text_itemizer
{
public:
text_itemizer();
void add_text(mapnik::value_unicode_string str, char_properties_ptr format);
std::list<text_item> const& itemize(unsigned start=0, unsigned end=0);
void clear();
mapnik::value_unicode_string const& text() const { return text_; }
/** Returns the start and end position of a certain line.
*
* Only forced line breaks with \n characters are handled here.
*/
std::pair<unsigned, unsigned> line(unsigned i) const;
unsigned num_lines() const;
private:
template<typename T> struct run
{
run(T data, unsigned start, unsigned end) : start(start), end(end), data(data){}
unsigned start;
unsigned end;
T data;
};
typedef run<char_properties_ptr> format_run_t;
typedef run<UBiDiDirection> direction_run_t;
typedef run<UScriptCode> script_run_t;
typedef std::list<format_run_t> format_run_list;
typedef std::list<script_run_t> script_run_list;
typedef std::list<direction_run_t> direction_run_list;
mapnik::value_unicode_string text_;
/// Format runs are always sorted by char index
format_run_list format_runs_;
/// Directions runs are always in visual order! This is different from
/// format and script runs!
direction_run_list direction_runs_;
/// Script runs are always sorted by char index
script_run_list script_runs_;
void itemize_direction(unsigned start, unsigned end);
void itemize_script();
void create_item_list();
std::list<text_item> output_;
template <typename T> typename T::const_iterator find_run(T const& list, unsigned position);
std::vector<unsigned> forced_line_breaks_; //Positions of \n characters
};
} //ns mapnik
#endif // TEXT_ITEMIZER_HPP

View file

@ -0,0 +1,109 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2013 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
*
*****************************************************************************/
#ifndef MAPNIK_TEXT_LAYOUT_HPP
#define MAPNIK_TEXT_LAYOUT_HPP
//mapnik
#include <mapnik/text/itemizer.hpp>
#include <mapnik/font_engine_freetype.hpp>
#include <mapnik/text/glyph_info.hpp>
#include <mapnik/text/char_properties_ptr.hpp>
#include <mapnik/text/harfbuzz_shaper.hpp>
#include <mapnik/text/icu_shaper.hpp>
#include <mapnik/text/dummy_shaper.hpp>
//stl
#include <vector>
#include <map>
namespace mapnik
{
class text_layout
{
public:
typedef std::vector<text_line> line_vector;
typedef line_vector::const_iterator const_iterator;
typedef harfbuzz_shaper shaper_type;
text_layout(face_manager_freetype & font_manager, double scale_factor);
/** Adds a new text part. Call this function repeatedly to build the complete text. */
void add_text(mapnik::value_unicode_string const& str, char_properties_ptr format);
/** Returns the complete text stored in this layout.*/
mapnik::value_unicode_string const& text() const;
/** Processes the text into a list of glyphs, performing RTL/LTR handling, shaping and line breaking. */
void layout(double wrap_width, unsigned text_ratio, bool wrap_before);
/** Clear all data stored in this object. The object's state is the same as directly after construction. */
void clear();
// Height of all lines together (in pixels).
inline double height() const { return height_; }
// Width of the longest line (in pixels).
inline double width() const { return width_ ; }
// Line iterator.
inline const_iterator begin() const { return lines_.begin(); }
inline const_iterator end() const { return lines_.end(); }
// Number of lines.
inline std::size_t num_lines() const { return lines_.size(); }
// Width of a certain glyph cluster (in pixels).
inline double cluster_width(unsigned cluster) const
{
std::map<unsigned, double>::const_iterator width_itr = width_map_.find(cluster);
if (width_itr != width_map_.end()) return width_itr->second;
return 0;
}
// Returns the number of glyphs so memory can be preallocated.
inline unsigned glyphs_count() const { return glyphs_count_;}
private:
void break_line(text_line & line, double wrap_width, unsigned text_ratio, bool wrap_before);
void shape_text(text_line & line);
void add_line(text_line & line);
void clear_cluster_widths(unsigned first, unsigned last);
//input
face_manager_freetype &font_manager_;
double scale_factor_;
//processing
text_itemizer itemizer_;
// Maps char index (UTF-16) to width. If multiple glyphs map to the same char the sum of all widths is used
// note: this probably isn't the best solution. it would be better to have an object for each cluster, but
// it needs to be implemented with no overhead.
std::map<unsigned, double> width_map_;
double width_;
double height_;
unsigned glyphs_count_;
//output
line_vector lines_;
};
}
#endif // TEXT_LAYOUT_HPP

View file

@ -2,7 +2,7 @@
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2011 Artem Pavlenko
* Copyright (C) 2013 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -19,146 +19,95 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
#ifndef MAPNIK_PLACEMENT_FINDER_HPP
#define MAPNIK_PLACEMENT_FINDER_HPP
// mapnik
#include <mapnik/geometry.hpp>
#include <mapnik/feature.hpp>
#include <mapnik/text/text_properties.hpp>
#include <mapnik/text/text_path.hpp>
#include <mapnik/label_collision_detector.hpp>
#include <mapnik/ctrans.hpp>
//mapnik
#include <mapnik/box2d.hpp>
#include <mapnik/pixel_position.hpp>
#include <mapnik/text/layout.hpp>
#include <mapnik/text/placements/base.hpp>
#include <mapnik/text/placements_list.hpp>
#include <mapnik/text/rotation.hpp>
#include <mapnik/noncopyable.hpp>
// agg
#include "agg_conv_clip_polyline.h"
// stl
#include <queue>
namespace mapnik
{
class text_placement_info;
class string_info;
class text_path;
typedef agg::conv_clip_polyline<geometry_type> clipped_geometry_type;
typedef coord_transform<CoordTransform,clipped_geometry_type> ClippedPathType;
typedef coord_transform<CoordTransform,geometry_type> PathType;
class label_collision_detector4;
typedef label_collision_detector4 DetectorType;
class feature_impl;
class vertex_cache;
template <typename DetectorT>
class placement_finder : mapnik::noncopyable
{
public:
placement_finder(text_placement_info const& placement_info,
string_info const& info,
DetectorT & detector,
box2d<double> const& extent);
placement_finder(feature_impl const& feature,
DetectorType & detector,
box2d<double> const& extent,
text_placement_info_ptr placement_info,
face_manager_freetype & font_manager,
double scale_factor);
// Try place a single label at the given point. */
void find_point_placement(double pos_x, double pos_y, double angle=0.0);
// Iterate over the given path, placing point labels with respect to label_spacing. */
/** Try to place a single label at the given point. */
bool find_point_placement(pixel_position const& pos);
/** Iterate over the given path, placing line-following labels or point labels with respect to label_spacing. */
template <typename T>
void find_point_placements(T & path);
bool find_line_placements(T & path, bool points);
/** Try next position alternative from placement_info. */
bool next_position();
// Iterate over the given path, placing line-following labels with respect to label_spacing. */
template <typename T>
void find_line_placements(T & path);
// Add placements to detector. */
void update_detector();
// Remove old placements. */
void clear_placements();
inline placements_type const& get_results() { return placements_; }
std::vector<box2d<double> > & additional_boxes() { return additional_boxes_;}
std::vector<box2d<double> > const& additional_boxes() const { return additional_boxes_;}
void set_collect_extents(bool collect) { collect_extents_ = collect; }
bool get_collect_extents() const { return collect_extents_; }
box2d<double> const& get_extents() const { return extents_; }
placements_list const& placements() const { return placements_; }
void set_marker(marker_info_ptr m, box2d<double> box, bool marker_unlocked, pixel_position const& marker_displacement);
private:
///Helpers for find_line_placement
///Returns a possible placement on the given line, does not test for collisions
//index: index of the node the current line ends on
//distance: distance along the given index that the placement should start at, this includes the offset,
// as such it may be > or < the length of the current line, so this must be checked for
//orientation: if set to != 0 the placement will be attempted with the given orientation
// otherwise it will autodetect the orientation.
// If >= 50% of the characters end up upside down, it will be retried the other way.
// RETURN: 1/-1 depending which way up the string ends up being.
std::unique_ptr<text_path> get_placement_offset(std::vector<vertex2d> const& path_positions,
std::vector<double> const& path_distances,
int & orientation, std::size_t index, double distance);
///Tests whether the given text_path be placed without a collision
// Returns true if it can
// NOTE: This edits p.envelopes so it can be used afterwards (you must clear it otherwise)
bool test_placement(std::unique_ptr<text_path> const& current_placement, int orientation);
///Does a line-circle intersect calculation
// NOTE: Follow the strict pre conditions
// Pre Conditions: x1,y1 is within radius distance of cx,cy. x2,y2 is outside radius distance of cx,cy
// This means there is exactly one intersect point
// Result is returned in ix, iy
void find_line_circle_intersection(
double cx, double cy, double radius,
double x1, double y1, double x2, double y2,
double & ix, double & iy);
void find_line_breaks();
void init_string_size();
void init_alignment();
void adjust_position(text_path *current_placement);
void add_line(double width, double height, bool first_line);
pixel_position alignment_offset() const;
double jalign_offset(double line_width) const;
bool single_line_placement(vertex_cache &pp, text_upright_e orientation);
/** Moves dx pixels but makes sure not to fall of the end. */
void path_move_dx(vertex_cache &pp);
/** Normalize angle in range [-pi, +pi]. */
static double normalize_angle(double angle);
/** Adjusts user defined spacing to place an integer number of labels. */
double get_spacing(double path_length, double layout_width) const;
/** Checks for collision. */
bool collision(box2d<double> const& box) const;
/** Adds marker to glyph_positions and to collision detector. Returns false if there is a collision. */
bool add_marker(glyph_positions_ptr glyphs, pixel_position const& pos) const;
/** Maps upright==auto, left_only and right_only to left,right to simplify processing.
angle = angle of at start of line (to estimate best option for upright==auto) */
text_upright_e simplify_upright(text_upright_e upright, double angle) const;
box2d<double> get_bbox(glyph_info const& glyph, pixel_position const& pos, rotation const& rot);
feature_impl const& feature_;
DetectorType &detector_;
box2d<double> const& extent_;
// Precalculated values for maximum performance
rotation orientation_;
text_layout layout_;
text_placement_info_ptr info_;
bool valid_;
///General Internals
DetectorT & detector_;
box2d<double> const& dimensions_;
string_info const& info_;
text_symbolizer_properties const& p;
text_placement_info const& pi;
// Length of the longest line after linebreaks.
// Before find_line_breaks() this is the total length of the string.
double string_width_;
// Height of the string after linebreaks.
// Before find_line_breaks() this is the total length of the string.
double string_height_;
// Height of the tallest font in the first line not including line spacing.
// Used to determine the correct offset for the first line.
double first_line_space_;
vertical_alignment_e valign_;
horizontal_alignment_e halign_;
/** Horizontal alignment for point placements. */
horizontal_alignment_e halign_point_;
/** Horizontal alignment for line placements. */
horizontal_alignment_e halign_line_;
justify_alignment_e jalign_;
std::vector<std::size_t> line_breaks_;
std::vector<std::pair<double, double> > line_sizes_;
std::queue< box2d<double> > envelopes_;
// Used to return all placements found. */
placements_type placements_;
// Bounding box of all texts placed. */
box2d<double> extents_;
// Collect a bounding box of all texts placed. */
bool collect_extents_;
double scale_factor_;
// Additional boxes to take into account when finding placement.
// Used for finding line placements where multiple placements are returned.
// Boxes are relative to starting point of current placement.
// Only used for point placements!
std::vector<box2d<double> > additional_boxes_;
placements_list placements_;
//ShieldSymbolizer
bool has_marker_;
marker_info_ptr marker_;
box2d<double> marker_box_;
bool marker_unlocked_;
pixel_position marker_displacement_;
};
}
}//ns mapnik
#endif // MAPNIK_PLACEMENT_FINDER_HPP
#endif // PLACEMENT_FINDER_HPP

View file

@ -33,7 +33,7 @@ namespace mapnik
typedef std::pair<double,double> dimension_type;
class MAPNIK_DECL text_placements;
/** Generate a possible placement and store results of placement_finder.
/** Generate a possible placement.
* This placement has first to be tested by placement_finder to verify it
* can actually be used.
*/
@ -58,16 +58,7 @@ public:
/** Scale factor used by the renderer. */
double scale_factor;
/** Set scale factor. */
void set_scale_factor(double factor) { scale_factor = factor; }
/** Get scale factor. */
double get_scale_factor() const { return scale_factor; }
/** Get label spacing taking the scale factor into account. */
double get_actual_label_spacing() const { return scale_factor * properties.label_spacing; }
/** Get minimum distance taking the scale factor into account. */
double get_actual_minimum_distance() const { return scale_factor * properties.minimum_distance; }
/** Get minimum padding taking the scale factor into account. */
double get_actual_minimum_padding() const { return scale_factor * properties.minimum_padding; }
};
typedef std::shared_ptr<text_placement_info> text_placement_info_ptr;

View file

@ -0,0 +1,95 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2013 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
*
*****************************************************************************/
#ifndef MAPNIK_PLACEMENTS_LIST_HPP
#define MAPNIK_PLACEMENTS_LIST_HPP
//mapnik
#include <mapnik/box2d.hpp>
#include <mapnik/pixel_position.hpp>
#include <mapnik/text/glyph_info.hpp>
#include <mapnik/text/rotation.hpp>
#include <mapnik/marker_cache.hpp>
// agg
#include "agg_trans_affine.h"
//stl
#include <vector>
#include <list>
namespace mapnik
{
struct glyph_info;
struct glyph_position
{
glyph_position(glyph_info const& glyph, pixel_position const& pos, rotation const& rot)
: glyph(&glyph), pos(pos), rot(rot) { }
glyph_info const* glyph;
pixel_position pos;
rotation rot;
};
struct marker_info
{
marker_info() : marker(), transform() {}
marker_info(marker_ptr marker, agg::trans_affine const& transform) :
marker(marker), transform(transform) {}
marker_ptr marker;
agg::trans_affine transform;
};
typedef std::shared_ptr<marker_info> marker_info_ptr;
/** Stores positions of glphys.
*
* The actual glyphs and their format are stored in text_layout.
*/
class glyph_positions
{
public:
typedef std::vector<glyph_position>::const_iterator const_iterator;
glyph_positions();
const_iterator begin() const;
const_iterator end() const;
void push_back(glyph_info const& glyph, pixel_position offset, rotation const& rot);
void reserve(unsigned count);
pixel_position const& get_base_point() const;
void set_base_point(pixel_position base_point);
void set_marker(marker_info_ptr marker, pixel_position const& marker_pos);
marker_info_ptr marker() const;
pixel_position const& marker_pos() const;
box2d<double> const & bbox() const;
private:
std::vector<glyph_position> data_;
pixel_position base_point_;
marker_info_ptr marker_;
pixel_position marker_pos_;
box2d<double> bbox_;
};
typedef std::shared_ptr<glyph_positions> glyph_positions_ptr;
typedef std::list<glyph_positions_ptr> placements_list;
}
#endif // PLACEMENTS_LIST_HPP

View file

@ -1,70 +0,0 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2012 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
*
*****************************************************************************/
#ifndef PROCESSED_TEXT_HPP
#define PROCESSED_TEXT_HPP
// mapnik
#include <mapnik/text/text_properties.hpp>
#include <mapnik/text/text_path.hpp>
#include <mapnik/noncopyable.hpp>
#include <mapnik/value_types.hpp>
// stl
#include <list>
namespace mapnik
{
// fwd declares
class freetype_engine;
template <typename T> class face_manager;
class MAPNIK_DECL processed_text : mapnik::noncopyable
{
public:
struct processed_expression
{
processed_expression(char_properties const& properties, mapnik::value_unicode_string const& text)
: p(properties),
str(text) {}
char_properties p;
mapnik::value_unicode_string str;
};
public:
processed_text(face_manager<freetype_engine> & font_manager, double scale_factor);
void push_back(char_properties const& properties, mapnik::value_unicode_string const& text);
std::size_t size() const { return expr_list_.size(); }
unsigned empty() const { return expr_list_.empty(); }
void clear();
typedef std::list<processed_expression> expression_list;
expression_list::const_iterator begin() const;
expression_list::const_iterator end() const;
string_info const& get_string_info();
private:
expression_list expr_list_;
face_manager<freetype_engine> &font_manager_;
double scale_factor_;
string_info info_;
};
} // ns mapnik
#endif // PROCESSED_TEXT_HPP

View file

@ -0,0 +1,116 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2013 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
*
*****************************************************************************/
#ifndef MAPNIK_TEXT_RENDERER_HPP
#define MAPNIK_TEXT_RENDERER_HPP
//mapnik
#include <mapnik/text/placement_finder.hpp>
#include <mapnik/image_compositing.hpp>
#include <mapnik/text_symbolizer.hpp>
#include <mapnik/noncopyable.hpp>
//TODO: Find a better place for halo_rasterizer_e!
//TODO: Halo rasterizer selection should go to text_properties because it might make sense to use a different rasterizer for different fonts
// freetype2
extern "C"
{
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_STROKER_H
}
namespace mapnik
{
struct glyph_t
{
FT_Glyph image;
char_properties_ptr properties;
glyph_t(FT_Glyph image_, char_properties_ptr properties_)
: image(image_), properties(properties_) {}
glyph_t( glyph_t && other) noexcept
: image(other.image),
properties(std::move(other.properties))
{
other.image = nullptr;
}
glyph_t(glyph_t const& ) = delete;
glyph_t & operator=(glyph_t const&) = delete;
~glyph_t () { FT_Done_Glyph(image);}
};
class text_renderer : private mapnik::noncopyable
{
public:
text_renderer (halo_rasterizer_e rasterizer,
composite_mode_e comp_op = src_over,
double scale_factor=1.0,
stroker_ptr stroker=stroker_ptr());
protected:
typedef std::vector<glyph_t> glyph_vector;
void prepare_glyphs(glyph_positions const& positions);
halo_rasterizer_e rasterizer_;
composite_mode_e comp_op_;
double scale_factor_;
glyph_vector glyphs_;
stroker_ptr stroker_;
};
template <typename T>
class agg_text_renderer : public text_renderer
{
public:
typedef T pixmap_type;
agg_text_renderer (pixmap_type & pixmap, halo_rasterizer_e rasterizer,
composite_mode_e comp_op = src_over,
double scale_factor=1.0,
stroker_ptr stroker=stroker_ptr());
void render(glyph_positions const& positions);
private:
pixmap_type & pixmap_;
void render_halo(FT_Bitmap_ *bitmap, unsigned rgba, int x, int y,
double halo_radius, double opacity,
composite_mode_e comp_op);
};
template <typename T>
class grid_text_renderer : public text_renderer
{
public:
typedef T pixmap_type;
grid_text_renderer (pixmap_type & pixmap, composite_mode_e comp_op = src_over,
double scale_factor=1.0);
void render(glyph_positions const& positions, value_integer feature_id);
private:
pixmap_type & pixmap_;
void render_halo_id(FT_Bitmap_ *bitmap, mapnik::value_integer feature_id, int x, int y, int halo_radius);
};
}
#endif // RENDERER_HPP

View file

@ -0,0 +1,23 @@
#ifndef MAPNIK_ROTATION_HPP
#define MAPNIK_ROTATION_HPP
#include <cmath>
namespace mapnik
{
struct rotation
{
rotation() : sin(0), cos(1.) { }
rotation(double sin_, double cos_) : sin(sin_), cos(cos_) { }
rotation(double angle) : sin(std::sin(angle)), cos(std::cos(angle)) {}
void reset() { sin = 0.; cos = 1.;}
void init(double angle) { sin = std::sin(angle); cos = std::cos(angle); }
double sin;
double cos;
rotation operator~() { return rotation(sin, -cos); }
rotation operator!() { return rotation(-sin, cos); }
};
}
#endif // ROTATION_HPP

View file

@ -0,0 +1,157 @@
/*
*******************************************************************************
*
* Copyright (C) 1999-2003, International Business Machines
* Corporation and others. All Rights Reserved.
*
*******************************************************************************
* file name: scrptrun.h
*
* created on: 10/17/2001
* created by: Eric R. Mader
*
* NOTE: This file is copied from ICU.
* http://source.icu-project.org/repos/icu/icu/trunk/license.html
*/
#ifndef __SCRPTRUN_H
#define __SCRPTRUN_H
#include "unicode/utypes.h"
#include "unicode/uobject.h"
#include "unicode/uscript.h"
struct ScriptRecord
{
UChar32 startChar;
UChar32 endChar;
UScriptCode scriptCode;
};
struct ParenStackEntry
{
int32_t pairIndex;
UScriptCode scriptCode;
};
class ScriptRun : public UObject {
public:
ScriptRun();
ScriptRun(const UChar chars[], int32_t length);
ScriptRun(const UChar chars[], int32_t start, int32_t length);
void reset();
void reset(int32_t start, int32_t count);
void reset(const UChar chars[], int32_t start, int32_t length);
int32_t getScriptStart();
int32_t getScriptEnd();
UScriptCode getScriptCode();
UBool next();
/**
* ICU "poor man's RTTI", returns a UClassID for the actual class.
*
* @stable ICU 2.2
*/
virtual inline UClassID getDynamicClassID() const { return getStaticClassID(); }
/**
* ICU "poor man's RTTI", returns a UClassID for this class.
*
* @stable ICU 2.2
*/
static inline UClassID getStaticClassID() { return (UClassID)&fgClassID; }
private:
static UBool sameScript(int32_t scriptOne, int32_t scriptTwo);
int32_t charStart;
int32_t charLimit;
const UChar *charArray;
int32_t scriptStart;
int32_t scriptEnd;
UScriptCode scriptCode;
ParenStackEntry parenStack[128];
int32_t parenSP;
static int8_t highBit(int32_t value);
static int32_t getPairIndex(UChar32 ch);
static UChar32 pairedChars[];
static const int32_t pairedCharCount;
static const int32_t pairedCharPower;
static const int32_t pairedCharExtra;
/**
* The address of this static class variable serves as this class's ID
* for ICU "poor man's RTTI".
*/
static const char fgClassID;
};
inline ScriptRun::ScriptRun()
{
reset(NULL, 0, 0);
}
inline ScriptRun::ScriptRun(const UChar chars[], int32_t length)
{
reset(chars, 0, length);
}
inline ScriptRun::ScriptRun(const UChar chars[], int32_t start, int32_t length)
{
reset(chars, start, length);
}
inline int32_t ScriptRun::getScriptStart()
{
return scriptStart;
}
inline int32_t ScriptRun::getScriptEnd()
{
return scriptEnd;
}
inline UScriptCode ScriptRun::getScriptCode()
{
return scriptCode;
}
inline void ScriptRun::reset()
{
scriptStart = charStart;
scriptEnd = charStart;
scriptCode = USCRIPT_INVALID_CODE;
parenSP = -1;
}
inline void ScriptRun::reset(int32_t start, int32_t length)
{
charStart = start;
charLimit = start + length;
reset();
}
inline void ScriptRun::reset(const UChar chars[], int32_t start, int32_t length)
{
charArray = chars;
reset(start, length);
}
#endif

View file

@ -2,7 +2,7 @@
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2012 Artem Pavlenko
* Copyright (C) 2013 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -26,34 +26,21 @@
#include <mapnik/text_symbolizer.hpp>
#include <mapnik/shield_symbolizer.hpp>
#include <mapnik/feature.hpp>
#include <mapnik/marker.hpp>
#include <mapnik/marker_cache.hpp>
#include <mapnik/text/processed_text.hpp>
#include <mapnik/text/text_path.hpp>
//boost
// agg
#include "agg_trans_affine.h"
// fwd declares
namespace mapnik {
class CoordTransform;
class marker;
class proj_transform;
class string_info;
class text_path;
template <typename DetectorT> class placement_finder;
}
#include <mapnik/text/placement_finder.hpp>
#include <mapnik/proj_transform.hpp>
#include <mapnik/ctrans.hpp>
namespace mapnik {
/** Helper object that does all the TextSymbolizer placement finding
* work except actually rendering the object. */
template <typename FaceManagerT, typename DetectorT>
class text_symbolizer_helper
{
public:
template <typename FaceManagerT, typename DetectorT>
text_symbolizer_helper(text_symbolizer const& sym,
feature_impl const& feature,
proj_transform const& prj_trans,
@ -65,18 +52,23 @@ public:
DetectorT &detector,
box2d<double> const& query_extent);
~text_symbolizer_helper();
// Return next placement.
// If no more placements are found returns null pointer.
bool next();
template <typename FaceManagerT, typename DetectorT>
text_symbolizer_helper(shield_symbolizer const& sym,
feature_impl const& feature,
proj_transform const& prj_trans,
unsigned width,
unsigned height,
double scale_factor,
CoordTransform const &t,
FaceManagerT &font_manager,
DetectorT &detector,
box2d<double> const& query_extent);
// Get current placement. next() has to be called before!
placements_type const& placements() const;
/** Return all placements.*/
placements_list const& get();
protected:
bool next_point_placement();
bool next_line_placement();
bool next_line_placement_clipped();
bool next_placement();
void initialize_geometries();
void initialize_points();
@ -85,103 +77,29 @@ protected:
feature_impl const& feature_;
proj_transform const& prj_trans_;
CoordTransform const& t_;
FaceManagerT & font_manager_;
DetectorT & detector_;
box2d<double> dims_;
box2d<double> const& query_extent_;
//Processing
processed_text text_;
// Using list instead of vector, because we delete random elements and need iterators to stay valid.
// Remaining geometries to be processed.
/* Using list instead of vector, because we delete random elements and need iterators to stay valid. */
/** Remaining geometries to be processed. */
std::list<geometry_type*> geometries_to_process_;
// Geometry currently being processed.
/** Geometry currently being processed. */
std::list<geometry_type*>::iterator geo_itr_;
// Remaining points to be processed.
std::list<position> points_;
// Point currently being processed.
std::list<position>::iterator point_itr_;
// Text rotation.
double angle_;
// Did last call to next_placement return true?
bool placement_valid_;
// Use point placement. Otherwise line placement is used.
/** Remaining points to be processed. */
std::list<pixel_position> points_;
/** Point currently being processed. */
std::list<pixel_position>::iterator point_itr_;
/** Use point placement. Otherwise line placement is used. */
bool point_placement_;
// Place text at points on a line instead of following the line (used for ShieldSymbolizer).
/** Place text at points on a line instead of following the line (used for ShieldSymbolizer) .*/
bool points_on_line_;
text_placement_info_ptr placement_;
std::unique_ptr<placement_finder<DetectorT> > finder_;
};
placement_finder finder_;
template <typename FaceManagerT, typename DetectorT>
class shield_symbolizer_helper: public text_symbolizer_helper<FaceManagerT, DetectorT>
{
public:
shield_symbolizer_helper(shield_symbolizer const& sym,
feature_impl const& feature,
proj_transform const& prj_trans,
unsigned width,
unsigned height,
double scale_factor,
CoordTransform const &t,
FaceManagerT &font_manager,
DetectorT &detector,
box2d<double> const& query_extent) :
text_symbolizer_helper<FaceManagerT, DetectorT>(sym, feature, prj_trans, width, height, scale_factor, t, font_manager, detector, query_extent),
sym_(sym)
{
this->points_on_line_ = true;
init_marker();
}
box2d<double> const& get_marker_extent() const
{
return marker_ext_;
}
double get_marker_height() const
{
return marker_h_;
}
double get_marker_width() const
{
return marker_w_;
}
bool next();
pixel_position get_marker_position(text_path const& p);
marker & get_marker() const;
agg::trans_affine const& get_image_transform() const;
protected:
bool next_point_placement();
bool next_line_placement();
//ShieldSymbolizer only
void init_marker();
shield_symbolizer const& sym_;
box2d<double> marker_ext_;
boost::optional<marker_ptr> marker_;
agg::trans_affine image_transform_;
double marker_w_;
double marker_h_;
double marker_x_;
double marker_y_;
using text_symbolizer_helper<FaceManagerT, DetectorT>::geometries_to_process_;
using text_symbolizer_helper<FaceManagerT, DetectorT>::placement_;
using text_symbolizer_helper<FaceManagerT, DetectorT>::next_placement;
using text_symbolizer_helper<FaceManagerT, DetectorT>::geo_itr_;
using text_symbolizer_helper<FaceManagerT, DetectorT>::point_itr_;
using text_symbolizer_helper<FaceManagerT, DetectorT>::points_;
using text_symbolizer_helper<FaceManagerT, DetectorT>::font_manager_;
using text_symbolizer_helper<FaceManagerT, DetectorT>::feature_;
using text_symbolizer_helper<FaceManagerT, DetectorT>::t_;
using text_symbolizer_helper<FaceManagerT, DetectorT>::detector_;
using text_symbolizer_helper<FaceManagerT, DetectorT>::dims_;
using text_symbolizer_helper<FaceManagerT, DetectorT>::prj_trans_;
using text_symbolizer_helper<FaceManagerT, DetectorT>::placement_valid_;
using text_symbolizer_helper<FaceManagerT, DetectorT>::point_placement_;
using text_symbolizer_helper<FaceManagerT, DetectorT>::angle_;
using text_symbolizer_helper<FaceManagerT, DetectorT>::finder_;
};
} //namespace
#endif // SYMBOLIZER_HELPERS_HPP

View file

@ -0,0 +1,93 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2013 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
*
*****************************************************************************/
#ifndef MAPNIK_TEXT_LINE_HPP
#define MAPNIK_TEXT_LINE_HPP
//mapnik
#include <mapnik/text/glyph_info.hpp>
#include <mapnik/text/text_properties.hpp>
//stl
#include <vector>
namespace mapnik
{
/** This class stores all glyphs of a line in left to right order.
*
* It can be used for rendering but no text processing (like line breaking)
* should be done!
*/
class text_line
{
public:
text_line(unsigned first_char, unsigned last_char);
typedef std::vector<glyph_info> glyph_vector;
typedef glyph_vector::const_iterator const_iterator;
/** Get glyph vector. */
glyph_vector const& glyphs() const { return glyphs_; }
/** Append glyph. */
void add_glyph(glyph_info const& glyph, double scale_factor_);
/** Preallocate memory. */
void reserve(glyph_vector::size_type length);
/** Iterator to first glyph. */
const_iterator begin() const;
/** Iterator beyond last glyph. */
const_iterator end() const;
/** Width of all glyphs including character spacing. */
double width() const { return width_; }
/** Real line height. For first line: max_char_height(), for all others: line_height(). */
double height() const;
/** Height of the tallest glyph in this line. */
double max_char_height() const { return max_char_height_; }
/** Called for each font/style to update the maximum height of this line. */
void update_max_char_height(double max_char_height);
/** Line height including line spacing. */
double line_height() const { return line_height_; }
/** Is this object is the first line of a multi-line text?
* Used to exclude linespacing from first line's height. */
void set_first_line(bool first_line);
/** Index of first UTF-16 char. */
unsigned first_char() const;
/** Index of last UTF-16 char. */
unsigned last_char() const;
/** Number of glyphs. */
unsigned size() const;
private:
glyph_vector glyphs_;
double line_height_; //Includes line spacing (returned by freetype)
double max_char_height_; //Height of 'X' character of the largest font in this run. //TODO: Initialize this!
double width_;
unsigned first_char_;
unsigned last_char_;
bool first_line_;
};
} //namespace mapnik
#endif // MAPNIK_TEXT_LINE_HPP

View file

@ -2,7 +2,7 @@
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2011 Artem Pavlenko
* Copyright (C) 2013 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public

View file

@ -2,7 +2,7 @@
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2012 Artem Pavlenko
* Copyright (C) 2013 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -23,6 +23,7 @@
#define TEXT_PROPERTIES_HPP
// mapnik
#include <mapnik/text/char_properties_ptr.hpp>
#include <mapnik/color.hpp>
#include <mapnik/attribute.hpp>
#include <mapnik/value.hpp>
@ -31,6 +32,7 @@
#include <mapnik/enumeration.hpp>
#include <mapnik/expression.hpp>
#include <mapnik/text/formatting/base.hpp>
#include <mapnik/pixel_position.hpp>
// stl
#include <map>
@ -67,7 +69,6 @@ struct MAPNIK_DECL char_properties
double character_spacing;
double line_spacing; //Largest total height (fontsize+line_spacing) per line is chosen
double text_opacity;
bool wrap_before;
unsigned wrap_char;
text_transform_e text_transform; //Per expression
color fill;
@ -75,7 +76,6 @@ struct MAPNIK_DECL char_properties
double halo_radius;
};
enum label_placement_enum
{
POINT_PLACEMENT,
@ -120,8 +120,19 @@ enum justify_alignment
DEFINE_ENUM(justify_alignment_e, justify_alignment);
typedef std::pair<double, double> position;
class processed_text;
enum text_upright
{
UPRIGHT_AUTO,
UPRIGHT_LEFT,
UPRIGHT_RIGHT,
UPRIGHT_LEFT_ONLY,
UPRIGHT_RIGHT_ONLY,
text_upright_MAX
};
DEFINE_ENUM(text_upright_e, text_upright);
class text_layout;
/** Contains all text symbolizer properties which are not directly related to text formatting. */
@ -136,7 +147,7 @@ struct MAPNIK_DECL text_symbolizer_properties
/** Takes a feature and produces formated text as output.
* The output object has to be created by the caller and passed in for thread safety.
*/
void process(processed_text &output, feature_impl const& feature) const;
void process(text_layout &output, feature_impl const& feature) const;
/** Automatically create processing instructions for a single expression. */
void set_old_style_expression(expression_ptr expr);
/** Sets new format tree. */
@ -149,7 +160,7 @@ struct MAPNIK_DECL text_symbolizer_properties
//Per symbolizer options
expression_ptr orientation;
position displacement;
pixel_position displacement;
label_placement_e label_placement;
horizontal_alignment_e halign;
justify_alignment_e jalign;
@ -170,8 +181,11 @@ struct MAPNIK_DECL text_symbolizer_properties
bool largest_bbox_only;
double text_ratio;
double wrap_width;
bool wrap_before;
bool rotate_displacement;
text_upright_e upright;
/** Default values for char_properties. */
char_properties format;
char_properties_ptr format;
private:
/** A tree of formatting::nodes which contain text and formatting information. */
formatting::node_ptr tree_;

View file

@ -0,0 +1,221 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2013 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
*
*****************************************************************************/
#ifndef MAPNIK_VERTEX_CACHE_HPP
#define MAPNIK_VERTEX_CACHE_HPP
// mapnik
#include <mapnik/pixel_position.hpp>
#include <mapnik/debug.hpp>
// agg
#include "agg_basics.h"
// stl
#include <vector>
#include <memory>
#include <map>
namespace mapnik
{
class vertex_cache;
typedef std::shared_ptr<vertex_cache> vertex_cache_ptr;
/** Caches all path points and their lengths. Allows easy moving in both directions. */
class vertex_cache
{
struct segment
{
segment(double x, double y, double length) : pos(x, y), length(length) {}
pixel_position pos; //Last point of this segment, first point is implicitly defined by the previous segement in this vector
double length;
};
/* The first segment always has the length 0 and just defines the starting point. */
struct segment_vector
{
segment_vector() : vector(), length(0.) {}
void add_segment(double x, double y, double len) {
if (len == 0. && !vector.empty()) return; //Don't add zero length segments
vector.push_back(segment(x, y, len));
length += len;
}
typedef std::vector<segment>::iterator iterator;
std::vector<segment> vector;
double length;
};
public:
/** This class has no public members to avoid acciedential modification.
* It should only be used with save_state/restore_state. */
class state
{
segment_vector::iterator current_segment;
double position_in_segment;
pixel_position current_position;
pixel_position segment_starting_point;
double position_;
friend class vertex_cache;
public:
pixel_position const& position() const { return current_position; }
};
class scoped_state
{
public:
scoped_state(vertex_cache &pp) : pp_(pp), state_(pp.save_state()), restored_(false) {}
void restore() { pp_.restore_state(state_); restored_ = true; }
~scoped_state() { if (!restored_) pp_.restore_state(state_); }
state const& get_state() const { return state_; }
private:
vertex_cache &pp_;
class state state_;
bool restored_;
};
/********************************************************************************************/
template <typename T> vertex_cache(T &path);
double length() const { return current_subpath_->length; }
pixel_position const& current_position() const { return current_position_; }
double angle(double width=0.);
double linear_position() const { return position_; }
/** Returns a parallel line in the specified distance. */
vertex_cache &get_offseted(double offset, double region_width);
/** Skip a certain amount of space.
*
* This function automatically calculates new points if the position is not exactly
* on a point on the path.
*/
bool forward(double length);
/** Go backwards. */
bool backward(double length);
/** Move in any direction (based on sign of length). Returns false if it reaches either end of the path. */
bool move(double length);
/** Work on next subpath. Returns false if the is no next subpath. */
bool next_subpath();
// Compatibility with standard path interface
void rewind(unsigned);
unsigned vertex(double *x, double *y);
//State
state save_state() const;
void restore_state(state const& s);
/** Go back to initial state. */
void reset();
private:
void rewind_subpath();
bool next_segment();
bool previous_segment();
/** Position as calculated by last move/forward/next call. */
pixel_position current_position_;
/** First pixel of current segment. */
pixel_position segment_starting_point_;
/** List of all subpaths. */
std::vector<segment_vector> subpaths_;
/** Currently active subpath. */
std::vector<segment_vector>::iterator current_subpath_;
/** Current segment for normal operation (move()). */
segment_vector::iterator current_segment_;
/** Current segment in compatibility mode (vertex(), rewind()). */
segment_vector::iterator vertex_segment_;
/** Currently active subpath in compatibility mode. */
std::vector<segment_vector>::iterator vertex_subpath_;
/** State is initialized (after first call to next_subpath()). */
bool initialized_;
/** Position from start of segment. */
double position_in_segment_;
/** Angle for current segment. */
mutable double angle_;
/** Is the value in angle_ valid?
* Used to avoid unnecessary calculations. */
mutable bool angle_valid_;
typedef std::map<double, vertex_cache_ptr> offseted_lines_map;
/** Cache of all offseted lines already computed. */
offseted_lines_map offseted_lines_;
/** Linear position, i.e distance from start of line. */
double position_;
};
template <typename T>
vertex_cache::vertex_cache(T &path)
: current_position_(),
segment_starting_point_(),
subpaths_(),
current_subpath_(),
current_segment_(),
vertex_segment_(),
vertex_subpath_(),
initialized_(false),
position_in_segment_(0.),
angle_(0.),
angle_valid_(false),
offseted_lines_(),
position_(0.)
{
path.rewind(0);
unsigned cmd;
double new_x = 0., new_y = 0., old_x = 0., old_y = 0.;
bool first = true; //current_subpath_ uninitalized
while (!agg::is_stop(cmd = path.vertex(&new_x, &new_y)))
{
if (agg::is_move_to(cmd))
{
//Create new sub path
subpaths_.push_back(segment_vector());
current_subpath_ = subpaths_.end()-1;
current_subpath_->add_segment(new_x, new_y, 0);
first = false;
}
if (agg::is_line_to(cmd))
{
if (first)
{
MAPNIK_LOG_ERROR(vertex_cache) << "No starting point in path!\n";
continue;
}
double dx = old_x - new_x;
double dy = old_y - new_y;
double segment_length = std::sqrt(dx*dx + dy*dy);
current_subpath_->add_segment(new_x, new_y, segment_length);
}
old_x = new_x;
old_y = new_y;
}
}
}
#endif

View file

@ -33,6 +33,7 @@
// boost
#include <memory>
// stl
#include <string>
@ -60,10 +61,10 @@ struct MAPNIK_DECL text_symbolizer : public symbolizer_base
// not able to compile make_shared used within a constructor
text_symbolizer(text_placements_ptr placements = text_placements_ptr(new text_placements_dummy));
text_symbolizer(expression_ptr name, std::string const& face_name,
float size, color const& fill,
double size, color const& fill,
text_placements_ptr placements = text_placements_ptr(new text_placements_dummy)
);
text_symbolizer(expression_ptr name, float size, color const& fill,
text_symbolizer(expression_ptr name, double size, color const& fill,
text_placements_ptr placements = text_placements_ptr(new text_placements_dummy)
);
text_symbolizer(text_symbolizer const& rhs);
@ -90,8 +91,8 @@ struct MAPNIK_DECL text_symbolizer : public symbolizer_base
void set_character_spacing(double spacing);
double get_label_spacing() const func_deprecated; // spacing between repeated labels on lines
void set_label_spacing(double spacing);
double get_label_position_tolerance() const func_deprecated; //distance the label can be moved on the line to fit, if 0 the default is used
void set_label_position_tolerance(double tolerance);
unsigned get_label_position_tolerance() const func_deprecated; //distance the label can be moved on the line to fit, if 0 the default is used
void set_label_position_tolerance(unsigned tolerance);
bool get_force_odd_labels() const func_deprecated; // try render an odd amount of labels
void set_force_odd_labels(bool force);
double get_max_char_angle_delta() const func_deprecated; // maximum change in angle between adjacent characters
@ -115,8 +116,8 @@ struct MAPNIK_DECL text_symbolizer : public symbolizer_base
void set_vertical_alignment(vertical_alignment_e valign);
vertical_alignment_e get_vertical_alignment() const func_deprecated;
void set_displacement(double x, double y);
void set_displacement(position const& p);
position const& get_displacement() const func_deprecated;
void set_displacement(pixel_position const& p);
pixel_position const& get_displacement() const func_deprecated;
void set_avoid_edges(bool avoid);
bool get_avoid_edges() const func_deprecated;
void set_minimum_distance(double distance);

View file

@ -21,15 +21,10 @@
*****************************************************************************/
// mapnik
#include <mapnik/feature.hpp>
#include <mapnik/agg_renderer.hpp>
#include <mapnik/agg_rasterizer.hpp>
#include <mapnik/image_util.hpp>
#include <mapnik/graphics.hpp>
#include <mapnik/text/symbolizer_helpers.hpp>
#include <mapnik/pixel_position.hpp>
#include <mapnik/text/face.hpp>
// boost
#include <mapnik/text/renderer.hpp>
namespace mapnik {
@ -39,42 +34,24 @@ void agg_renderer<T>::process(shield_symbolizer const& sym,
proj_transform const& prj_trans)
{
box2d<double> clip_box = clipping_extent();
shield_symbolizer_helper<face_manager<freetype_engine>,
label_collision_detector4> helper(
text_symbolizer_helper helper(
sym, feature, prj_trans,
width_, height_,
scale_factor_,
t_, font_manager_, *detector_,
clip_box);
text_renderer<T> ren(*current_buffer_,
font_manager_,
sym.get_halo_rasterizer(),
sym.comp_op(),
scale_factor_);
agg_text_renderer<T> ren(*current_buffer_, sym.get_halo_rasterizer(), sym.comp_op(), scale_factor_, font_manager_.get_stroker());
while (helper.next())
placements_list const& placements = helper.get();
for (glyph_positions_ptr glyphs : placements)
{
placements_type const& placements = helper.placements();
for (unsigned int ii = 0; ii < placements.size(); ++ii)
{
// get_marker_position returns (minx,miny) corner position,
// while (currently only) agg_renderer::render_marker newly
// expects center position;
// until all renderers and shield_symbolizer_helper are
// modified accordingly, we must adjust the position here
pixel_position pos = helper.get_marker_position(placements[ii]);
pos.x += 0.5 * helper.get_marker_width();
pos.y += 0.5 * helper.get_marker_height();
render_marker(pos,
helper.get_marker(),
helper.get_image_transform(),
sym.get_opacity(),
sym.comp_op());
ren.prepare_glyphs(placements[ii]);
ren.render(placements[ii].center);
}
if (glyphs->marker())
render_marker(glyphs->marker_pos(),
*(glyphs->marker()->marker),
glyphs->marker()->transform,
sym.get_opacity(), sym.comp_op());
ren.render(*glyphs);
}
}

View file

@ -21,12 +21,10 @@
*****************************************************************************/
// mapnik
#include <mapnik/feature.hpp>
#include <mapnik/agg_renderer.hpp>
#include <mapnik/agg_rasterizer.hpp>
#include <mapnik/text/symbolizer_helpers.hpp>
#include <mapnik/graphics.hpp>
#include <mapnik/text/face.hpp>
#include <mapnik/text/symbolizer_helpers.hpp>
#include <mapnik/text/renderer.hpp>
namespace mapnik {
@ -36,28 +34,19 @@ void agg_renderer<T>::process(text_symbolizer const& sym,
proj_transform const& prj_trans)
{
box2d<double> clip_box = clipping_extent();
text_symbolizer_helper<face_manager<freetype_engine>,
label_collision_detector4> helper(
text_symbolizer_helper helper(
sym, feature, prj_trans,
width_,height_,
width_, height_,
scale_factor_,
t_, font_manager_, *detector_,
clip_box);
text_renderer<T> ren(*current_buffer_,
font_manager_,
sym.get_halo_rasterizer(),
sym.comp_op(),
scale_factor_);
agg_text_renderer<T> ren(*current_buffer_, sym.get_halo_rasterizer(), sym.comp_op(), scale_factor_, font_manager_.get_stroker());
while (helper.next())
placements_list const& placements = helper.get();
for (glyph_positions_ptr glyphs : placements)
{
placements_type const& placements = helper.placements();
for (unsigned int ii = 0; ii < placements.size(); ++ii)
{
ren.prepare_glyphs(placements[ii]);
ren.render(placements[ii].center);
}
ren.render(*glyphs);
}
}

View file

@ -360,6 +360,15 @@ bool box2d<T>::valid() const
return (minx_ <= maxx_ && miny_ <= maxy_) ;
}
template <typename T>
void box2d<T>::move(T x, T y)
{
minx_ += x;
maxx_ += x;
miny_ += y;
maxy_ += y;
}
template <typename T>
box2d<T>& box2d<T>::operator+=(box2d<T> const& other)
{
@ -367,6 +376,22 @@ box2d<T>& box2d<T>::operator+=(box2d<T> const& other)
return *this;
}
template <typename T>
box2d<T> box2d<T>::operator+ (T other) const
{
return box2d<T>(minx_ - other, miny_ - other, maxx_ + other, maxy_ + other);
}
template <typename T>
box2d<T>& box2d<T>::operator+= (T other)
{
minx_ -= other;
miny_ -= other;
maxx_ += other;
maxy_ += other;
return *this;
}
template <typename T>
box2d<T>& box2d<T>::operator*=(T t)

View file

@ -57,7 +57,7 @@ regex = 'boost_regex%s' % env['BOOST_APPEND']
system = 'boost_system%s' % env['BOOST_APPEND']
# clear out and re-set libs for this env
lib_env['LIBS'] = ['freetype',env['ICU_LIB_NAME'],filesystem,system,regex]
lib_env['LIBS'] = ['freetype',env['ICU_LIB_NAME'],filesystem,system,regex,'harfbuzz', 'harfbuzz-icu']
if '-DMAPNIK_USE_PROJ4' in env['CPPDEFINES']:
lib_env['LIBS'].append('proj')
@ -164,7 +164,6 @@ source = Split(
parse_transform.cpp
palette.cpp
path_expression_grammar.cpp
text/placement_finder.cpp
plugin.cpp
point_symbolizer.cpp
polygon_pattern_symbolizer.cpp
@ -182,7 +181,6 @@ source = Split(
memory_datasource.cpp
stroke.cpp
symbolizer.cpp
text/symbolizer_helpers.cpp
unicode.cpp
markers_symbolizer.cpp
raster_colorizer.cpp
@ -202,7 +200,16 @@ source = Split(
json/feature_parser.cpp
json/feature_collection_parser.cpp
json/geojson_generator.cpp
text/processed_text.cpp
text/vertex_cache.cpp
text/layout.cpp
text/text_line.cpp
text/itemizer.cpp
text/scrptrun.cpp
text/face.cpp
text/placement_finder.cpp
text/renderer.cpp
text/symbolizer_helpers.cpp
text/text_properties.cpp
text/formatting/base.cpp
text/formatting/expression.cpp
text/formatting/list.cpp
@ -214,7 +221,6 @@ source = Split(
text/placements/dummy.cpp
text/placements/list.cpp
text/placements/simple.cpp
text/text_properties.cpp
xml_tree.cpp
config_error.cpp
color_factory.cpp

View file

@ -23,7 +23,6 @@
#include <mapnik/cairo_context.hpp>
#include <mapnik/text/face.hpp>
#include <mapnik/text/text_properties.hpp>
#include <mapnik/text/text_path.hpp>
#include <mapnik/font_set.hpp>
#include <cairo-ft.h>
@ -221,7 +220,7 @@ void cairo_context::set_line_width(double width)
check_object_status_and_throw_exception(*this);
}
void cairo_context::set_dash(dash_array const& dashes, double scale_factor)
void cairo_context::set_dash(dash_array const &dashes, double scale_factor)
{
std::valarray<double> d(dashes.size() * 2);
dash_array::const_iterator itr = dashes.begin();
@ -404,100 +403,80 @@ void cairo_context::restore()
check_object_status_and_throw_exception(*this);
}
void cairo_context::show_glyph(unsigned long index, double x, double y)
void cairo_context::show_glyph(unsigned long index, pixel_position const& pos)
{
cairo_glyph_t glyph;
glyph.index = index;
glyph.x = x;
glyph.y = y;
glyph.x = pos.x;
glyph.y = pos.y;
cairo_show_glyphs(cairo_.get(), &glyph, 1);
check_object_status_and_throw_exception(*this);
}
void cairo_context::glyph_path(unsigned long index, double x, double y)
void cairo_context::glyph_path(unsigned long index, pixel_position const& pos)
{
cairo_glyph_t glyph;
glyph.index = index;
glyph.x = x;
glyph.y = y;
glyph.x = pos.x;
glyph.y = pos.y;
cairo_glyph_path(cairo_.get(), &glyph, 1);
check_object_status_and_throw_exception(*this);
}
void cairo_context::add_text(text_path const& path,
void cairo_context::add_text(glyph_positions_ptr pos,
cairo_face_manager & manager,
face_manager<freetype_engine> & font_manager,
double scale_factor)
{
double sx = path.center.x;
double sy = path.center.y;
pixel_position const& base = pos->get_base_point();
path.rewind();
for (std::size_t iii = 0; iii < path.num_nodes(); ++iii)
//Render halo
glyph_positions::const_iterator itr, end = pos->end();
for (itr = pos->begin(); itr != end; itr++)
{
char_info_ptr c;
double x, y, angle;
glyph_info const& glyph = *(itr->glyph);
double text_size = glyph.format->text_size * scale_factor;
glyph.face->set_character_sizes(text_size);
path.vertex(c, x, y, angle);
cairo_matrix_t matrix;
matrix.xx = text_size * itr->rot.cos;
matrix.xy = text_size * itr->rot.sin;
matrix.yx = text_size * -itr->rot.sin;
matrix.yy = text_size * itr->rot.cos;
matrix.x0 = 0;
matrix.y0 = 0;
face_set_ptr faces = font_manager.get_face_set(c->format->face_name, c->format->fontset);
double text_size = c->format->text_size * scale_factor;
faces->set_character_sizes(text_size);
set_font_matrix(matrix);
set_font_face(manager, glyph.face);
glyph_ptr glyph = faces->get_glyph(c->c);
if (glyph)
{
cairo_matrix_t matrix;
matrix.xx = text_size * std::cos(angle);
matrix.xy = text_size * std::sin(angle);
matrix.yx = text_size * -std::sin(angle);
matrix.yy = text_size * std::cos(angle);
matrix.x0 = 0;
matrix.y0 = 0;
set_font_matrix(matrix);
set_font_face(manager, glyph->get_face());
glyph_path(glyph->get_index(), sx + x, sy - y);
set_line_width(2.0 * c->format->halo_radius * scale_factor);
set_line_join(ROUND_JOIN);
set_color(c->format->halo_fill);
stroke();
}
glyph_path(glyph.glyph_index, base + ~(itr->pos + glyph.offset.rotate(itr->rot)));
set_line_width(2.0 * glyph.format->halo_radius * scale_factor);
set_line_join(ROUND_JOIN);
set_color(glyph.format->halo_fill);
stroke();
}
path.rewind();
for (std::size_t iii = 0; iii < path.num_nodes(); ++iii)
//Render text
for (itr = pos->begin(); itr != end; itr++)
{
char_info_ptr c;
double x, y, angle;
glyph_info const& glyph = *(itr->glyph);
double text_size = glyph.format->text_size * scale_factor;
glyph.face->set_character_sizes(text_size);
path.vertex(c, x, y, angle);
cairo_matrix_t matrix;
matrix.xx = text_size * itr->rot.cos;
matrix.xy = text_size * itr->rot.sin;
matrix.yx = text_size * -itr->rot.sin;
matrix.yy = text_size * itr->rot.cos;
matrix.x0 = 0;
matrix.y0 = 0;
face_set_ptr faces = font_manager.get_face_set(c->format->face_name, c->format->fontset);
double text_size = c->format->text_size * scale_factor;
faces->set_character_sizes(text_size);
glyph_ptr glyph = faces->get_glyph(c->c);
if (glyph)
{
cairo_matrix_t matrix;
matrix.xx = text_size * std::cos(angle);
matrix.xy = text_size * std::sin(angle);
matrix.yx = text_size * -std::sin(angle);
matrix.yy = text_size * std::cos(angle);
matrix.x0 = 0;
matrix.y0 = 0;
set_font_matrix(matrix);
set_font_face(manager, glyph->get_face());
set_color(c->format->fill);
show_glyph(glyph->get_index(), sx + x, sy - y);
}
set_font_matrix(matrix);
set_font_face(manager, glyph.face);
set_color(glyph.format->fill);
show_glyph(glyph.glyph_index, base + ~(itr->pos + glyph.offset.rotate(itr->rot)));
}
}
}
}
} //ns mapnik

View file

@ -49,7 +49,6 @@
#include <mapnik/expression_evaluator.hpp>
#include <mapnik/warp.hpp>
#include <mapnik/config.hpp>
#include <mapnik/text/text_path.hpp>
#include <mapnik/vertex_converters.hpp>
#include <mapnik/marker_helpers.hpp>
#include <mapnik/noncopyable.hpp>
@ -61,6 +60,7 @@
#include <cairo-version.h>
// boost
#include <boost/math/special_functions/round.hpp>
// agg
@ -711,8 +711,7 @@ void cairo_renderer_base::process(shield_symbolizer const& sym,
mapnik::feature_impl & feature,
proj_transform const& prj_trans)
{
shield_symbolizer_helper<face_manager<freetype_engine>,
label_collision_detector4> helper(
text_symbolizer_helper helper(
sym, feature, prj_trans,
width_, height_,
scale_factor_,
@ -721,21 +720,16 @@ void cairo_renderer_base::process(shield_symbolizer const& sym,
cairo_save_restore guard(context_);
context_.set_operator(sym.comp_op());
while (helper.next())
placements_list const& placements = helper.get();
for (glyph_positions_ptr const& glyphs : placements)
{
placements_type const& placements = helper.placements();
for (unsigned int ii = 0; ii < placements.size(); ++ii)
if (glyphs->marker())
{
pixel_position pos = helper.get_marker_position(placements[ii]);
pos.x += 0.5 * helper.get_marker_width();
pos.y += 0.5 * helper.get_marker_height();
render_marker(pos,
helper.get_marker(),
helper.get_image_transform(),
sym.get_opacity());
context_.add_text(placements[ii], face_manager_, font_manager_, scale_factor_);
render_marker(glyphs->marker_pos(),
*(glyphs->marker()->marker), glyphs->marker()->transform,
sym.get_opacity());
}
context_.add_text(glyphs, face_manager_, font_manager_, scale_factor_);
}
}
@ -1278,8 +1272,7 @@ void cairo_renderer_base::process(text_symbolizer const& sym,
mapnik::feature_impl & feature,
proj_transform const& prj_trans)
{
text_symbolizer_helper<face_manager<freetype_engine>,
label_collision_detector4> helper(
text_symbolizer_helper helper(
sym, feature, prj_trans,
width_, height_,
scale_factor_,
@ -1288,13 +1281,10 @@ void cairo_renderer_base::process(text_symbolizer const& sym,
cairo_save_restore guard(context_);
context_.set_operator(sym.comp_op());
while (helper.next())
placements_list const& placements = helper.get();
for (glyph_positions_ptr const& glyphs : placements)
{
placements_type const& placements = helper.placements();
for (unsigned int ii = 0; ii < placements.size(); ++ii)
{
context_.add_text(placements[ii], face_manager_, font_manager_, scale_factor_);
}
context_.add_text(glyphs, face_manager_, font_manager_, scale_factor_);
}
}

View file

@ -23,15 +23,6 @@
// mapnik
#include <mapnik/debug.hpp>
#include <mapnik/font_engine_freetype.hpp>
#include <mapnik/graphics.hpp>
#include <mapnik/value_types.hpp>
#if defined(GRID_RENDERER)
#include <mapnik/grid/grid.hpp>
#endif
#include <mapnik/text/text_properties.hpp>
#include <mapnik/text/text_path.hpp>
#include <mapnik/pixel_position.hpp>
#include <mapnik/text/face.hpp>
#include <mapnik/util/fs.hpp>
@ -41,20 +32,20 @@
#include <boost/filesystem.hpp>
// stl
#include <sstream>
#include <algorithm>
#include <stdexcept>
// icu
#include <unicode/ubidi.h>
#include <unicode/ushape.h>
#include <unicode/schriter.h>
#include <unicode/uversion.h>
// freetype2
extern "C"
{
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_STROKER_H
}
namespace mapnik
{
freetype_engine::freetype_engine() :
library_(NULL)
@ -267,6 +258,7 @@ face_ptr freetype_engine::create_face(std::string const& family_name)
return face_ptr();
}
stroker_ptr freetype_engine::create_stroker()
{
FT_Stroker s;
@ -278,464 +270,81 @@ stroker_ptr freetype_engine::create_stroker()
return stroker_ptr();
}
void font_face_set::add(face_ptr face)
{
faces_.push_back(face);
dimension_cache_.clear(); //Make sure we don't use old cached data
}
font_face_set::size_type font_face_set::size() const
{
return faces_.size();
}
glyph_ptr font_face_set::get_glyph(unsigned c) const
template <typename T>
face_ptr face_manager<T>::get_face(const std::string &name)
{
for ( face_ptr const& face : faces_)
{
FT_UInt g = face->get_char(c);
if (g) return std::make_shared<font_glyph>(face, g);
}
// Final fallback to empty square if nothing better in any font
return std::make_shared<font_glyph>(*faces_.begin(), 0);
}
char_info font_face_set::character_dimensions(unsigned int c)
{
//Check if char is already in cache
typedef std::map<unsigned, char_info>::const_iterator iterator_type;
iterator_type itr = dimension_cache_.find(c);
if (itr != dimension_cache_.end())
face_ptr_cache_type::iterator itr;
itr = face_ptr_cache_.find(name);
if (itr != face_ptr_cache_.end())
{
return itr->second;
}
FT_Vector pen;
FT_Error error;
pen.x = 0;
pen.y = 0;
FT_BBox glyph_bbox;
FT_Glyph image;
glyph_ptr glyph = get_glyph(c);
FT_Face face = glyph->get_face()->get_face();
FT_Set_Transform(face, 0, &pen);
error = FT_Load_Glyph (face, glyph->get_index(), FT_LOAD_NO_HINTING);
if ( error )
return char_info();
error = FT_Get_Glyph(face->glyph, &image);
if ( error )
return char_info();
FT_Glyph_Get_CBox(image, ft_glyph_bbox_pixels, &glyph_bbox);
FT_Done_Glyph(image);
unsigned tempx = face->glyph->advance.x >> 6;
char_info dim(c, tempx, glyph_bbox.yMax, glyph_bbox.yMin, face->size->metrics.height/64.0);
dimension_cache_.insert(std::make_pair(c, dim));
return dim;
else
{
face_ptr face = engine_.create_face(name);
if (face)
{
face_ptr_cache_.insert(make_pair(name,face));
}
return face;
}
}
void font_face_set::get_string_info(string_info & info, mapnik::value_unicode_string const& ustr, char_properties *format)
template <typename T>
face_set_ptr face_manager<T>::get_face_set(const std::string &name)
{
double avg_height = character_dimensions('X').height();
UErrorCode err = U_ZERO_ERROR;
mapnik::value_unicode_string reordered;
mapnik::value_unicode_string shaped;
int32_t length = ustr.length();
UBiDi *bidi = ubidi_openSized(length, 0, &err);
ubidi_setPara(bidi, ustr.getBuffer(), length, UBIDI_DEFAULT_LTR, 0, &err);
ubidi_writeReordered(bidi, reordered.getBuffer(length),
length, UBIDI_DO_MIRRORING, &err);
reordered.releaseBuffer(length);
u_shapeArabic(reordered.getBuffer(), length,
shaped.getBuffer(length), length,
U_SHAPE_LETTERS_SHAPE | U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
U_SHAPE_TEXT_DIRECTION_VISUAL_LTR, &err);
shaped.releaseBuffer(length);
if (U_SUCCESS(err)) {
U_NAMESPACE_QUALIFIER StringCharacterIterator iter(shaped);
for (iter.setToStart(); iter.hasNext();) {
UChar ch = iter.nextPostInc();
char_info char_dim = character_dimensions(ch);
char_dim.format = format;
char_dim.avg_height = avg_height;
info.add_info(char_dim);
}
}
#if (U_ICU_VERSION_MAJOR_NUM*100 + U_ICU_VERSION_MINOR_NUM >= 406)
if (ubidi_getBaseDirection(ustr.getBuffer(), length) == UBIDI_RTL)
face_set_ptr face_set = std::make_shared<font_face_set>();
if (face_ptr face = get_face(name))
{
info.set_rtl(true);
face_set->add(face);
}
return face_set;
}
template <typename T>
face_set_ptr face_manager<T>::get_face_set(const font_set &fset)
{
std::vector<std::string> const& names = fset.get_face_names();
face_set_ptr face_set = std::make_shared<font_face_set>();
for (std::vector<std::string>::const_iterator name = names.begin(); name != names.end(); ++name)
{
face_ptr face = get_face(*name);
if (face)
{
face_set->add(face);
}
#ifdef MAPNIK_LOG
else
{
MAPNIK_LOG_DEBUG(font_engine_freetype)
<< "Failed to find face '" << *name
<< "' in font set '" << fset.get_name() << "'\n";
}
#endif
ubidi_close(bidi);
}
void font_face_set::set_pixel_sizes(unsigned size)
{
for ( face_ptr const& face : faces_)
{
face->set_pixel_sizes(size);
}
}
void font_face_set::set_character_sizes(double size)
{
for ( face_ptr const& face : faces_)
{
face->set_character_sizes(size);
}
}
template <typename T>
void composite_bitmap(T & pixmap,
FT_Bitmap *bitmap,
unsigned rgba,
int x,
int y,
double opacity,
composite_mode_e comp_op)
{
int x_max=x+bitmap->width;
int y_max=y+bitmap->rows;
int i,p,j,q;
for (i=x,p=0;i<x_max;++i,++p)
{
for (j=y,q=0;j<y_max;++j,++q)
{
unsigned gray=bitmap->buffer[q*bitmap->width+p];
if (gray)
{
pixmap.composite_pixel(comp_op, i, j, rgba, gray, opacity);
}
}
}
return face_set;
}
template <typename T>
void render_halo(T & pixmap,
FT_Bitmap *bitmap,
unsigned rgba,
int x1,
int y1,
double halo_radius,
double opacity,
composite_mode_e comp_op)
face_set_ptr face_manager<T>::get_face_set(const std::string &name, boost::optional<font_set> fset)
{
int width = bitmap->width;
int height = bitmap->rows;
int x, y;
if (halo_radius < 1.0)
if (fset && fset->size() > 0)
{
for (x=0; x < width; x++)
{
for (y=0; y < height; y++)
{
int gray = bitmap->buffer[y*bitmap->width+x];
if (gray)
{
pixmap.composite_pixel(comp_op, x+x1-1, y+y1-1, rgba, gray*halo_radius*halo_radius, opacity);
pixmap.composite_pixel(comp_op, x+x1, y+y1-1, rgba, gray*halo_radius, opacity);
pixmap.composite_pixel(comp_op, x+x1+1, y+y1-1, rgba, gray*halo_radius*halo_radius, opacity);
pixmap.composite_pixel(comp_op, x+x1-1, y+y1, rgba, gray*halo_radius, opacity);
pixmap.composite_pixel(comp_op, x+x1, y+y1, rgba, gray, opacity);
pixmap.composite_pixel(comp_op, x+x1+1, y+y1, rgba, gray*halo_radius, opacity);
pixmap.composite_pixel(comp_op, x+x1-1, y+y1+1, rgba, gray*halo_radius*halo_radius, opacity);
pixmap.composite_pixel(comp_op, x+x1, y+y1+1, rgba, gray*halo_radius, opacity);
pixmap.composite_pixel(comp_op, x+x1+1, y+y1+1, rgba, gray*halo_radius*halo_radius, opacity);
}
}
}
} else {
for (x=0; x < width; x++)
{
for (y=0; y < height; y++)
{
int gray = bitmap->buffer[y*bitmap->width+x];
if (gray)
{
for (int n=-halo_radius; n <=halo_radius; ++n)
for (int m=-halo_radius; m <= halo_radius; ++m)
pixmap.composite_pixel(comp_op, x+x1+m, y+y1+n, rgba, gray, opacity);
}
}
}
return get_face_set(*fset);
}
else
{
return get_face_set(name);
}
}
template <typename T>
void render_halo_id(T & pixmap,
FT_Bitmap *bitmap,
mapnik::value_integer feature_id,
int x1,
int y1,
int halo_radius)
{
int width = bitmap->width;
int height = bitmap->rows;
int x, y;
for (x=0; x < width; x++)
{
for (y=0; y < height; y++)
{
int gray = bitmap->buffer[y*bitmap->width+x];
if (gray)
{
for (int n=-halo_radius; n <=halo_radius; ++n)
for (int m=-halo_radius; m <= halo_radius; ++m)
pixmap.setPixel(x+x1+m,y+y1+n,feature_id);
}
}
}
}
template <typename T>
text_renderer<T>::text_renderer(pixmap_type & pixmap,
face_manager<freetype_engine> & font_manager,
halo_rasterizer_e rasterizer,
composite_mode_e comp_op,
double scale_factor)
: pixmap_(pixmap),
font_manager_(font_manager),
rasterizer_(rasterizer),
comp_op_(comp_op),
scale_factor_(scale_factor) {}
template <typename T>
box2d<double> text_renderer<T>::prepare_glyphs(text_path const& path)
{
//clear glyphs
glyphs_.clear();
FT_Matrix matrix;
FT_Vector pen;
FT_Error error;
FT_BBox bbox;
bbox.xMin = bbox.yMin = 32000; // Initialize these so we can tell if we
bbox.xMax = bbox.yMax = -32000; // properly grew the bbox later
for (std::size_t i = 0; i < path.num_nodes(); ++i)
{
char_info_ptr c;
double x, y, angle;
path.vertex(c, x, y, angle);
// TODO Enable when we have support for setting verbosity
// MAPNIK_LOG_DEBUG(font_engine_freetype) << "text_renderer: prepare_glyphs="
// << c << "," << x << "," << y << "," << angle;
FT_BBox glyph_bbox;
FT_Glyph image;
pen.x = int(x * 64);
pen.y = int(y * 64);
face_set_ptr faces = font_manager_.get_face_set(c->format->face_name, c->format->fontset);
faces->set_character_sizes(c->format->text_size*scale_factor_);
glyph_ptr glyph = faces->get_glyph(unsigned(c->c));
FT_Face face = glyph->get_face()->get_face();
matrix.xx = (FT_Fixed)( std::cos( angle ) * 0x10000L );
matrix.xy = (FT_Fixed)(-std::sin( angle ) * 0x10000L );
matrix.yx = (FT_Fixed)( std::sin( angle ) * 0x10000L );
matrix.yy = (FT_Fixed)( std::cos( angle ) * 0x10000L );
FT_Set_Transform(face, &matrix, &pen);
error = FT_Load_Glyph(face, glyph->get_index(), FT_LOAD_NO_HINTING);
if ( error )
continue;
error = FT_Get_Glyph(face->glyph, &image);
if ( error )
continue;
FT_Glyph_Get_CBox(image,ft_glyph_bbox_pixels, &glyph_bbox);
if (glyph_bbox.xMin < bbox.xMin)
bbox.xMin = glyph_bbox.xMin;
if (glyph_bbox.yMin < bbox.yMin)
bbox.yMin = glyph_bbox.yMin;
if (glyph_bbox.xMax > bbox.xMax)
bbox.xMax = glyph_bbox.xMax;
if (glyph_bbox.yMax > bbox.yMax)
bbox.yMax = glyph_bbox.yMax;
// Check if we properly grew the bbox
if ( bbox.xMin > bbox.xMax )
{
bbox.xMin = 0;
bbox.yMin = 0;
bbox.xMax = 0;
bbox.yMax = 0;
}
// take ownership of the glyph
glyphs_.push_back(new glyph_t(image, c->format));
}
return box2d<double>(bbox.xMin, bbox.yMin, bbox.xMax, bbox.yMax);
}
template <typename T>
void text_renderer<T>::render(pixel_position const& pos)
{
FT_Error error;
FT_Vector start;
int height = pixmap_.height();
start.x = static_cast<FT_Pos>(pos.x * (1 << 6));
start.y = static_cast<FT_Pos>((height - pos.y) * (1 << 6));
// now render transformed glyphs
typename glyphs_t::iterator itr;
for (itr = glyphs_.begin(); itr != glyphs_.end(); ++itr)
{
double halo_radius = itr->properties->halo_radius * scale_factor_;
//make sure we've got reasonable values.
if (halo_radius <= 0.0 || halo_radius > 1024.0) continue;
FT_Glyph g;
error = FT_Glyph_Copy(itr->image, &g);
if (!error)
{
FT_Glyph_Transform(g,0,&start);
if (rasterizer_ == HALO_RASTERIZER_FULL)
{
stroker_ptr stk = font_manager_.get_stroker();
stk->init(halo_radius);
FT_Glyph_Stroke(&g,stk->get(),1);
error = FT_Glyph_To_Bitmap( &g,FT_RENDER_MODE_NORMAL,0,1);
if (!error)
{
FT_BitmapGlyph bit = (FT_BitmapGlyph)g;
composite_bitmap(pixmap_,
&bit->bitmap,
itr->properties->halo_fill.rgba(),
bit->left,
height - bit->top,
itr->properties->text_opacity,
comp_op_);
}
}
else
{
error = FT_Glyph_To_Bitmap( &g,FT_RENDER_MODE_NORMAL,0,1);
if (!error)
{
FT_BitmapGlyph bit = (FT_BitmapGlyph)g;
render_halo(pixmap_,
&bit->bitmap,
itr->properties->halo_fill.rgba(),
bit->left,
height - bit->top,
halo_radius,
itr->properties->text_opacity,
comp_op_);
}
}
}
FT_Done_Glyph(g);
}
//render actual text
for (itr = glyphs_.begin(); itr != glyphs_.end(); ++itr)
{
FT_Glyph_Transform(itr->image,0,&start);
error = FT_Glyph_To_Bitmap( &(itr->image),FT_RENDER_MODE_NORMAL,0,1);
if ( ! error )
{
FT_BitmapGlyph bit = (FT_BitmapGlyph)itr->image;
composite_bitmap(pixmap_,
&bit->bitmap,
itr->properties->fill.rgba(),
bit->left,
height - bit->top,
itr->properties->text_opacity,
comp_op_
);
}
}
}
#if defined(GRID_RENDERER)
template <typename T>
void text_renderer<T>::render_id(mapnik::value_integer feature_id,
pixel_position const& pos)
{
FT_Error error;
FT_Vector start;
unsigned height = pixmap_.height();
start.x = static_cast<FT_Pos>(pos.x * (1 << 6));
start.y = static_cast<FT_Pos>((height - pos.y) * (1 << 6));
// now render transformed glyphs
typename glyphs_t::iterator itr;
for (itr = glyphs_.begin(); itr != glyphs_.end(); ++itr)
{
FT_Glyph_Transform(itr->image,0,&start);
error = FT_Glyph_To_Bitmap( &(itr->image),FT_RENDER_MODE_NORMAL,0,1);
if ( ! error )
{
FT_BitmapGlyph bit = (FT_BitmapGlyph)itr->image;
render_halo_id(pixmap_,
&bit->bitmap,
feature_id,
bit->left,
height - bit->top,
static_cast<int>(itr->properties->halo_radius));
}
}
}
#endif
#ifdef MAPNIK_THREADSAFE
std::mutex freetype_engine::mutex_;
#endif
std::map<std::string,std::pair<int,std::string> > freetype_engine::name2file_;
std::map<std::string,std::string> freetype_engine::memory_fonts_;
template class face_manager<freetype_engine>;
template text_renderer<image_32>::text_renderer(image_32&,
face_manager<freetype_engine>&,
halo_rasterizer_e,
composite_mode_e,
double);
template box2d<double>text_renderer<image_32>::prepare_glyphs(text_path const&);
template void text_renderer<image_32>::render(pixel_position const&);
#if defined(GRID_RENDERER)
template void text_renderer<grid>::render_id(mapnik::value_integer,
pixel_position const&);
template text_renderer<grid>::text_renderer(grid&,
face_manager<freetype_engine>&,
halo_rasterizer_e,
composite_mode_e, double);
template box2d<double>text_renderer<grid>::prepare_glyphs(text_path const& );
#endif
}

View file

@ -22,14 +22,9 @@
*****************************************************************************/
// mapnik
#include <mapnik/feature.hpp>
#include <mapnik/grid/grid_rasterizer.hpp>
#include <mapnik/grid/grid_renderer.hpp>
#include <mapnik/grid/grid_renderer_base.hpp>
#include <mapnik/grid/grid.hpp>
#include <mapnik/text/symbolizer_helpers.hpp>
#include <mapnik/pixel_position.hpp>
#include <mapnik/text/face.hpp>
#include <mapnik/text/renderer.hpp>
// agg
#include "agg_trans_affine.h"
@ -41,50 +36,28 @@ void grid_renderer<T>::process(shield_symbolizer const& sym,
mapnik::feature_impl & feature,
proj_transform const& prj_trans)
{
shield_symbolizer_helper<face_manager<freetype_engine>,
label_collision_detector4> helper(
text_symbolizer_helper helper(
sym, feature, prj_trans,
width_, height_,
scale_factor_,
scale_factor_ * (1.0/pixmap_.get_resolution()),
t_, font_manager_, *detector_,
query_extent_);
bool placement_found = false;
text_renderer<T> ren(pixmap_,
font_manager_,
sym.get_halo_rasterizer(),
sym.comp_op(),
scale_factor_);
grid_text_renderer<T> ren(pixmap_, sym.comp_op(), scale_factor_);
text_placement_info_ptr placement;
while (helper.next())
placements_list const& placements = helper.get();
if (placements.empty()) return;
for (glyph_positions_ptr glyphs : placements)
{
placement_found = true;
placements_type const& placements = helper.placements();
for (unsigned int ii = 0; ii < placements.size(); ++ii)
{
// get_marker_position returns (minx,miny) corner position,
// while (currently only) agg_renderer::render_marker newly
// expects center position;
// until all renderers and shield_symbolizer_helper are
// modified accordingly, we must adjust the position here
pixel_position pos = helper.get_marker_position(placements[ii]);
pos.x += 0.5 * helper.get_marker_width();
pos.y += 0.5 * helper.get_marker_height();
render_marker(feature,
pixmap_.get_resolution(),
pos,
helper.get_marker(),
helper.get_image_transform(),
sym.get_opacity(),
sym.comp_op());
ren.prepare_glyphs(placements[ii]);
ren.render_id(feature.id(), placements[ii].center);
}
if (glyphs->marker()->marker)
render_marker(feature, pixmap_.get_resolution(),
glyphs->marker_pos(),
*(glyphs->marker()->marker),
glyphs->marker()->transform,
sym.get_opacity(), sym.comp_op());
ren.render(*glyphs, feature.id());
}
if (placement_found)
pixmap_.add_feature(feature);
pixmap_.add_feature(feature);
}
template void grid_renderer<grid>::process(shield_symbolizer const&,

View file

@ -21,10 +21,9 @@
*****************************************************************************/
// mapnik
#include <mapnik/feature.hpp>
#include <mapnik/grid/grid_renderer.hpp>
#include <mapnik/text/symbolizer_helpers.hpp>
#include <mapnik/text/face.hpp>
#include <mapnik/text/renderer.hpp>
namespace mapnik {
@ -33,32 +32,22 @@ void grid_renderer<T>::process(text_symbolizer const& sym,
mapnik::feature_impl & feature,
proj_transform const& prj_trans)
{
text_symbolizer_helper<face_manager<freetype_engine>,
label_collision_detector4> helper(
text_symbolizer_helper helper(
sym, feature, prj_trans,
width_, height_,
scale_factor_ * (1.0/pixmap_.get_resolution()),
t_, font_manager_, *detector_,
query_extent_);
bool placement_found = false;
text_renderer<T> ren(pixmap_,
font_manager_,
sym.get_halo_rasterizer(),
sym.comp_op(),
scale_factor_);
grid_text_renderer<T> ren(pixmap_, sym.comp_op(), scale_factor_);
while (helper.next()) {
placement_found = true;
placements_type const& placements = helper.placements();
for (unsigned int ii = 0; ii < placements.size(); ++ii)
{
ren.prepare_glyphs(placements[ii]);
ren.render_id(feature.id(), placements[ii].center);
}
placements_list const& placements = helper.get();
if (!placements.size()) return;
for (glyph_positions_ptr glyphs : placements)
{
ren.render(*glyphs, feature.id());
}
if (placement_found) pixmap_.add_feature(feature);
pixmap_.add_feature(feature);
}
template void grid_renderer<grid>::process(text_symbolizer const&,
@ -66,4 +55,3 @@ template void grid_renderer<grid>::process(text_symbolizer const&,
proj_transform const&);
}

View file

@ -1192,10 +1192,10 @@ void map_parser::parse_text_symbolizer(rule & rule, xml_node const& sym)
placement_finder = std::make_shared<text_placements_dummy>();
placement_finder->defaults.from_xml(sym, fontsets_);
}
if (strict_ &&
!placement_finder->defaults.format.fontset)
if (strict_ && (!placement_finder->defaults.format->fontset ||
!placement_finder->defaults.format->fontset->size()))
{
ensure_font_face(placement_finder->defaults.format.face_name);
ensure_font_face(placement_finder->defaults.format->face_name);
}
text_symbolizer text_symbol = text_symbolizer(placement_finder);
@ -1225,9 +1225,10 @@ void map_parser::parse_shield_symbolizer(rule & rule, xml_node const& sym)
}
placement_finder->defaults.from_xml(sym, fontsets_);
if (strict_ &&
!placement_finder->defaults.format.fontset)
(!placement_finder->defaults.format->fontset ||
!placement_finder->defaults.format->fontset->size()))
{
ensure_font_face(placement_finder->defaults.format.face_name);
ensure_font_face(placement_finder->defaults.format->face_name);
}
shield_symbolizer shield_symbol = shield_symbolizer(placement_finder);

View file

@ -228,19 +228,19 @@ public:
set_attr(sym_node, "unlock-image", sym.get_unlock_image());
}
if (sym.get_placement_options()->defaults.format.text_opacity !=
dfl.get_placement_options()->defaults.format.text_opacity || explicit_defaults_)
if (sym.get_placement_options()->defaults.format->text_opacity !=
dfl.get_placement_options()->defaults.format->text_opacity || explicit_defaults_)
{
set_attr(sym_node, "text-opacity", sym.get_placement_options()->defaults.format.text_opacity);
set_attr(sym_node, "text-opacity", sym.get_placement_options()->defaults.format->text_opacity);
}
position displacement = sym.get_shield_displacement();
if (displacement.first != dfl.get_shield_displacement().first || explicit_defaults_)
pixel_position displacement = sym.get_shield_displacement();
if (displacement.x != dfl.get_shield_displacement().x || explicit_defaults_)
{
set_attr(sym_node, "shield-dx", displacement.first);
set_attr(sym_node, "shield-dx", displacement.x);
}
if (displacement.second != dfl.get_shield_displacement().second || explicit_defaults_)
if (displacement.y != dfl.get_shield_displacement().y || explicit_defaults_)
{
set_attr(sym_node, "shield-dy", displacement.second);
set_attr(sym_node, "shield-dy", displacement.y);
}
if (sym.get_image_transform())
{

View file

@ -76,10 +76,10 @@ bool shield_symbolizer::get_unlock_image() const
void shield_symbolizer::set_shield_displacement(double shield_dx,double shield_dy)
{
shield_displacement_ = std::make_pair(shield_dx, shield_dy);
shield_displacement_.set(shield_dx, shield_dy);
}
position const& shield_symbolizer::get_shield_displacement() const
pixel_position const& shield_symbolizer::get_shield_displacement() const
{
return shield_displacement_;
}

140
src/text/face.cpp Normal file
View file

@ -0,0 +1,140 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2013 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
*
*****************************************************************************/
// mapnik
#include <mapnik/text/face.hpp>
#include <mapnik/debug.hpp>
extern "C"
{
#include FT_GLYPH_H
}
namespace mapnik
{
font_face::font_face(FT_Face face)
: face_(face), dimension_cache_(), char_height_(0.0)
{
}
double font_face::get_char_height() const
{
if (char_height_ != 0.0) return char_height_;
glyph_info tmp;
tmp.glyph_index = FT_Get_Char_Index(face_, 'X');
glyph_dimensions(tmp);
char_height_ = tmp.height();
return char_height_;
}
bool font_face::set_character_sizes(double size)
{
char_height_ = 0.0;
return !FT_Set_Char_Size(face_,0,(FT_F26Dot6)(size * (1<<6)),0,0);
}
void font_face::glyph_dimensions(glyph_info & glyph) const
{
//TODO
//Check if char is already in cache
// std::map<glyph_index_t, glyph_info>::const_iterator itr;
// itr = dimension_cache_.find(glyph.glyph_index);
// if (itr != dimension_cache_.end()) {
// glyph = itr->second;
// return;
// }
FT_Vector pen;
pen.x = 0;
pen.y = 0;
/*
FT_Matrix matrix;
matrix.xx = (FT_Fixed)( 1 * 0x10000L );
matrix.xy = (FT_Fixed)( 0 * 0x10000L );
matrix.yx = (FT_Fixed)( 0 * 0x10000L );
matrix.yy = (FT_Fixed)( 1 * 0x10000L );
FT_Set_Transform(face_, &matrix, &pen);
*/
// TODO - any benefit to using a matrix here?
FT_Set_Transform(face_, 0, &pen);
if (FT_Load_Glyph (face_, glyph.glyph_index, FT_LOAD_NO_HINTING)) return;
FT_Glyph image;
if (FT_Get_Glyph(face_->glyph, &image)) return;
FT_BBox glyph_bbox;
FT_Glyph_Get_CBox(image, ft_glyph_bbox_pixels, &glyph_bbox);
FT_Done_Glyph(image);
glyph.ymin = glyph_bbox.yMin; //pixels!
glyph.ymax = glyph_bbox.yMax;
glyph.line_height = face_->size->metrics.height/64.0;
// TODO: we round to integers for now to maintain
// back compatibility with Mapnik 2.x
//glyph.width = face_->glyph->advance.x/64.0;
glyph.width = face_->glyph->advance.x >> 6;
//TODO: dimension_cache_.insert(std::pair<unsigned, char_info>(c, dim));
}
font_face::~font_face()
{
MAPNIK_LOG_DEBUG(font_face) <<
"font_face: Clean up face \"" << family_name() <<
" " << style_name() << "\"";
FT_Done_Face(face_);
}
/******************************************************************************/
void font_face_set::add(face_ptr face)
{
faces_.push_back(face);
}
void font_face_set::set_character_sizes(double size)
{
for (face_ptr const& face : faces_)
{
face->set_character_sizes(size);
}
}
/******************************************************************************/
void stroker::init(double radius)
{
FT_Stroker_Set(s_, (FT_Fixed) (radius * (1<<6)),
FT_STROKER_LINECAP_ROUND,
FT_STROKER_LINEJOIN_ROUND,
0);
}
stroker::~stroker()
{
MAPNIK_LOG_DEBUG(font_engine_freetype) << "stroker: Destroy stroker=" << s_;
FT_Stroker_Done(s_);
}
}//ns mapnik

View file

@ -27,10 +27,12 @@
#include <mapnik/expression_string.hpp>
#include <mapnik/expression_evaluator.hpp>
#include <mapnik/text/text_properties.hpp>
#include <mapnik/color_factory.hpp>
#include <mapnik/feature.hpp>
#include <mapnik/xml_node.hpp>
// boost
//boost
#include <boost/property_tree/ptree.hpp>
@ -46,7 +48,6 @@ void expression_format::to_xml(boost::property_tree::ptree &xml) const
if (character_spacing) set_attr(new_node, "character-spacing", to_expression_string(*character_spacing));
if (line_spacing) set_attr(new_node, "line-spacing", to_expression_string(*line_spacing));
if (text_opacity) set_attr(new_node, "opacity", to_expression_string(*text_opacity));
if (wrap_before) set_attr(new_node, "wrap-before", to_expression_string(*wrap_before));
if (wrap_char) set_attr(new_node, "wrap-character", to_expression_string(*wrap_char));
if (fill) set_attr(new_node, "fill", to_expression_string(*fill));
if (halo_fill) set_attr(new_node, "halo-fill", to_expression_string(*halo_fill));
@ -67,7 +68,6 @@ node_ptr expression_format::from_xml(xml_node const& xml)
n->character_spacing = get_expression(xml, "character-spacing");
n->line_spacing = get_expression(xml, "line-spacing");
n->text_opacity = get_expression(xml, "opacity");
n->wrap_before = get_expression(xml, "wrap-before");
n->wrap_char = get_expression(xml, "wrap-character");
n->fill = get_expression(xml, "fill");
n->halo_fill = get_expression(xml, "halo-fill");
@ -83,28 +83,26 @@ expression_ptr expression_format::get_expression(xml_node const& xml, std::strin
}
void expression_format::apply(char_properties const& p, feature_impl const& feature, processed_text &output) const
void expression_format::apply(char_properties_ptr p, feature_impl const& feature, text_layout &output) const
{
char_properties new_properties = p;
if (face_name) new_properties.face_name =
char_properties_ptr new_properties = std::make_shared<char_properties>(*p);
if (face_name) new_properties->face_name =
boost::apply_visitor(evaluate<feature_impl,value_type>(feature), *face_name).to_string();
if (text_size) new_properties.text_size =
if (text_size) new_properties->text_size =
boost::apply_visitor(evaluate<feature_impl,value_type>(feature), *text_size).to_double();
if (character_spacing) new_properties.character_spacing =
if (character_spacing) new_properties->character_spacing =
boost::apply_visitor(evaluate<feature_impl,value_type>(feature), *character_spacing).to_double();
if (line_spacing) new_properties.line_spacing =
if (line_spacing) new_properties->line_spacing =
boost::apply_visitor(evaluate<feature_impl,value_type>(feature), *line_spacing).to_double();
if (text_opacity) new_properties.text_opacity =
if (text_opacity) new_properties->text_opacity =
boost::apply_visitor(evaluate<feature_impl,value_type>(feature), *text_opacity).to_double();
if (wrap_before) new_properties.wrap_before =
boost::apply_visitor(evaluate<feature_impl,value_type>(feature), *wrap_before).to_bool();
if (wrap_char) new_properties.wrap_char =
if (wrap_char) new_properties->wrap_char =
boost::apply_visitor(evaluate<feature_impl,value_type>(feature), *character_spacing).to_unicode()[0];
// if (fill) new_properties.fill =
// boost::apply_visitor(evaluate<feature_impl,value_type>(feature), *fill).to_color();
// if (halo_fill) new_properties.halo_fill =
// boost::apply_visitor(evaluate<feature_impl,value_type>(feature), *halo_fill).to_color();
if (halo_radius) new_properties.halo_radius =
if (fill) new_properties->fill = parse_color(
boost::apply_visitor(evaluate<feature_impl,value_type>(feature), *fill).to_string());
if (halo_fill) new_properties->halo_fill = parse_color(
boost::apply_visitor(evaluate<feature_impl,value_type>(feature), *halo_fill).to_string());
if (halo_radius) new_properties->halo_radius =
boost::apply_visitor(evaluate<feature_impl,value_type>(feature), *halo_radius).to_double();
if (child_) {
@ -134,7 +132,6 @@ void expression_format::add_expressions(expression_set &output) const
output.insert(character_spacing);
output.insert(line_spacing);
output.insert(text_opacity);
output.insert(wrap_before);
output.insert(wrap_char);
output.insert(fill);
output.insert(halo_fill);

View file

@ -27,7 +27,8 @@
#include <mapnik/ptree_helpers.hpp>
#include <mapnik/xml_node.hpp>
// boost
//boost
#include <boost/property_tree/ptree.hpp>
namespace mapnik {
@ -67,8 +68,6 @@ node_ptr format_node::from_xml(xml_node const& xml)
n->character_spacing = xml.get_opt_attr<double>("character-spacing");
n->line_spacing = xml.get_opt_attr<double>("line-spacing");
n->text_opacity = xml.get_opt_attr<double>("opacity");
boost::optional<boolean> wrap = xml.get_opt_attr<boolean>("wrap-before");
if (wrap) n->wrap_before = *wrap;
n->wrap_char = xml.get_opt_attr<unsigned>("wrap-character");
n->text_transform = xml.get_opt_attr<text_transform_e>("text-transform");
n->fill = xml.get_opt_attr<color>("fill");
@ -78,20 +77,19 @@ node_ptr format_node::from_xml(xml_node const& xml)
}
void format_node::apply(char_properties const& p, const feature_impl &feature, processed_text &output) const
void format_node::apply(char_properties_ptr p, const feature_impl &feature, text_layout &output) const
{
char_properties new_properties = p;
if (face_name) new_properties.face_name = *face_name;
if (text_size) new_properties.text_size = *text_size;
if (character_spacing) new_properties.character_spacing = *character_spacing;
if (line_spacing) new_properties.line_spacing = *line_spacing;
if (text_opacity) new_properties.text_opacity = *text_opacity;
if (wrap_before) new_properties.wrap_before = *wrap_before;
if (wrap_char) new_properties.wrap_char = *wrap_char;
if (text_transform) new_properties.text_transform = *text_transform;
if (fill) new_properties.fill = *fill;
if (halo_fill) new_properties.halo_fill = *halo_fill;
if (halo_radius) new_properties.halo_radius = *halo_radius;
char_properties_ptr new_properties = std::make_shared<char_properties>(*p);
if (face_name) new_properties->face_name = *face_name;
if (text_size) new_properties->text_size = *text_size;
if (character_spacing) new_properties->character_spacing = *character_spacing;
if (line_spacing) new_properties->line_spacing = *line_spacing;
if (text_opacity) new_properties->text_opacity = *text_opacity;
if (wrap_char) new_properties->wrap_char = *wrap_char;
if (text_transform) new_properties->text_transform = *text_transform;
if (fill) new_properties->fill = *fill;
if (halo_fill) new_properties->halo_fill = *halo_fill;
if (halo_radius) new_properties->halo_radius = *halo_radius;
if (child_) {
child_->apply(new_properties, feature, output);

View file

@ -32,7 +32,6 @@ namespace mapnik {
using boost::property_tree::ptree;
namespace formatting {
/************************************************************/
void list_node::to_xml(boost::property_tree::ptree & xml) const
{
@ -43,7 +42,7 @@ void list_node::to_xml(boost::property_tree::ptree & xml) const
}
void list_node::apply(char_properties const& p, feature_impl const& feature, processed_text &output) const
void list_node::apply(char_properties_ptr p, feature_impl const& feature, text_layout & output) const
{
for (node_ptr const& node : children_)
{

View file

@ -25,13 +25,16 @@
#include <mapnik/expression_evaluator.hpp>
#include <mapnik/feature.hpp>
#include <mapnik/text/text_properties.hpp>
#include <mapnik/text/processed_text.hpp>
#include <mapnik/xml_node.hpp>
#include <mapnik/value_types.hpp>
#include <mapnik/text/layout.hpp>
//boost
// boost
#include <boost/property_tree/ptree.hpp>
namespace mapnik
{
namespace formatting
@ -51,26 +54,26 @@ node_ptr text_node::from_xml(xml_node const& xml)
return std::make_shared<text_node>(xml.get_value<expression_ptr>());
}
void text_node::apply(char_properties const& p, feature_impl const& feature, processed_text &output) const
void text_node::apply(char_properties_ptr p, feature_impl const& feature, text_layout &output) const
{
mapnik::value_unicode_string text_str = boost::apply_visitor(evaluate<feature_impl,value_type>(feature), *text_).to_unicode();
if (p.text_transform == UPPERCASE)
if (p->text_transform == UPPERCASE)
{
text_str = text_str.toUpper();
}
else if (p.text_transform == LOWERCASE)
else if (p->text_transform == LOWERCASE)
{
text_str = text_str.toLower();
}
#if !UCONFIG_NO_BREAK_ITERATION
else if (p.text_transform == CAPITALIZE)
else if (p->text_transform == CAPITALIZE)
{
// note: requires BreakIterator support in ICU which is optional
text_str = text_str.toTitle(NULL);
}
#endif
if (text_str.length() > 0) {
output.push_back(p, text_str);
output.add_text(text_str, p);
}
}

200
src/text/itemizer.cpp Normal file
View file

@ -0,0 +1,200 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2013 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
*
*****************************************************************************/
//mapnik
#include <mapnik/text/itemizer.hpp>
#include <mapnik/text/scrptrun.hpp>
#include <mapnik/debug.hpp>
// stl
#include <algorithm>
namespace mapnik
{
text_itemizer::text_itemizer()
: text_(), format_runs_(), direction_runs_(), script_runs_()
{
forced_line_breaks_.push_back(0);
}
void text_itemizer::add_text(mapnik::value_unicode_string str, char_properties_ptr format)
{
unsigned start = text_.length();
text_ += str;
format_runs_.emplace_back(format, start, text_.length());
while ((start = text_.indexOf('\n', start)+1) > 0)
{
forced_line_breaks_.push_back(start);
}
}
std::list<text_item> const& text_itemizer::itemize(unsigned start, unsigned end)
{
if (end == 0) {
end = text_.length();
}
// format itemiziation is done by add_text()
itemize_direction(start, end);
itemize_script();
create_item_list();
return output_;
}
void text_itemizer::clear()
{
output_.clear();
text_.remove();
format_runs_.clear();
forced_line_breaks_.clear();
forced_line_breaks_.push_back(0);
}
std::pair<unsigned, unsigned> text_itemizer::line(unsigned i) const
{
#ifdef MAPNIK_DEBUG
if (i >= forced_line_breaks_.size()) return std::make_pair(0, 0);
#endif
if (i == forced_line_breaks_.size()-1)
{
return std::make_pair(forced_line_breaks_[i], text_.length());
}
//Note -1 offset to exclude the \n char
return std::make_pair(forced_line_breaks_[i], forced_line_breaks_[i+1]-1);
}
unsigned text_itemizer::num_lines() const
{
return forced_line_breaks_.size();
}
void text_itemizer::itemize_direction(unsigned start, unsigned end)
{
direction_runs_.clear();
UErrorCode error = U_ZERO_ERROR;
int32_t length = end - start;
UBiDi *bidi = ubidi_openSized(length, 0, &error);
if (!bidi || U_FAILURE(error))
{
MAPNIK_LOG_ERROR(text_itemizer) << "Failed to create bidi object: " << u_errorName(error) << "\n";
return;
}
ubidi_setPara(bidi, text_.getBuffer() + start, length, UBIDI_DEFAULT_LTR, 0, &error);
if (U_SUCCESS(error))
{
UBiDiDirection direction = ubidi_getDirection(bidi);
if (direction != UBIDI_MIXED)
{
direction_runs_.emplace_back(direction, start, end);
}
else
{
// mixed-directional
int32_t count = ubidi_countRuns(bidi, &error);
if(U_SUCCESS(error))
{
for(int i=0; i<count; ++i)
{
int32_t length;
int32_t run_start;
direction = ubidi_getVisualRun(bidi, i, &run_start, &length);
run_start += start; //Add offset to compensate offset in setPara
direction_runs_.emplace_back(direction, run_start, run_start+length);
}
}
}
}
else
{
MAPNIK_LOG_ERROR(text_itemizer) << "ICU error: " << u_errorName(error) << "\n"; //TODO: Exception
}
ubidi_close(bidi);
}
void text_itemizer::itemize_script()
{
script_runs_.clear();
ScriptRun runs(text_.getBuffer(), text_.length());
while (runs.next())
{
script_runs_.emplace_back(runs.getScriptCode(), runs.getScriptStart(), runs.getScriptEnd());
}
}
template <typename T>
typename T::const_iterator text_itemizer::find_run(T const& list, unsigned position)
{
typename T::const_iterator itr = list.begin(), end = list.end();
for ( ;itr!=end; ++itr)
{
// end is the first character not included in text range!
if (itr->start <= position && itr->end > position) return itr;
}
return itr;
}
void text_itemizer::create_item_list()
{
/* This function iterates over direction runs in visual order and splits them if neccessary.
* Split RTL runs are processed in reverse order to keep glyphs in correct order.
*
* logical 123 | 456789
* LTR visual 123 | 456789
* RTL visual 987654 | 321
* Glyphs within a single run are reversed by the shaper.
*/
output_.clear();
for (auto const& dir_run : direction_runs_)
{
unsigned position = dir_run.start;
unsigned end = dir_run.end;
std::list<text_item>::iterator rtl_insertion_point = output_.end();
// Find first script and format run
format_run_list::const_iterator format_itr = find_run(format_runs_, position);
script_run_list::const_iterator script_itr = find_run(script_runs_, position);
while (position < end)
{
assert(script_itr != script_runs_.end());
assert(format_itr != format_runs_.end());
text_item item;
item.start = position;
position = std::min(script_itr->end, std::min(format_itr->end, end));
item.end = position;
item.format = format_itr->data;
item.script = script_itr->data;
item.rtl = dir_run.data;
if (dir_run.data == UBIDI_LTR)
{
output_.push_back(item);
}
else
{
rtl_insertion_point = output_.insert(rtl_insertion_point, item);
}
if (script_itr->end == position) ++script_itr;
if (format_itr->end == position) ++format_itr;
}
}
}
} //ns mapnik

200
src/text/layout.cpp Normal file
View file

@ -0,0 +1,200 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2013 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
*
*****************************************************************************/
#include <mapnik/text/layout.hpp>
#include <mapnik/text/text_properties.hpp>
#include <mapnik/debug.hpp>
// ICU
#include <unicode/brkiter.h>
namespace mapnik
{
text_layout::text_layout(face_manager_freetype & font_manager, double scale_factor)
: font_manager_(font_manager),
scale_factor_(scale_factor),
itemizer_(),
width_map_(),
width_(0.0),
height_(0.0),
glyphs_count_(0),
lines_()
{
}
void text_layout::add_text(mapnik::value_unicode_string const& str, char_properties_ptr format)
{
itemizer_.add_text(str, format);
}
mapnik::value_unicode_string const& text_layout::text() const
{
return itemizer_.text();
}
void text_layout::layout(double wrap_width, unsigned text_ratio, bool wrap_before)
{
unsigned num_lines = itemizer_.num_lines();
for (unsigned i = 0; i < num_lines; ++i)
{
std::pair<unsigned, unsigned> line_limits = itemizer_.line(i);
text_line line(line_limits.first, line_limits.second);
break_line(line, wrap_width, text_ratio, wrap_before); //Break line if neccessary
}
}
/* In the Unicode string characters are always stored in logical order.
* This makes line breaking easy. One word is added to the current line at a time. Once the line is too long
* we either go back one step or inset the line break at the current position (depending on "wrap_before" setting).
* At the end everything that is left over is added as the final line. */
void text_layout::break_line(text_line & line, double wrap_width, unsigned text_ratio, bool wrap_before)
{
shape_text(line);
if (!wrap_width || line.width() < wrap_width)
{
add_line(line);
return;
}
if (text_ratio)
{
double wrap_at;
double string_width = line.width();
double string_height = line.line_height();
for (double i = 1.0; ((wrap_at = string_width/i)/(string_height*i)) > text_ratio && (string_width/i) > wrap_width; i += 1.0) ;
wrap_width = wrap_at;
}
mapnik::value_unicode_string const& text = itemizer_.text();
Locale locale; // TODO: Is the default constructor correct?
UErrorCode status = U_ZERO_ERROR;
BreakIterator *breakitr = BreakIterator::createLineInstance(locale, status);
// Not breaking the text if an error occurs is probably the best thing we can do.
// https://github.com/mapnik/mapnik/issues/2072
if (!U_SUCCESS(status))
{
add_line(line);
MAPNIK_LOG_ERROR(text_layout) << " could not create BreakIterator: " << u_errorName(status);
return;
}
breakitr->setText(text);
double current_line_length = 0;
int last_break_position = static_cast<int>(line.first_char());
for (unsigned i=line.first_char(); i < line.last_char(); ++i)
{
// TODO: character_spacing
std::map<unsigned, double>::const_iterator width_itr = width_map_.find(i);
if (width_itr != width_map_.end())
{
current_line_length += width_itr->second;
}
if (current_line_length <= wrap_width) continue;
/***********************************************/
int break_position = wrap_before ? breakitr->preceding(i) : breakitr->following(i);
// following() returns a break position after the last word. So DONE should only be returned
// when calling preceding.
if (break_position <= last_break_position || break_position == static_cast<int>(BreakIterator::DONE))
{
// A single word is longer than the maximum line width.
// Violate line width requirement and choose next break position
break_position = breakitr->following(i);
if (break_position == static_cast<int>(BreakIterator::DONE))
{
break_position = line.last_char();
MAPNIK_LOG_ERROR(text_layout) << "Unexpected result in break_line. Trying to recover...\n";
}
}
// Break iterator operates on the whole string, while we only look at one line. So we need to
// clamp break values.
if (break_position < static_cast<int>(line.first_char()))
{
break_position = line.first_char();
}
if (break_position > static_cast<int>(line.last_char()))
{
break_position = line.last_char();
}
text_line new_line(last_break_position, break_position);
clear_cluster_widths(last_break_position, break_position);
shape_text(new_line);
add_line(new_line);
last_break_position = break_position;
i = break_position - 1;
current_line_length = 0;
}
if (last_break_position == static_cast<int>(line.first_char()))
{
// No line breaks => no reshaping required
add_line(line);
}
else if (last_break_position != static_cast<int>(line.last_char()))
{
text_line new_line(last_break_position, line.last_char());
clear_cluster_widths(last_break_position, line.last_char());
shape_text(new_line);
add_line(new_line);
}
}
void text_layout::add_line(text_line & line)
{
if (lines_.empty())
{
line.set_first_line(true);
}
height_ += line.height();
glyphs_count_ += line.size();
width_ = std::max(width_, line.width());
lines_.push_back(line);
}
void text_layout::clear_cluster_widths(unsigned first, unsigned last)
{
for (unsigned i=first; i<last; ++i)
{
width_map_[i] = 0;
}
}
void text_layout::clear()
{
itemizer_.clear();
lines_.clear();
width_map_.clear();
width_ = 0.;
height_ = 0.;
}
void text_layout::shape_text(text_line & line)
{
shaper_type::shape_text(line, itemizer_, width_map_, font_manager_, scale_factor_);
}
} //ns mapnik

File diff suppressed because it is too large Load diff

View file

@ -19,9 +19,11 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
// mapnik
#include <mapnik/text/placements/base.hpp>
namespace mapnik {
text_placements::text_placements() : defaults()
{
}
@ -39,7 +41,7 @@ text_placement_info::text_placement_info(text_placements const* parent,
: properties(parent->defaults),
scale_factor(scale_factor_)
{
properties.format = std::make_shared<char_properties>(*(properties.format));
}
} //ns mapnik

View file

@ -27,7 +27,6 @@
//boost
#include <boost/property_tree/ptree.hpp>
namespace mapnik
{
@ -99,6 +98,8 @@ text_placements_ptr text_placements_list::from_xml(xml_node const &xml, fontset_
{
if (itr->is_text() || !itr->is("Placement")) continue;
text_symbolizer_properties &p = list->add();
p.format = std::make_shared<char_properties>(*(p.format)); //Make a deep copy
//TODO: This needs a real copy constructor for text_symbolizer_properties
p.from_xml(*itr, fontsets);
//TODO: if (strict_ &&
// !p.format.fontset.size())

View file

@ -30,6 +30,7 @@
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
#include <boost/property_tree/ptree.hpp>
namespace mapnik
@ -48,7 +49,7 @@ bool text_placement_info_simple::next()
if (state > 0)
{
if (state > parent_->text_sizes_.size()) return false;
properties.format.text_size = parent_->text_sizes_[state-1];
properties.format->text_size = parent_->text_sizes_[state-1];
}
if (!next_position_only()) {
state++;
@ -62,8 +63,8 @@ bool text_placement_info_simple::next()
bool text_placement_info_simple::next_position_only()
{
const position &pdisp = parent_->defaults.displacement;
position &displacement = properties.displacement;
pixel_position const& pdisp = parent_->defaults.displacement;
pixel_position &displacement = properties.displacement;
if (position_state >= parent_->direction_.size()) return false;
directions_t dir = parent_->direction_[position_state];
switch (dir) {
@ -71,28 +72,28 @@ bool text_placement_info_simple::next_position_only()
displacement = pdisp;
break;
case NORTH:
displacement = std::make_pair(0, -abs(pdisp.second));
displacement.set(0, -abs(pdisp.y));
break;
case EAST:
displacement = std::make_pair(abs(pdisp.first), 0);
displacement.set(abs(pdisp.x), 0);
break;
case SOUTH:
displacement = std::make_pair(0, abs(pdisp.second));
displacement.set(0, abs(pdisp.y));
break;
case WEST:
displacement = std::make_pair(-abs(pdisp.first), 0);
displacement.set(-abs(pdisp.x), 0);
break;
case NORTHEAST:
displacement = std::make_pair(abs(pdisp.first), -abs(pdisp.second));
displacement.set(abs(pdisp.x), -abs(pdisp.y));
break;
case SOUTHEAST:
displacement = std::make_pair(abs(pdisp.first), abs(pdisp.second));
displacement.set(abs(pdisp.x), abs(pdisp.y));
break;
case NORTHWEST:
displacement = std::make_pair(-abs(pdisp.first), -abs(pdisp.second));
displacement.set(-abs(pdisp.x), -abs(pdisp.y));
break;
case SOUTHWEST:
displacement = std::make_pair(-abs(pdisp.first), abs(pdisp.second));
displacement.set(-abs(pdisp.x), abs(pdisp.y));
break;
default:
MAPNIK_LOG_WARN(text_placements) << "Unknown placement";

View file

@ -1,104 +0,0 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2012 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
*
*****************************************************************************/
#include <mapnik/text/processed_text.hpp>
#include <mapnik/config_error.hpp>
#include <mapnik/font_engine_freetype.hpp>
#include <mapnik/value_types.hpp>
namespace mapnik
{
void processed_text::push_back(char_properties const& properties, mapnik::value_unicode_string const& text)
{
expr_list_.push_back(processed_expression(properties, text));
}
processed_text::expression_list::const_iterator processed_text::begin() const
{
return expr_list_.begin();
}
processed_text::expression_list::const_iterator processed_text::end() const
{
return expr_list_.end();
}
processed_text::processed_text(face_manager<freetype_engine> & font_manager, double scale_factor)
: font_manager_(font_manager), scale_factor_(scale_factor)
{
}
void processed_text::clear()
{
info_.clear();
expr_list_.clear();
}
string_info const& processed_text::get_string_info()
{
info_.clear(); //if this function is called twice invalid results are returned, so clear string_info first
expression_list::iterator itr = expr_list_.begin();
expression_list::iterator end = expr_list_.end();
for (; itr != end; ++itr)
{
char_properties const &p = itr->p;
face_set_ptr faces = font_manager_.get_face_set(p.face_name, p.fontset);
if (faces->size() == 0)
{
if (p.fontset && !p.fontset->get_name().empty())
{
if (p.fontset->size())
{
if (!p.face_name.empty())
{
throw config_error("Unable to find specified font face '" + p.face_name + "' in font set: '" + p.fontset->get_name() + "'");
}
else
{
throw config_error("No valid font face could be loaded for font set: '" + p.fontset->get_name() + "'");
}
}
else
{
throw config_error("Font set '" + p.fontset->get_name() + "' does not contain any Font face-name entries");
}
}
else if (!p.face_name.empty())
{
throw config_error("Unable to find specified font face '" + p.face_name + "'");
}
else
{
throw config_error("Both font set and face name are empty!");
}
}
faces->set_character_sizes(p.text_size * scale_factor_);
faces->get_string_info(info_, itr->str, &(itr->p));
info_.add_text(itr->str);
}
return info_;
}
} //ns mapnik

323
src/text/renderer.cpp Normal file
View file

@ -0,0 +1,323 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2013 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
*
*****************************************************************************/
// mapnik
#include <mapnik/text/renderer.hpp>
#include <mapnik/graphics.hpp>
#include <mapnik/grid/grid.hpp>
#include <mapnik/text/text_properties.hpp>
#include <mapnik/font_engine_freetype.hpp>
#include <mapnik/text/face.hpp>
namespace mapnik
{
text_renderer::text_renderer (halo_rasterizer_e rasterizer, composite_mode_e comp_op, double scale_factor, stroker_ptr stroker)
: rasterizer_(rasterizer),
comp_op_(comp_op),
scale_factor_(scale_factor),
glyphs_(),
stroker_(stroker)
{}
void text_renderer::prepare_glyphs(glyph_positions const& positions)
{
FT_Matrix matrix;
FT_Vector pen;
FT_Error error;
for (auto const& glyph_pos : positions)
{
glyph_info const& glyph = *(glyph_pos.glyph);
glyph.face->set_character_sizes(glyph.format->text_size * scale_factor_); //TODO: Optimize this?
matrix.xx = static_cast<FT_Fixed>( glyph_pos.rot.cos * 0x10000L);
matrix.xy = static_cast<FT_Fixed>(-glyph_pos.rot.sin * 0x10000L);
matrix.yx = static_cast<FT_Fixed>( glyph_pos.rot.sin * 0x10000L);
matrix.yy = static_cast<FT_Fixed>( glyph_pos.rot.cos * 0x10000L);
pixel_position pos = glyph_pos.pos + glyph.offset.rotate(glyph_pos.rot);
pen.x = static_cast<FT_Pos>(pos.x * 64);
pen.y = static_cast<FT_Pos>(pos.y * 64);
FT_Face face = glyph.face->get_face();
FT_Set_Transform(face, &matrix, &pen);
error = FT_Load_Glyph(face, glyph.glyph_index, FT_LOAD_NO_HINTING);
if (error) continue;
FT_Glyph image;
error = FT_Get_Glyph(face->glyph, &image);
if (error) continue;
glyphs_.emplace_back(image, glyph.format);
}
}
template <typename T>
void composite_bitmap(T & pixmap, FT_Bitmap *bitmap, unsigned rgba, int x, int y, double opacity, composite_mode_e comp_op)
{
int x_max=x+bitmap->width;
int y_max=y+bitmap->rows;
int i,p,j,q;
for (i=x,p=0;i<x_max;++i,++p)
{
for (j=y,q=0;j<y_max;++j,++q)
{
unsigned gray=bitmap->buffer[q*bitmap->width+p];
if (gray)
{
pixmap.composite_pixel(comp_op, i, j, rgba, gray, opacity);
}
}
}
}
template <typename T>
agg_text_renderer<T>::agg_text_renderer (pixmap_type & pixmap,
halo_rasterizer_e rasterizer,
composite_mode_e comp_op,
double scale_factor,
stroker_ptr stroker)
: text_renderer(rasterizer, comp_op, scale_factor, stroker), pixmap_(pixmap)
{}
template <typename T>
void agg_text_renderer<T>::render(glyph_positions const& pos)
{
glyphs_.clear();
prepare_glyphs(pos);
FT_Error error;
FT_Vector start;
int height = pixmap_.height();
pixel_position const& base_point = pos.get_base_point();
start.x = static_cast<FT_Pos>(base_point.x * (1 << 6));
start.y = static_cast<FT_Pos>((height - base_point.y) * (1 << 6));
//render halo
double halo_radius = 0;
char_properties_ptr format;
for (auto const& glyph : glyphs_)
{
if (glyph.properties)
{
format = glyph.properties;
// Settings have changed.
halo_radius = glyph.properties->halo_radius * scale_factor_;
}
// make sure we've got reasonable values.
if (halo_radius <= 0.0 || halo_radius > 1024.0) continue;
FT_Glyph g;
error = FT_Glyph_Copy(glyph.image, &g);
if (!error)
{
FT_Glyph_Transform(g,0,&start);
if (rasterizer_ == HALO_RASTERIZER_FULL)
{
stroker_->init(halo_radius);
FT_Glyph_Stroke(&g, stroker_->get(), 1);
error = FT_Glyph_To_Bitmap(&g, FT_RENDER_MODE_NORMAL, 0, 1);
if (!error)
{
FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(g);
composite_bitmap(pixmap_,
&bit->bitmap,
format->halo_fill.rgba(),
bit->left,
height - bit->top,
format->text_opacity,
comp_op_);
}
}
else
{
error = FT_Glyph_To_Bitmap(&g, FT_RENDER_MODE_NORMAL, 0, 1);
if (!error)
{
FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(g);
render_halo(&bit->bitmap,
format->halo_fill.rgba(),
bit->left,
height - bit->top,
halo_radius,
format->text_opacity,
comp_op_);
}
}
}
FT_Done_Glyph(g);
}
// render actual text
for (auto & glyph : glyphs_)
{
if (glyph.properties)
{
format = glyph.properties;
}
FT_Glyph_Transform(glyph.image, 0, &start);
error = FT_Glyph_To_Bitmap(&glyph.image ,FT_RENDER_MODE_NORMAL,0,1);
if (!error)
{
FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(glyph.image);
composite_bitmap(pixmap_,
&bit->bitmap,
format->fill.rgba(),
bit->left,
height - bit->top,
format->text_opacity,
comp_op_);
}
}
}
template <typename T>
void grid_text_renderer<T>::render(glyph_positions const& pos, value_integer feature_id)
{
glyphs_.clear();
prepare_glyphs(pos);
FT_Error error;
FT_Vector start;
unsigned height = pixmap_.height();
pixel_position const& base_point = pos.get_base_point();
start.x = static_cast<FT_Pos>(base_point.x * (1 << 6));
start.y = static_cast<FT_Pos>((height - base_point.y) * (1 << 6));
// now render transformed glyphs
double halo_radius = 0.0;
for (auto & glyph : glyphs_)
{
if (glyph.properties)
{
halo_radius = glyph.properties->halo_radius * scale_factor_;
}
FT_Glyph_Transform(glyph.image, 0, &start);
error = FT_Glyph_To_Bitmap(&glyph.image, FT_RENDER_MODE_NORMAL, 0, 1);
if (!error)
{
FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(glyph.image);
render_halo_id(&bit->bitmap,
feature_id,
bit->left,
height - bit->top,
static_cast<int>(halo_radius));
}
}
}
template <typename T>
void agg_text_renderer<T>::render_halo(FT_Bitmap *bitmap,
unsigned rgba,
int x1,
int y1,
double halo_radius,
double opacity,
composite_mode_e comp_op)
{
int width = bitmap->width;
int height = bitmap->rows;
int x, y;
if (halo_radius < 1.0)
{
for (x=0; x < width; x++)
{
for (y=0; y < height; y++)
{
int gray = bitmap->buffer[y*bitmap->width+x];
if (gray)
{
pixmap_.composite_pixel(comp_op, x+x1-1, y+y1-1, rgba, gray*halo_radius*halo_radius, opacity);
pixmap_.composite_pixel(comp_op, x+x1, y+y1-1, rgba, gray*halo_radius, opacity);
pixmap_.composite_pixel(comp_op, x+x1+1, y+y1-1, rgba, gray*halo_radius*halo_radius, opacity);
pixmap_.composite_pixel(comp_op, x+x1-1, y+y1, rgba, gray*halo_radius, opacity);
pixmap_.composite_pixel(comp_op, x+x1, y+y1, rgba, gray, opacity);
pixmap_.composite_pixel(comp_op, x+x1+1, y+y1, rgba, gray*halo_radius, opacity);
pixmap_.composite_pixel(comp_op, x+x1-1, y+y1+1, rgba, gray*halo_radius*halo_radius, opacity);
pixmap_.composite_pixel(comp_op, x+x1, y+y1+1, rgba, gray*halo_radius, opacity);
pixmap_.composite_pixel(comp_op, x+x1+1, y+y1+1, rgba, gray*halo_radius*halo_radius, opacity);
}
}
}
}
else
{
for (x=0; x < width; x++)
{
for (y=0; y < height; y++)
{
int gray = bitmap->buffer[y*bitmap->width+x];
if (gray)
{
for (int n=-halo_radius; n <=halo_radius; ++n)
for (int m=-halo_radius; m <= halo_radius; ++m)
pixmap_.composite_pixel(comp_op, x+x1+m, y+y1+n, rgba, gray, opacity);
}
}
}
}
}
template <typename T>
void grid_text_renderer<T>::render_halo_id(
FT_Bitmap *bitmap,
mapnik::value_integer feature_id,
int x1,
int y1,
int halo_radius)
{
int width = bitmap->width;
int height = bitmap->rows;
int x, y;
for (x=0; x < width; x++)
{
for (y=0; y < height; y++)
{
int gray = bitmap->buffer[y*bitmap->width+x];
if (gray)
{
for (int n=-halo_radius; n <=halo_radius; ++n)
for (int m=-halo_radius; m <= halo_radius; ++m)
pixmap_.setPixel(x+x1+m,y+y1+n,feature_id);
}
}
}
}
template <typename T>
grid_text_renderer<T>::grid_text_renderer(pixmap_type &pixmap,
composite_mode_e comp_op,
double scale_factor) :
text_renderer(HALO_RASTERIZER_FAST, comp_op, scale_factor), pixmap_(pixmap)
{
}
template class agg_text_renderer<image_32>;
template class grid_text_renderer<grid>;
}

205
src/text/scrptrun.cpp Normal file
View file

@ -0,0 +1,205 @@
/*
*******************************************************************************
*
* Copyright (C) 1999-2001, International Business Machines
* Corporation and others. All Rights Reserved.
*
*******************************************************************************
* file name: scrptrun.cpp
*
* created on: 10/17/2001
* created by: Eric R. Mader
*
* NOTE: This file is copied from ICU.
* http://source.icu-project.org/repos/icu/icu/trunk/license.html
*/
#include <unicode/utypes.h>
#include <unicode/uscript.h>
#include <mapnik/text/scrptrun.hpp>
#define ARRAY_SIZE(array) (sizeof array / sizeof array[0])
const char ScriptRun::fgClassID=0;
UChar32 ScriptRun::pairedChars[] = {
0x0028, 0x0029, // ascii paired punctuation
0x003c, 0x003e,
0x005b, 0x005d,
0x007b, 0x007d,
0x00ab, 0x00bb, // guillemets
0x2018, 0x2019, // general punctuation
0x201c, 0x201d,
0x2039, 0x203a,
0x3008, 0x3009, // chinese paired punctuation
0x300a, 0x300b,
0x300c, 0x300d,
0x300e, 0x300f,
0x3010, 0x3011,
0x3014, 0x3015,
0x3016, 0x3017,
0x3018, 0x3019,
0x301a, 0x301b
};
const int32_t ScriptRun::pairedCharCount = ARRAY_SIZE(pairedChars);
const int32_t ScriptRun::pairedCharPower = 1 << highBit(pairedCharCount);
const int32_t ScriptRun::pairedCharExtra = pairedCharCount - pairedCharPower;
int8_t ScriptRun::highBit(int32_t value)
{
if (value <= 0) {
return -32;
}
int8_t bit = 0;
if (value >= 1 << 16) {
value >>= 16;
bit += 16;
}
if (value >= 1 << 8) {
value >>= 8;
bit += 8;
}
if (value >= 1 << 4) {
value >>= 4;
bit += 4;
}
if (value >= 1 << 2) {
value >>= 2;
bit += 2;
}
if (value >= 1 << 1) {
value >>= 1;
bit += 1;
}
return bit;
}
int32_t ScriptRun::getPairIndex(UChar32 ch)
{
int32_t probe = pairedCharPower;
int32_t index = 0;
if (ch >= pairedChars[pairedCharExtra]) {
index = pairedCharExtra;
}
while (probe > (1 << 0)) {
probe >>= 1;
if (ch >= pairedChars[index + probe]) {
index += probe;
}
}
if (pairedChars[index] != ch) {
index = -1;
}
return index;
}
UBool ScriptRun::sameScript(int32_t scriptOne, int32_t scriptTwo)
{
return scriptOne <= USCRIPT_INHERITED || scriptTwo <= USCRIPT_INHERITED || scriptOne == scriptTwo;
}
UBool ScriptRun::next()
{
int32_t startSP = parenSP; // used to find the first new open character
UErrorCode error = U_ZERO_ERROR;
// if we've fallen off the end of the text, we're done
if (scriptEnd >= charLimit) {
return false;
}
scriptCode = USCRIPT_COMMON;
for (scriptStart = scriptEnd; scriptEnd < charLimit; scriptEnd += 1) {
UChar high = charArray[scriptEnd];
UChar32 ch = high;
// if the character is a high surrogate and it's not the last one
// in the text, see if it's followed by a low surrogate
if (high >= 0xD800 && high <= 0xDBFF && scriptEnd < charLimit - 1)
{
UChar low = charArray[scriptEnd + 1];
// if it is followed by a low surrogate,
// consume it and form the full character
if (low >= 0xDC00 && low <= 0xDFFF) {
ch = (high - 0xD800) * 0x0400 + low - 0xDC00 + 0x10000;
scriptEnd += 1;
}
}
UScriptCode sc = uscript_getScript(ch, &error);
int32_t pairIndex = getPairIndex(ch);
// Paired character handling:
//
// if it's an open character, push it onto the stack.
// if it's a close character, find the matching open on the
// stack, and use that script code. Any non-matching open
// characters above it on the stack will be poped.
if (pairIndex >= 0) {
if ((pairIndex & 1) == 0) {
parenStack[++parenSP].pairIndex = pairIndex;
parenStack[parenSP].scriptCode = scriptCode;
} else if (parenSP >= 0) {
int32_t pi = pairIndex & ~1;
while (parenSP >= 0 && parenStack[parenSP].pairIndex != pi) {
parenSP -= 1;
}
if (parenSP < startSP) {
startSP = parenSP;
}
if (parenSP >= 0) {
sc = parenStack[parenSP].scriptCode;
}
}
}
if (sameScript(scriptCode, sc)) {
if (scriptCode <= USCRIPT_INHERITED && sc > USCRIPT_INHERITED) {
scriptCode = sc;
// now that we have a final script code, fix any open
// characters we pushed before we knew the script code.
while (startSP < parenSP) {
parenStack[++startSP].scriptCode = scriptCode;
}
}
// if this character is a close paired character,
// pop it from the stack
if (pairIndex >= 0 && (pairIndex & 1) != 0 && parenSP >= 0) {
parenSP -= 1;
startSP -= 1;
}
} else {
// if the run broke on a surrogate pair,
// end it before the high surrogate
if (ch >= 0x10000) {
scriptEnd -= 1;
}
break;
}
}
return true;
}

View file

@ -2,7 +2,7 @@
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2011 Artem Pavlenko
* Copyright (C) 2013 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -21,120 +21,58 @@
*****************************************************************************/
// mapnik
#include <mapnik/debug.hpp>
#include <mapnik/value.hpp>
#include <mapnik/feature.hpp>
#include <mapnik/geom_util.hpp>
#include <mapnik/parse_path.hpp>
#include <mapnik/text/symbolizer_helpers.hpp>
#include <mapnik/label_collision_detector.hpp>
#include <mapnik/text/placement_finder.hpp>
#include <mapnik/font_engine_freetype.hpp>
#include <mapnik/text/layout.hpp>
#include <mapnik/geom_util.hpp>
#include <mapnik/marker.hpp>
#include <mapnik/expression_evaluator.hpp>
#include <mapnik/pixel_position.hpp>
#include <mapnik/parse_path.hpp>
#include <mapnik/debug.hpp>
// agg
//agg
#include "agg_conv_clip_polyline.h"
namespace mapnik {
template <typename FaceManagerT, typename DetectorT>
text_symbolizer_helper<FaceManagerT, DetectorT>::text_symbolizer_helper(text_symbolizer const& sym,
feature_impl const& feature,
proj_transform const& prj_trans,
unsigned width,
unsigned height,
double scale_factor,
CoordTransform const& t,
FaceManagerT &font_manager,
DetectorT &detector,
box2d<double> const& query_extent)
: sym_(sym),
feature_(feature),
prj_trans_(prj_trans),
t_(t),
font_manager_(font_manager),
detector_(detector),
dims_(0, 0, width, height),
query_extent_(query_extent),
text_(font_manager, scale_factor),
angle_(0.0),
placement_valid_(false),
points_on_line_(false),
finder_()
{
initialize_geometries();
if (!geometries_to_process_.size()) return;
placement_ = sym_.get_placement_options()->get_placement_info(scale_factor);
next_placement();
initialize_points();
}
template <typename FaceManagerT, typename DetectorT>
text_symbolizer_helper<FaceManagerT, DetectorT>::~text_symbolizer_helper()
{}
template <typename FaceManagerT, typename DetectorT>
bool text_symbolizer_helper<FaceManagerT, DetectorT>::next()
text_symbolizer_helper::text_symbolizer_helper(const text_symbolizer &sym, const feature_impl &feature, const proj_transform &prj_trans, unsigned width, unsigned height, double scale_factor, const CoordTransform &t, FaceManagerT &font_manager, DetectorT &detector, const box2d<double> &query_extent)
: sym_(sym),
feature_(feature),
prj_trans_(prj_trans),
t_(t),
dims_(0, 0, width, height),
query_extent_(query_extent),
points_on_line_(false),
placement_(sym_.get_placement_options()->get_placement_info(scale_factor)),
finder_(feature, detector, dims_, placement_, font_manager, scale_factor)
{
initialize_geometries();
if (!geometries_to_process_.size()) return;
finder_.next_position();
initialize_points();
}
placements_list const& text_symbolizer_helper::get()
{
if (!placement_valid_) return false;
if (point_placement_)
return next_point_placement();
else if (sym_.clip())
return next_line_placement_clipped();
else
return next_line_placement();
}
template <typename FaceManagerT, typename DetectorT>
bool text_symbolizer_helper<FaceManagerT, DetectorT>::next_line_placement()
{
while (!geometries_to_process_.empty())
{
if (geo_itr_ == geometries_to_process_.end())
{
//Just processed the last geometry. Try next placement.
if (!next_placement()) return false; //No more placements
//Start again from begin of list
geo_itr_ = geometries_to_process_.begin();
continue; //Reexecute size check
}
typedef coord_transform<CoordTransform,geometry_type> path_type;
path_type path(t_, **geo_itr_, prj_trans_);
finder_->clear_placements();
if (points_on_line_) {
finder_->find_point_placements(path);
} else {
finder_->find_line_placements(path);
}
if (!finder_->get_results().empty())
{
//Found a placement
if (points_on_line_)
{
finder_->update_detector();
}
geo_itr_ = geometries_to_process_.erase(geo_itr_);
return true;
}
//No placement for this geometry. Keep it in geometries_to_process_ for next try.
geo_itr_++;
while (next_point_placement());
}
return false;
else
{
while (next_line_placement());
}
return finder_.placements();
}
template <typename FaceManagerT, typename DetectorT>
bool text_symbolizer_helper<FaceManagerT, DetectorT>::next_line_placement_clipped()
bool text_symbolizer_helper::next_line_placement()
{
while (!geometries_to_process_.empty())
{
if (geo_itr_ == geometries_to_process_.end())
{
//Just processed the last geometry. Try next placement.
if (!next_placement()) return false; //No more placements
if (!finder_.next_position()) return false; //No more placements
//Start again from begin of list
geo_itr_ = geometries_to_process_.begin();
continue; //Reexecute size check
@ -142,23 +80,15 @@ bool text_symbolizer_helper<FaceManagerT, DetectorT>::next_line_placement_clippe
typedef agg::conv_clip_polyline<geometry_type> clipped_geometry_type;
typedef coord_transform<CoordTransform,clipped_geometry_type> path_type;
clipped_geometry_type clipped(**geo_itr_);
clipped.clip_box(query_extent_.minx(),query_extent_.miny(),query_extent_.maxx(),query_extent_.maxy());
path_type path(t_, clipped, prj_trans_);
finder_->clear_placements();
if (points_on_line_) {
finder_->find_point_placements(path);
} else {
finder_->find_line_placements(path);
}
if (!finder_->get_results().empty())
clipped_geometry_type clipped(**geo_itr_);
clipped.clip_box(query_extent_.minx(), query_extent_.miny(),
query_extent_.maxx(), query_extent_.maxy());
path_type path(t_, clipped, prj_trans_);
bool success = finder_.find_line_placements(path, points_on_line_);
if (success)
{
//Found a placement
if (points_on_line_)
{
finder_->update_detector();
}
geo_itr_ = geometries_to_process_.erase(geo_itr_);
return true;
}
@ -168,26 +98,22 @@ bool text_symbolizer_helper<FaceManagerT, DetectorT>::next_line_placement_clippe
return false;
}
template <typename FaceManagerT, typename DetectorT>
bool text_symbolizer_helper<FaceManagerT, DetectorT>::next_point_placement()
bool text_symbolizer_helper::next_point_placement()
{
while (!points_.empty())
{
if (point_itr_ == points_.end())
{
//Just processed the last point. Try next placement.
if (!next_placement()) return false; //No more placements
if (!finder_.next_position()) return false; //No more placements
//Start again from begin of list
point_itr_ = points_.begin();
continue; //Reexecute size check
}
finder_->clear_placements();
finder_->find_point_placement(point_itr_->first, point_itr_->second, angle_);
if (!finder_->get_results().empty())
if (finder_.find_point_placement(*point_itr_))
{
//Found a placement
point_itr_ = points_.erase(point_itr_);
finder_->update_detector();
return true;
}
//No placement for this point. Keep it in points_ for next try.
@ -207,19 +133,17 @@ struct largest_bbox_first
};
template <typename FaceManagerT, typename DetectorT>
void text_symbolizer_helper<FaceManagerT, DetectorT>::initialize_geometries()
void text_symbolizer_helper::initialize_geometries()
{
bool largest_box_only = false;
std::size_t num_geom = feature_.num_geometries();
for (std::size_t i = 0; i < num_geom; ++i)
for (std::size_t i=0; i<num_geom; ++i)
{
geometry_type const& geom = feature_.get_geometry(i);
// don't bother with empty geometries
if (geom.size() == 0) continue;
geometry_type::types type = geom.type();
mapnik::geometry_type::types type = geom.type();
if (type == geometry_type::types::Polygon)
{
largest_box_only = sym_.largest_bbox_only();
@ -245,8 +169,7 @@ void text_symbolizer_helper<FaceManagerT, DetectorT>::initialize_geometries()
geo_itr_ = geometries_to_process_.begin();
}
template <typename FaceManagerT, typename DetectorT>
void text_symbolizer_helper<FaceManagerT, DetectorT>::initialize_points()
void text_symbolizer_helper::initialize_points()
{
label_placement_enum how_placed = placement_->properties.label_placement;
if (how_placed == LINE_PLACEMENT)
@ -276,7 +199,7 @@ void text_symbolizer_helper<FaceManagerT, DetectorT>::initialize_points()
geom.vertex(&label_x, &label_y);
prj_trans_.backward(label_x, label_y, z);
t_.forward(&label_x, &label_y);
points_.push_back(std::make_pair(label_x, label_y));
points_.push_back(pixel_position(label_x, label_y));
}
}
else
@ -304,205 +227,83 @@ void text_symbolizer_helper<FaceManagerT, DetectorT>::initialize_points()
{
prj_trans_.backward(label_x, label_y, z);
t_.forward(&label_x, &label_y);
points_.push_back(std::make_pair(label_x, label_y));
points_.push_back(pixel_position(label_x, label_y));
}
}
}
point_itr_ = points_.begin();
}
template <typename FaceManagerT, typename DetectorT>
bool text_symbolizer_helper<FaceManagerT, DetectorT>::next_placement()
{
if (!placement_->next()) {
placement_valid_ = false;
return false;
}
placement_->properties.process(text_, feature_);
if (placement_->properties.orientation)
{
// https://github.com/mapnik/mapnik/issues/1352
mapnik::evaluate<feature_impl, value_type> evaluator(feature_);
angle_ = boost::apply_visitor(
evaluator,
*(placement_->properties.orientation)).to_double();
} else {
angle_ = 0.0;
}
finder_.reset(new placement_finder<DetectorT>(*placement_,
text_.get_string_info(),
detector_, dims_));
placement_valid_ = true;
return true;
}
template <typename FaceManagerT, typename DetectorT>
placements_type const& text_symbolizer_helper<FaceManagerT, DetectorT>::placements() const
{
return finder_->get_results();
}
/*****************************************************************************/
template <typename FaceManagerT, typename DetectorT>
bool shield_symbolizer_helper<FaceManagerT, DetectorT>::next()
text_symbolizer_helper::text_symbolizer_helper(
const shield_symbolizer &sym, const feature_impl &feature,
const proj_transform &prj_trans,
unsigned width, unsigned height, double scale_factor,
const CoordTransform &t, FaceManagerT &font_manager,
DetectorT &detector, const box2d<double> &query_extent)
: sym_(sym),
feature_(feature),
prj_trans_(prj_trans),
t_(t),
dims_(0, 0, width, height),
query_extent_(query_extent),
points_on_line_(true),
placement_(sym_.get_placement_options()->get_placement_info(scale_factor)),
finder_(feature, detector, dims_, placement_, font_manager, scale_factor)
{
if (!placement_valid_ || !marker_) return false;
if (point_placement_)
return next_point_placement();
else
return next_line_placement();
}
template <typename FaceManagerT, typename DetectorT>
bool shield_symbolizer_helper<FaceManagerT, DetectorT>::next_point_placement()
{
position const& shield_pos = sym_.get_shield_displacement();
while (!points_.empty())
{
if (point_itr_ == points_.end())
{
//Just processed the last point. Try next placement.
if (!next_placement()) return false; //No more placements
//Start again from begin of list
point_itr_ = points_.begin();
continue; //Reexecute size check
}
position const& text_disp = placement_->properties.displacement;
double label_x = point_itr_->first + shield_pos.first;
double label_y = point_itr_->second + shield_pos.second;
finder_->clear_placements();
finder_->find_point_placement(label_x, label_y, angle_);
if (finder_->get_results().empty())
{
//No placement for this point. Keep it in points_ for next try.
point_itr_++;
continue;
}
//Found a label placement but not necessarily also a marker placement
// check to see if image overlaps anything too, there is only ever 1 placement found for points and verticies
if (!sym_.get_unlock_image())
{
// center image at text center position
// remove displacement from image label
placements_type const& p = finder_->get_results();
double lx = p[0].center.x - text_disp.first;
double ly = p[0].center.y - text_disp.second;
marker_x_ = lx - 0.5 * marker_w_;
marker_y_ = ly - 0.5 * marker_h_;
marker_ext_.re_center(lx, ly);
}
else
{ // center image at reference location
marker_x_ = label_x - 0.5 * marker_w_;
marker_y_ = label_y - 0.5 * marker_h_;
marker_ext_.re_center(label_x, label_y);
}
if (placement_->properties.allow_overlap || detector_.has_placement(marker_ext_))
{
detector_.insert(marker_ext_);
finder_->update_detector();
point_itr_ = points_.erase(point_itr_);
return true;
}
//No placement found. Try again
point_itr_++;
}
return false;
initialize_geometries();
if (!geometries_to_process_.size()) return;
finder_.next_position();
initialize_points();
init_marker();
}
template <typename FaceManagerT, typename DetectorT>
bool shield_symbolizer_helper<FaceManagerT, DetectorT>::next_line_placement()
void text_symbolizer_helper::init_marker()
{
position const& pos = placement_->properties.displacement;
finder_->additional_boxes().clear();
//Markers are automatically centered
finder_->additional_boxes().push_back(
box2d<double>(-0.5 * marker_ext_.width() - pos.first,
-0.5 * marker_ext_.height() - pos.second,
0.5 * marker_ext_.width() - pos.first,
0.5 * marker_ext_.height() - pos.second));
if ( sym_.clip())
return text_symbolizer_helper<FaceManagerT, DetectorT>::next_line_placement_clipped();
else
return text_symbolizer_helper<FaceManagerT, DetectorT>::next_line_placement();
}
template <typename FaceManagerT, typename DetectorT>
void shield_symbolizer_helper<FaceManagerT, DetectorT>::init_marker()
{
std::string filename = path_processor_type::evaluate(*sym_.get_filename(), this->feature_);
evaluate_transform(image_transform_, feature_, sym_.get_image_transform());
marker_.reset();
shield_symbolizer const& sym = static_cast<shield_symbolizer const&>(sym_);
std::string filename = path_processor_type::evaluate(*sym.get_filename(), feature_);
agg::trans_affine trans;
evaluate_transform(trans, feature_, sym.get_image_transform());
boost::optional<marker_ptr> opt_marker; //TODO: Why boost::optional?
if (!filename.empty())
{
marker_ = marker_cache::instance().find(filename, true);
opt_marker = marker_cache::instance().find(filename, true);
}
if (!marker_) {
marker_w_ = 0;
marker_h_ = 0;
marker_ext_.init(0, 0, 0, 0);
return;
}
marker_w_ = (*marker_)->width();
marker_h_ = (*marker_)->height();
double px0 = - 0.5 * marker_w_;
double py0 = - 0.5 * marker_h_;
double px1 = 0.5 * marker_w_;
double py1 = 0.5 * marker_h_;
marker_ptr m;
if (opt_marker) m = *opt_marker;
if (!m) return;
double width = m->width();
double height = m->height();
double px0 = - 0.5 * width;
double py0 = - 0.5 * height;
double px1 = 0.5 * width;
double py1 = 0.5 * height;
double px2 = px1;
double py2 = py0;
double px3 = px0;
double py3 = py1;
image_transform_.transform(&px0,&py0);
image_transform_.transform(&px1,&py1);
image_transform_.transform(&px2,&py2);
image_transform_.transform(&px3,&py3);
marker_ext_.init(px0, py0, px1, py1);
marker_ext_.expand_to_include(px2, py2);
marker_ext_.expand_to_include(px3, py3);
trans.transform(&px0, &py0);
trans.transform(&px1, &py1);
trans.transform(&px2, &py2);
trans.transform(&px3, &py3);
box2d<double> bbox(px0, py0, px1, py1);
bbox.expand_to_include(px2, py2);
bbox.expand_to_include(px3, py3);
finder_.set_marker(std::make_shared<marker_info>(m, trans), bbox, sym.get_unlock_image(), sym.get_shield_displacement());
}
template <typename FaceManagerT, typename DetectorT>
pixel_position shield_symbolizer_helper<FaceManagerT, DetectorT>::get_marker_position(text_path const& p)
{
position const& pos = placement_->properties.displacement;
if (placement_->properties.label_placement == LINE_PLACEMENT) {
double lx = p.center.x - pos.first;
double ly = p.center.y - pos.second;
double px = lx - 0.5*marker_w_;
double py = ly - 0.5*marker_h_;
marker_ext_.re_center(lx, ly);
//label is added to detector by get_line_placement(), but marker isn't
detector_.insert(marker_ext_);
return pixel_position(px, py);
} else {
//collision_detector is already updated for point placement in get_point_placement()
return pixel_position(marker_x_, marker_y_);
}
}
template text_symbolizer_helper::text_symbolizer_helper(const text_symbolizer &sym, const feature_impl &feature,
const proj_transform &prj_trans,
unsigned width, unsigned height, double scale_factor,
const CoordTransform &t, face_manager<freetype_engine> &font_manager,
label_collision_detector4 &detector, const box2d<double> &query_extent);
template <typename FaceManagerT, typename DetectorT>
marker& shield_symbolizer_helper<FaceManagerT, DetectorT>::get_marker() const
{
return **marker_;
}
template <typename FaceManagerT, typename DetectorT>
agg::trans_affine const& shield_symbolizer_helper<FaceManagerT, DetectorT>::get_image_transform() const
{
return image_transform_;
}
template class text_symbolizer_helper<face_manager<freetype_engine>, label_collision_detector4>;
template class shield_symbolizer_helper<face_manager<freetype_engine>, label_collision_detector4>;
template text_symbolizer_helper::text_symbolizer_helper(const shield_symbolizer &sym, const feature_impl &feature,
const proj_transform &prj_trans,
unsigned width, unsigned height, double scale_factor,
const CoordTransform &t, face_manager<freetype_engine> &font_manager,
label_collision_detector4 &detector, const box2d<double> &query_extent);
} //namespace

99
src/text/text_line.cpp Normal file
View file

@ -0,0 +1,99 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2013 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
*
*****************************************************************************/
#include <mapnik/text/text_line.hpp>
namespace mapnik {
text_line::text_line(unsigned first_char, unsigned last_char)
: glyphs_(),
line_height_(0.0),
max_char_height_(0.0),
width_(0.0),
first_char_(first_char),
last_char_(last_char),
first_line_(false)
{}
void text_line::add_glyph(glyph_info const& glyph, double scale_factor_)
{
line_height_ = std::max(line_height_, glyph.line_height + glyph.format->line_spacing);
if (glyphs_.empty())
{
width_ = glyph.width;
}
else if (glyph.width)
{
// Only add character spacing if the character is not a zero-width part of a cluster.
width_ += glyph.width + glyphs_.back().format->character_spacing * scale_factor_;
}
glyphs_.push_back(glyph);
}
void text_line::reserve(glyph_vector::size_type length)
{
glyphs_.reserve(length);
}
text_line::const_iterator text_line::begin() const
{
return glyphs_.begin();
}
text_line::const_iterator text_line::end() const
{
return glyphs_.end();
}
double text_line::height() const
{
if (first_line_) return max_char_height_;
return line_height_;
}
void text_line::update_max_char_height(double max_char_height)
{
max_char_height_ = std::max(max_char_height_, max_char_height);
}
void text_line::set_first_line(bool first_line)
{
first_line_ = first_line;
}
unsigned text_line::first_char() const
{
return first_char_;
}
unsigned text_line::last_char() const
{
return last_char_;
}
unsigned text_line::size() const
{
return glyphs_.size();
}
} // end namespace mapnik

View file

@ -2,7 +2,7 @@
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2011 Artem Pavlenko
* Copyright (C) 2013 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -20,10 +20,10 @@
*
*****************************************************************************/
// mapnik
#include <mapnik/text/text_properties.hpp>
#include <mapnik/text/layout.hpp>
#include <mapnik/debug.hpp>
#include <mapnik/feature.hpp>
#include <mapnik/text/text_properties.hpp>
#include <mapnik/text/processed_text.hpp>
#include <mapnik/ptree_helpers.hpp>
#include <mapnik/expression_string.hpp>
#include <mapnik/text/formatting/text.hpp>
@ -31,12 +31,71 @@
#include <mapnik/config_error.hpp>
// boost
#include <boost/property_tree/ptree.hpp>
namespace mapnik
{
using boost::optional;
static const char * label_placement_strings[] = {
"point",
"line",
"vertex",
"interior",
""
};
IMPLEMENT_ENUM(label_placement_e, label_placement_strings)
static const char * vertical_alignment_strings[] = {
"top",
"middle",
"bottom",
"auto",
""
};
IMPLEMENT_ENUM(vertical_alignment_e, vertical_alignment_strings)
static const char * horizontal_alignment_strings[] = {
"left",
"middle",
"right",
"auto",
""
};
IMPLEMENT_ENUM(horizontal_alignment_e, horizontal_alignment_strings)
static const char * justify_alignment_strings[] = {
"left",
"center", // not 'middle' in order to match CSS
"right",
"auto",
""
};
IMPLEMENT_ENUM(justify_alignment_e, justify_alignment_strings)
static const char * text_transform_strings[] = {
"none",
"uppercase",
"lowercase",
"capitalize",
""
};
IMPLEMENT_ENUM(text_transform_e, text_transform_strings)
static const char * text_upright_strings[] = {
"auto",
"left",
"right",
"left_only",
"right_only",
""
};
IMPLEMENT_ENUM(text_upright_e, text_upright_strings)
text_symbolizer_properties::text_symbolizer_properties() :
orientation(),
displacement(0.0,0.0),
@ -56,13 +115,16 @@ text_symbolizer_properties::text_symbolizer_properties() :
largest_bbox_only(true),
text_ratio(0.0),
wrap_width(0.0),
format(),
wrap_before(false),
rotate_displacement(false),
upright(UPRIGHT_AUTO),
format(std::make_shared<char_properties>()),
tree_()
{
}
void text_symbolizer_properties::process(processed_text &output, feature_impl const& feature) const
void text_symbolizer_properties::process(text_layout &output, feature_impl const& feature) const
{
output.clear();
if (tree_) {
@ -92,6 +154,8 @@ void text_symbolizer_properties::from_xml(xml_node const &sym, fontset_map const
if (text_ratio_) text_ratio = *text_ratio_;
optional<double> wrap_width_ = sym.get_opt_attr<double>("wrap-width");
if (wrap_width_) wrap_width = *wrap_width_;
optional<boolean> wrap_before_ = sym.get_opt_attr<boolean>("wrap-before");
if (wrap_before_) wrap_before = *wrap_before_;
optional<double> label_position_tolerance_ = sym.get_opt_attr<double>("label-position-tolerance");
if (label_position_tolerance_) label_position_tolerance = *label_position_tolerance_;
optional<double> spacing_ = sym.get_opt_attr<double>("spacing");
@ -119,10 +183,14 @@ void text_symbolizer_properties::from_xml(xml_node const &sym, fontset_map const
if (jalign_) jalign = *jalign_;
optional<expression_ptr> orientation_ = sym.get_opt_attr<expression_ptr>("orientation");
if (orientation_) orientation = *orientation_;
optional<boolean> rotate_displacement_ = sym.get_opt_attr<boolean>("rotate-displacement");
if (rotate_displacement_) rotate_displacement = *rotate_displacement_;
optional<text_upright_e> upright_ = sym.get_opt_attr<text_upright_e>("upright");
if (upright_) upright = *upright_;
optional<double> dx = sym.get_opt_attr<double>("dx");
if (dx) displacement.first = *dx;
if (dx) displacement.x = *dx;
optional<double> dy = sym.get_opt_attr<double>("dy");
if (dy) displacement.second = *dy;
if (dy) displacement.y = *dy;
optional<double> max_char_angle_delta_ = sym.get_opt_attr<double>("max-char-angle-delta");
if (max_char_angle_delta_) max_char_angle_delta=(*max_char_angle_delta_)*(M_PI/180);
@ -134,7 +202,7 @@ void text_symbolizer_properties::from_xml(xml_node const &sym, fontset_map const
set_old_style_expression(*name_);
}
format.from_xml(sym, fontsets);
format->from_xml(sym, fontsets);
formatting::node_ptr n(formatting::node::from_xml(sym));
if (n) set_format_tree(n);
}
@ -151,13 +219,13 @@ void text_symbolizer_properties::to_xml(boost::property_tree::ptree &node,
}
}
if (displacement.first != dfl.displacement.first || explicit_defaults)
if (displacement.x != dfl.displacement.x || explicit_defaults)
{
set_attr(node, "dx", displacement.first);
set_attr(node, "dx", displacement.x);
}
if (displacement.second != dfl.displacement.second || explicit_defaults)
if (displacement.y != dfl.displacement.y || explicit_defaults)
{
set_attr(node, "dy", displacement.second);
set_attr(node, "dy", displacement.y);
}
if (label_placement != dfl.label_placement || explicit_defaults)
{
@ -175,6 +243,10 @@ void text_symbolizer_properties::to_xml(boost::property_tree::ptree &node,
{
set_attr(node, "wrap-width", wrap_width);
}
if (wrap_before != dfl.wrap_before || explicit_defaults)
{
set_attr(node, "wrap-before", wrap_before);
}
if (label_position_tolerance != dfl.label_position_tolerance || explicit_defaults)
{
set_attr(node, "label-position-tolerance", label_position_tolerance);
@ -223,7 +295,15 @@ void text_symbolizer_properties::to_xml(boost::property_tree::ptree &node,
{
set_attr(node, "vertical-alignment", valign);
}
format.to_xml(node, explicit_defaults, dfl.format);
if (rotate_displacement != dfl.rotate_displacement || explicit_defaults)
{
set_attr(node, "rotate-displacement", rotate_displacement);
}
if (upright != dfl.upright || explicit_defaults)
{
set_attr(node, "upright", upright);
}
format->to_xml(node, explicit_defaults, *(dfl.format));
if (tree_) tree_->to_xml(node);
}
@ -246,7 +326,6 @@ char_properties::char_properties() :
character_spacing(0),
line_spacing(0),
text_opacity(1.0),
wrap_before(false),
wrap_char(' '),
text_transform(NONE),
fill(color(0,0,0)),
@ -268,8 +347,6 @@ void char_properties::from_xml(xml_node const& sym, fontset_map const& fontsets)
if (halo_fill_) halo_fill = *halo_fill_;
optional<double> halo_radius_ = sym.get_opt_attr<double>("halo-radius");
if (halo_radius_) halo_radius = *halo_radius_;
optional<boolean> wrap_before_ = sym.get_opt_attr<boolean>("wrap-before");
if (wrap_before_) wrap_before = *wrap_before_;
optional<text_transform_e> tconvert_ = sym.get_opt_attr<text_transform_e>("text-transform");
if (tconvert_) text_transform = *tconvert_;
optional<double> line_spacing_ = sym.get_opt_attr<double>("line-spacing");
@ -334,10 +411,6 @@ void char_properties::to_xml(boost::property_tree::ptree &node, bool explicit_de
{
set_attr(node, "halo-fill", halo_fill);
}
if (wrap_before != dfl.wrap_before || explicit_defaults)
{
set_attr(node, "wrap-before", wrap_before);
}
if (wrap_char != dfl.wrap_char || explicit_defaults)
{
set_attr(node, "wrap-character", std::string(1, wrap_char));

222
src/text/vertex_cache.cpp Normal file
View file

@ -0,0 +1,222 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2013 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
*
*****************************************************************************/
// mapnik
#include <mapnik/text/vertex_cache.hpp>
#include <mapnik/offset_converter.hpp>
// boost
namespace mapnik
{
double vertex_cache::angle(double width)
{
/* IMPORTANT NOTE: See note about coordinate systems in placement_finder::find_point_placement()
* for imformation about why the y axis is inverted! */
double tmp = width + position_in_segment_;
if ((tmp <= current_segment_->length) && (tmp >= 0))
{
//Only calculate angle on request as it is expensive
if (!angle_valid_)
{
angle_ = atan2(-(current_segment_->pos.y - segment_starting_point_.y),
current_segment_->pos.x - segment_starting_point_.x);
}
return width >= 0 ? angle_ : angle_ + M_PI;
} else
{
scoped_state s(*this);
pixel_position const& old_pos = s.get_state().position();
move(width);
double angle = atan2(-(current_position_.y - old_pos.y),
current_position_.x - old_pos.x);
return angle;
}
}
bool vertex_cache::next_subpath()
{
if (!initialized_)
{
current_subpath_ = subpaths_.begin();
initialized_ = true;
} else
{
current_subpath_++;
}
if (current_subpath_ == subpaths_.end()) return false;
rewind_subpath(); //Initialize position values
return true;
}
void vertex_cache::rewind_subpath()
{
current_segment_ = current_subpath_->vector.begin();
//All subpaths contain at least one segment (i.e. the starting point)
segment_starting_point_ = current_position_ = current_segment_->pos;
position_in_segment_ = 0;
angle_valid_ = false;
position_ = 0;
}
void vertex_cache::reset()
{
initialized_ = false;
}
bool vertex_cache::next_segment()
{
segment_starting_point_ = current_segment_->pos; //Next segments starts at the end of the current one
if (current_segment_ == current_subpath_->vector.end()) return false;
current_segment_++;
angle_valid_ = false;
if (current_segment_ == current_subpath_->vector.end()) return false;
return true;
}
bool vertex_cache::previous_segment()
{
if (current_segment_ == current_subpath_->vector.begin()) return false;
current_segment_--;
angle_valid_ = false;
if (current_segment_ == current_subpath_->vector.begin())
{
//First segment is special
segment_starting_point_ = current_segment_->pos;
return true;
}
segment_starting_point_ = (current_segment_-1)->pos;
return true;
}
vertex_cache & vertex_cache::get_offseted(double offset, double region_width)
{
if (fabs(offset) < 0.01)
{
return *this;
}
vertex_cache_ptr offseted_line;
offseted_lines_map::iterator pos = offseted_lines_.find(offset);
if (pos != offseted_lines_.end())
{
offseted_line = pos->second;
} else {
offset_converter<vertex_cache> converter(*this);
converter.set_offset(offset);
offseted_line = vertex_cache_ptr(new vertex_cache(converter));
}
offseted_line->reset();
offseted_line->next_subpath(); //TODO: Multiple subpath support
double seek = (position_ + region_width/2.) * offseted_line->length() / length() - region_width/2.;
if (seek < 0) seek = 0;
if (seek > offseted_line->length()) seek = offseted_line->length();
offseted_line->move(seek);
offseted_lines_[offset] = offseted_line;
return *offseted_line;
}
bool vertex_cache::forward(double length)
{
if (length < 0)
{
MAPNIK_LOG_ERROR(vertex_cache) << "vertex_cache::forward() called with negative argument!\n";
return false;
}
return move(length);
}
bool vertex_cache::backward(double length)
{
if (length < 0)
{
MAPNIK_LOG_ERROR(vertex_cache) << "vertex_cache::backward() called with negative argument!\n";
return false;
}
return move(-length);
}
bool vertex_cache::move(double length)
{
position_ += length;
length += position_in_segment_;
while (length >= current_segment_->length)
{
length -= current_segment_->length;
if (!next_segment()) return false; //Skip all complete segments
}
while (length < 0)
{
if (!previous_segment()) return false;
length += current_segment_->length;
}
double factor = length / current_segment_->length;
position_in_segment_ = length;
current_position_ = segment_starting_point_ + (current_segment_->pos - segment_starting_point_) * factor;
return true;
}
void vertex_cache::rewind(unsigned)
{
vertex_subpath_ = subpaths_.begin();
vertex_segment_ = vertex_subpath_->vector.begin();
}
unsigned vertex_cache::vertex(double *x, double *y)
{
if (vertex_segment_ == vertex_subpath_->vector.end())
{
vertex_subpath_++;
if (vertex_subpath_ == subpaths_.end()) return agg::path_cmd_stop;
vertex_segment_ = vertex_subpath_->vector.begin();
}
*x = vertex_segment_->pos.x;
*y = vertex_segment_->pos.y;
unsigned cmd = (vertex_segment_ == vertex_subpath_->vector.begin()) ? agg::path_cmd_move_to : agg::path_cmd_line_to;
vertex_segment_++;
return cmd;
}
vertex_cache::state vertex_cache::save_state() const
{
state s;
s.current_segment = current_segment_;
s.position_in_segment = position_in_segment_;
s.current_position = current_position_;
s.segment_starting_point = segment_starting_point_;
s.position_ = position_;
return s;
}
void vertex_cache::restore_state(state const& s)
{
current_segment_ = s.current_segment;
position_in_segment_ = s.position_in_segment;
current_position_ = s.current_position;
segment_starting_point_ = s.segment_starting_point;
position_ = s.position_;
angle_valid_ = false;
}
} //ns mapnik

View file

@ -38,62 +38,6 @@ static const char * halo_rasterizer_strings[] = {
IMPLEMENT_ENUM( halo_rasterizer_e, halo_rasterizer_strings )
static const char * label_placement_strings[] = {
"point",
"line",
"vertex",
"interior",
""
};
IMPLEMENT_ENUM( label_placement_e, label_placement_strings )
static const char * vertical_alignment_strings[] = {
"top",
"middle",
"bottom",
"auto",
""
};
IMPLEMENT_ENUM( vertical_alignment_e, vertical_alignment_strings )
static const char * horizontal_alignment_strings[] = {
"left",
"middle",
"right",
"auto",
""
};
IMPLEMENT_ENUM( horizontal_alignment_e, horizontal_alignment_strings )
static const char * justify_alignment_strings[] = {
"left",
"center", // not 'middle' in order to match CSS
"right",
"auto",
""
};
IMPLEMENT_ENUM( justify_alignment_e, justify_alignment_strings )
static const char * text_transform_strings[] = {
"none",
"uppercase",
"lowercase",
"capitalize",
""
};
IMPLEMENT_ENUM( text_transform_e, text_transform_strings )
text_symbolizer::text_symbolizer(text_placements_ptr placements)
: symbolizer_base(),
placement_options_(placements),
@ -103,7 +47,7 @@ text_symbolizer::text_symbolizer(text_placements_ptr placements)
}
text_symbolizer::text_symbolizer(expression_ptr name, std::string const& face_name,
float size, color const& fill,
double size, color const& fill,
text_placements_ptr placements)
: symbolizer_base(),
placement_options_(placements),
@ -115,7 +59,7 @@ text_symbolizer::text_symbolizer(expression_ptr name, std::string const& face_na
set_fill(fill);
}
text_symbolizer::text_symbolizer(expression_ptr name, float size, color const& fill,
text_symbolizer::text_symbolizer(expression_ptr name, double size, color const& fill,
text_placements_ptr placements)
: symbolizer_base(),
placement_options_(placements),
@ -167,22 +111,22 @@ void text_symbolizer::set_orientation(expression_ptr orientation)
std::string const& text_symbolizer::get_face_name() const
{
return placement_options_->defaults.format.face_name;
return placement_options_->defaults.format->face_name;
}
void text_symbolizer::set_face_name(std::string face_name)
{
placement_options_->defaults.format.face_name = face_name;
placement_options_->defaults.format->face_name = face_name;
}
void text_symbolizer::set_fontset(font_set const& fontset)
{
placement_options_->defaults.format.fontset = fontset;
placement_options_->defaults.format->fontset = fontset;
}
boost::optional<font_set> const& text_symbolizer::get_fontset() const
{
return placement_options_->defaults.format.fontset;
return placement_options_->defaults.format->fontset;
}
double text_symbolizer::get_text_ratio() const
@ -207,62 +151,62 @@ void text_symbolizer::set_wrap_width(double width)
bool text_symbolizer::get_wrap_before() const
{
return placement_options_->defaults.format.wrap_before;
return placement_options_->defaults.wrap_before;
}
void text_symbolizer::set_wrap_before(bool wrap_before)
{
placement_options_->defaults.format.wrap_before = wrap_before;
placement_options_->defaults.wrap_before = wrap_before;
}
unsigned char text_symbolizer::get_wrap_char() const
{
return placement_options_->defaults.format.wrap_char;
return placement_options_->defaults.format->wrap_char;
}
std::string text_symbolizer::get_wrap_char_string() const
{
return std::string(1, placement_options_->defaults.format.wrap_char);
return std::string(1, placement_options_->defaults.format->wrap_char);
}
void text_symbolizer::set_wrap_char(unsigned char character)
{
placement_options_->defaults.format.wrap_char = character;
placement_options_->defaults.format->wrap_char = character;
}
void text_symbolizer::set_wrap_char_from_string(std::string const& character)
{
placement_options_->defaults.format.wrap_char = (character)[0];
placement_options_->defaults.format->wrap_char = (character)[0];
}
text_transform_e text_symbolizer::get_text_transform() const
{
return placement_options_->defaults.format.text_transform;
return placement_options_->defaults.format->text_transform;
}
void text_symbolizer::set_text_transform(text_transform_e convert)
{
placement_options_->defaults.format.text_transform = convert;
placement_options_->defaults.format->text_transform = convert;
}
double text_symbolizer::get_line_spacing() const
{
return placement_options_->defaults.format.line_spacing;
return placement_options_->defaults.format->line_spacing;
}
void text_symbolizer::set_line_spacing(double spacing)
{
placement_options_->defaults.format.line_spacing = spacing;
placement_options_->defaults.format->line_spacing = spacing;
}
double text_symbolizer::get_character_spacing() const
{
return placement_options_->defaults.format.character_spacing;
return placement_options_->defaults.format->character_spacing;
}
void text_symbolizer::set_character_spacing(double spacing)
{
placement_options_->defaults.format.character_spacing = spacing;
placement_options_->defaults.format->character_spacing = spacing;
}
double text_symbolizer::get_label_spacing() const
@ -275,12 +219,12 @@ void text_symbolizer::set_label_spacing(double spacing)
placement_options_->defaults.label_spacing = spacing;
}
double text_symbolizer::get_label_position_tolerance() const
unsigned text_symbolizer::get_label_position_tolerance() const
{
return placement_options_->defaults.label_position_tolerance;
}
void text_symbolizer::set_label_position_tolerance(double tolerance)
void text_symbolizer::set_label_position_tolerance(unsigned tolerance)
{
placement_options_->defaults.label_position_tolerance = tolerance;
}
@ -307,42 +251,42 @@ void text_symbolizer::set_max_char_angle_delta(double angle)
void text_symbolizer::set_text_size(double size)
{
placement_options_->defaults.format.text_size = size;
placement_options_->defaults.format->text_size = size;
}
double text_symbolizer::get_text_size() const
{
return placement_options_->defaults.format.text_size;
return placement_options_->defaults.format->text_size;
}
void text_symbolizer::set_fill(color const& fill)
{
placement_options_->defaults.format.fill = fill;
placement_options_->defaults.format->fill = fill;
}
color const& text_symbolizer::get_fill() const
{
return placement_options_->defaults.format.fill;
return placement_options_->defaults.format->fill;
}
void text_symbolizer::set_halo_fill(color const& fill)
{
placement_options_->defaults.format.halo_fill = fill;
placement_options_->defaults.format->halo_fill = fill;
}
color const& text_symbolizer::get_halo_fill() const
{
return placement_options_->defaults.format.halo_fill;
return placement_options_->defaults.format->halo_fill;
}
void text_symbolizer::set_halo_radius(double radius)
{
placement_options_->defaults.format.halo_radius = radius;
placement_options_->defaults.format->halo_radius = radius;
}
double text_symbolizer::get_halo_radius() const
{
return placement_options_->defaults.format.halo_radius;
return placement_options_->defaults.format->halo_radius;
}
void text_symbolizer::set_halo_rasterizer(halo_rasterizer_e rasterizer_p)
@ -367,15 +311,15 @@ label_placement_e text_symbolizer::get_label_placement() const
void text_symbolizer::set_displacement(double x, double y)
{
placement_options_->defaults.displacement = std::make_pair(x,y);
placement_options_->defaults.displacement.set(x, y);
}
void text_symbolizer::set_displacement(position const& p)
void text_symbolizer::set_displacement(const pixel_position &p)
{
placement_options_->defaults.displacement = p;
}
position const& text_symbolizer::get_displacement() const
pixel_position const& text_symbolizer::get_displacement() const
{
return placement_options_->defaults.displacement;
}
@ -442,12 +386,12 @@ bool text_symbolizer::get_allow_overlap() const
void text_symbolizer::set_text_opacity(double text_opacity)
{
placement_options_->defaults.format.text_opacity = text_opacity;
placement_options_->defaults.format->text_opacity = text_opacity;
}
double text_symbolizer::get_text_opacity() const
{
return placement_options_->defaults.format.text_opacity;
return placement_options_->defaults.format->text_opacity;
}
void text_symbolizer::set_vertical_alignment(vertical_alignment_e valign)

View file

@ -400,6 +400,7 @@ compile_get_opt_attr(label_placement_e);
compile_get_opt_attr(vertical_alignment_e);
compile_get_opt_attr(horizontal_alignment_e);
compile_get_opt_attr(justify_alignment_e);
compile_get_opt_attr(text_upright_e);
compile_get_opt_attr(halo_rasterizer_e);
compile_get_opt_attr(expression_ptr);
compile_get_attr(std::string);

View file

@ -6,6 +6,7 @@
<Datasource>
<Parameter name="type">csv</Parameter>
<Parameter name="file">../data/points.csv</Parameter>
<Parameter name="extent">-0.05, -0.01, 0.95, 0.01</Parameter>
</Datasource>
</Layer>