From 64d5153aeaeb1c9e736bfead297dfea39b066d2c Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 22 Nov 2013 00:06:32 -0800 Subject: [PATCH] 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 --- SConstruct | 7 +- bindings/python/mapnik/__init__.py | 5 - bindings/python/mapnik_symbolizer.cpp | 16 +- bindings/python/mapnik_text_placement.cpp | 51 +- include/mapnik/box2d.hpp | 3 + include/mapnik/cairo_context.hpp | 9 +- include/mapnik/font_engine_freetype.hpp | 178 +- include/mapnik/pixel_position.hpp | 62 +- include/mapnik/shield_symbolizer.hpp | 4 +- include/mapnik/text/char_properties_ptr.hpp | 34 + include/mapnik/text/dummy_shaper.hpp | 92 ++ include/mapnik/text/face.hpp | 136 +- include/mapnik/text/formatting/base.hpp | 6 +- .../text/formatting/expression_format.hpp | 4 +- include/mapnik/text/formatting/format.hpp | 2 +- include/mapnik/text/formatting/list.hpp | 3 +- include/mapnik/text/formatting/text.hpp | 2 +- .../text/{char_info.hpp => glyph_info.hpp} | 71 +- include/mapnik/text/harfbuzz_shaper.hpp | 123 ++ include/mapnik/text/icu_shaper.hpp | 123 ++ include/mapnik/text/itemizer.hpp | 109 ++ include/mapnik/text/layout.hpp | 109 ++ include/mapnik/text/placement_finder.hpp | 183 +-- include/mapnik/text/placements/base.hpp | 13 +- include/mapnik/text/placements_list.hpp | 95 ++ include/mapnik/text/processed_text.hpp | 70 - include/mapnik/text/renderer.hpp | 116 ++ include/mapnik/text/rotation.hpp | 23 + include/mapnik/text/scrptrun.hpp | 157 ++ include/mapnik/text/symbolizer_helpers.hpp | 146 +- include/mapnik/text/text_line.hpp | 93 ++ include/mapnik/text/text_path.hpp | 2 +- include/mapnik/text/text_properties.hpp | 30 +- include/mapnik/text/vertex_cache.hpp | 221 +++ include/mapnik/text_symbolizer.hpp | 13 +- src/agg/process_shield_symbolizer.cpp | 47 +- src/agg/process_text_symbolizer.cpp | 27 +- src/box2d.cpp | 25 + src/build.py | 16 +- src/cairo_context.cpp | 115 +- src/cairo_renderer.cpp | 36 +- src/font_engine_freetype.cpp | 507 +----- src/grid/process_shield_symbolizer.cpp | 57 +- src/grid/process_text_symbolizer.cpp | 30 +- src/load_map.cpp | 11 +- src/save_map.cpp | 16 +- src/shield_symbolizer.cpp | 4 +- src/text/face.cpp | 140 ++ src/text/formatting/expression.cpp | 35 +- src/text/formatting/format.cpp | 30 +- src/text/formatting/list.cpp | 3 +- src/text/formatting/text.cpp | 17 +- src/text/itemizer.cpp | 200 +++ src/text/layout.cpp | 200 +++ src/text/placement_finder.cpp | 1443 ++++++----------- src/text/placements/base.cpp | 4 +- src/text/placements/list.cpp | 3 +- src/text/placements/simple.cpp | 23 +- src/text/processed_text.cpp | 104 -- src/text/renderer.cpp | 323 ++++ src/text/scrptrun.cpp | 205 +++ src/text/symbolizer_helpers.cpp | 401 ++--- src/text/text_line.cpp | 99 ++ src/text/text_properties.cpp | 113 +- src/text/vertex_cache.cpp | 222 +++ src/text_symbolizer.cpp | 122 +- src/xml_tree.cpp | 1 + tests/visual_tests/styles/hb-fontsets.xml | 1 + 68 files changed, 4067 insertions(+), 2824 deletions(-) create mode 100644 include/mapnik/text/char_properties_ptr.hpp create mode 100644 include/mapnik/text/dummy_shaper.hpp rename include/mapnik/text/{char_info.hpp => glyph_info.hpp} (54%) create mode 100644 include/mapnik/text/harfbuzz_shaper.hpp create mode 100644 include/mapnik/text/icu_shaper.hpp create mode 100644 include/mapnik/text/itemizer.hpp create mode 100644 include/mapnik/text/layout.hpp create mode 100644 include/mapnik/text/placements_list.hpp delete mode 100644 include/mapnik/text/processed_text.hpp create mode 100644 include/mapnik/text/renderer.hpp create mode 100644 include/mapnik/text/rotation.hpp create mode 100644 include/mapnik/text/scrptrun.hpp create mode 100644 include/mapnik/text/text_line.hpp create mode 100644 include/mapnik/text/vertex_cache.hpp create mode 100644 src/text/face.cpp create mode 100644 src/text/itemizer.cpp create mode 100644 src/text/layout.cpp delete mode 100644 src/text/processed_text.cpp create mode 100644 src/text/renderer.cpp create mode 100644 src/text/scrptrun.cpp create mode 100644 src/text/text_line.cpp create mode 100644 src/text/vertex_cache.cpp diff --git a/SConstruct b/SConstruct index 447392808..1601f50bf 100644 --- a/SConstruct +++ b/SConstruct @@ -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 = [] diff --git a/bindings/python/mapnik/__init__.py b/bindings/python/mapnik/__init__.py index b0afbcf3c..2108b08fa 100644 --- a/bindings/python/mapnik/__init__.py +++ b/bindings/python/mapnik/__init__.py @@ -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): diff --git a/bindings/python/mapnik_symbolizer.cpp b/bindings/python/mapnik_symbolizer.cpp index e343466a1..c1fbf7863 100644 --- a/bindings/python/mapnik_symbolizer.cpp +++ b/bindings/python/mapnik_symbolizer.cpp @@ -46,7 +46,7 @@ #include #include #include // for known_svg_prefix_ - +#include 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(arg[0]); - s.get_placement_options()->defaults.displacement.second = extract(arg[1]); + s.get_placement_options()->defaults.displacement.x = extract(arg[0]); + s.get_placement_options()->defaults.displacement.y = extract(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) diff --git a/bindings/python/mapnik_text_placement.cpp b/bindings/python/mapnik_text_placement.cpp index 11f0a05cc..11f5521bb 100644 --- a/bindings/python/mapnik_text_placement.cpp +++ b/bindings/python/mapnik_text_placement.cpp @@ -27,11 +27,13 @@ #include #include +#include +#include #include #include #include #include -#include +#include #include #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(arg[0]); double y = extract(arg[1]); - t.displacement = std::make_pair(x, y); + t.displacement.set(x, y); } @@ -123,7 +125,7 @@ struct NodeWrap: formatting::node, wrapper } - 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 } - 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 } } - 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 struct FormatNodeWrap: formatting::format_node, wrapper { - 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 } } - 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 struct ExprFormatWrap: formatting::expression_format, wrapper { - 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 /* 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 } } - 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()), + 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 + class_with_converter > ("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()) .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 >(); - class_, - boost::noncopyable> - ("ProcessedText", no_init) - .def("push_back", &processed_text::push_back) - .def("clear", &processed_text::clear) - ; - - class_, 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) diff --git a/include/mapnik/box2d.hpp b/include/mapnik/box2d.hpp index 6133ffbe5..a4f680fd9 100644 --- a/include/mapnik/box2d.hpp +++ b/include/mapnik/box2d.hpp @@ -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; diff --git a/include/mapnik/cairo_context.hpp b/include/mapnik/cairo_context.hpp index 6c9596255..5d8b3e1ac 100644 --- a/include/mapnik/cairo_context.hpp +++ b/include/mapnik/cairo_context.hpp @@ -33,6 +33,9 @@ #include #include #include +#include +#include +#include #include #include @@ -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 & font_manager, double scale_factor = 1.0); diff --git a/include/mapnik/font_engine_freetype.hpp b/include/mapnik/font_engine_freetype.hpp index e7a8399cd..d484368a0 100644 --- a/include/mapnik/font_engine_freetype.hpp +++ b/include/mapnik/font_engine_freetype.hpp @@ -24,15 +24,9 @@ #define MAPNIK_FONT_ENGINE_FREETYPE_HPP // mapnik -#include -#include -#include +#include #include -#include -#include #include -#include -#include #include #include #include @@ -40,90 +34,38 @@ // boost #include -#include +#include +#include #ifdef MAPNIK_THREADSAFE #include #endif -// stl -#include +//// stl #include -#include 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_ptr; +class font_face_set; +typedef std::shared_ptr face_set_ptr; +class font_face; typedef std::shared_ptr 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 glyph_ptr; - - - -class MAPNIK_DECL font_face_set : private mapnik::noncopyable -{ -public: - typedef std::vector 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 dimension_cache_; -}; - -typedef std::shared_ptr face_set_ptr; -typedef std::shared_ptr 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 > name2file_; + static std::map > name2file_; static std::map memory_fonts_; }; @@ -149,7 +91,7 @@ template class MAPNIK_DECL face_manager : private mapnik::noncopyable { typedef T font_engine_type; - typedef std::map face_ptr_cache_type; + typedef std::map 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 fset); - face_set_ptr get_face_set(std::string const& name) - { - face_set_ptr face_set = std::make_shared(); - 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 const& names = fset.get_face_names(); - face_set_ptr face_set = std::make_shared(); - 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 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 -struct text_renderer : private mapnik::noncopyable -{ - - typedef boost::ptr_vector glyphs_t; - typedef T pixmap_type; - - text_renderer (pixmap_type & pixmap, - face_manager & font_manager, - halo_rasterizer_e rasterizer, - composite_mode_e comp_op = src_over, - double scale_factor=1.0); - box2d 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 & font_manager_; - halo_rasterizer_e rasterizer_; - glyphs_t glyphs_; - composite_mode_e comp_op_; - double scale_factor_; -}; - typedef face_manager face_manager_freetype; } diff --git a/include/mapnik/pixel_position.hpp b/include/mapnik/pixel_position.hpp index 1ac537e45..a0d073f7b 100644 --- a/include/mapnik/pixel_position.hpp +++ b/include/mapnik/pixel_position.hpp @@ -22,13 +22,73 @@ #ifndef MAPNIK_PIXEL_POSITION_HPP #define MAPNIK_PIXEL_POSITION_HPP -// Store a pixel position. +// stl +#include + +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 +inline std::basic_ostream& +operator << (std::basic_ostream& out, + const pixel_position& e) +{ + std::basic_ostringstream 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 diff --git a/include/mapnik/shield_symbolizer.hpp b/include/mapnik/shield_symbolizer.hpp index 6b28826f2..7cb80df05 100644 --- a/include/mapnik/shield_symbolizer.hpp +++ b/include/mapnik/shield_symbolizer.hpp @@ -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_; }; } diff --git a/include/mapnik/text/char_properties_ptr.hpp b/include/mapnik/text/char_properties_ptr.hpp new file mode 100644 index 000000000..8d824668a --- /dev/null +++ b/include/mapnik/text/char_properties_ptr.hpp @@ -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 + +namespace mapnik +{ +struct char_properties; +typedef std::shared_ptr char_properties_ptr; +} + +#endif // CHAR_PROPERTIES_PTR_HPP diff --git a/include/mapnik/text/dummy_shaper.hpp b/include/mapnik/text/dummy_shaper.hpp new file mode 100644 index 000000000..8f80a2514 --- /dev/null +++ b/include/mapnik/text/dummy_shaper.hpp @@ -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 +#include +#include +// stl +#include + +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 & 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 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 diff --git a/include/mapnik/text/face.hpp b/include/mapnik/text/face.hpp index 6e04988a2..0528cf6a6 100644 --- a/include/mapnik/text/face.hpp +++ b/include/mapnik/text/face.hpp @@ -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 +#include #include -#include - // freetype2 extern "C" { #include #include FT_FREETYPE_H -#include FT_GLYPH_H #include FT_STROKER_H } +//stl +#include +#include +#include +#include + 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 dimension_cache_; + mutable double char_height_; +}; +typedef std::shared_ptr face_ptr; + + +class MAPNIK_DECL font_face_set : private mapnik::noncopyable +{ +public: + typedef std::vector::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 faces_; +}; +typedef std::shared_ptr 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 diff --git a/include/mapnik/text/formatting/base.hpp b/include/mapnik/text/formatting/base.hpp index ccb699f5e..17704ecb9 100644 --- a/include/mapnik/text/formatting/base.hpp +++ b/include/mapnik/text/formatting/base.hpp @@ -24,16 +24,16 @@ // mapnik #include +#include // boost #include 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 diff --git a/include/mapnik/text/formatting/expression_format.hpp b/include/mapnik/text/formatting/expression_format.hpp index 8e6f15f49..8acbd9e36 100644 --- a/include/mapnik/text/formatting/expression_format.hpp +++ b/include/mapnik/text/formatting/expression_format.hpp @@ -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; diff --git a/include/mapnik/text/formatting/format.hpp b/include/mapnik/text/formatting/format.hpp index b6a763ef0..b7aa862e3 100644 --- a/include/mapnik/text/formatting/format.hpp +++ b/include/mapnik/text/formatting/format.hpp @@ -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); diff --git a/include/mapnik/text/formatting/list.hpp b/include/mapnik/text/formatting/list.hpp index 54be03d33..d74a77295 100644 --- a/include/mapnik/text/formatting/list.hpp +++ b/include/mapnik/text/formatting/list.hpp @@ -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 - diff --git a/include/mapnik/text/formatting/text.hpp b/include/mapnik/text/formatting/text.hpp index b15004d6b..a868e5845 100644 --- a/include/mapnik/text/formatting/text.hpp +++ b/include/mapnik/text/formatting/text.hpp @@ -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); diff --git a/include/mapnik/text/char_info.hpp b/include/mapnik/text/glyph_info.hpp similarity index 54% rename from include/mapnik/text/char_info.hpp rename to include/mapnik/text/glyph_info.hpp index bd21aeab3..a387045e0 100644 --- a/include/mapnik/text/char_info.hpp +++ b/include/mapnik/text/glyph_info.hpp @@ -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 +#include #include -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 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 diff --git a/include/mapnik/text/harfbuzz_shaper.hpp b/include/mapnik/text/harfbuzz_shaper.hpp new file mode 100644 index 000000000..453a74d80 --- /dev/null +++ b/include/mapnik/text/harfbuzz_shaper.hpp @@ -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 +#include +#include +// stl +#include + +// harfbuzz +#include +#include +#include + +namespace mapnik +{ + +struct harfbuzz_shaper +{ +static void shape_text(text_line & line, + text_itemizer & itemizer, + std::map & 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 const& list = itemizer.itemize(start, end); + + auto hb_buffer_deleter = [](hb_buffer_t * buffer) { hb_buffer_destroy(buffer);}; + const std::unique_ptr 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; iglyph_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 diff --git a/include/mapnik/text/icu_shaper.hpp b/include/mapnik/text/icu_shaper.hpp new file mode 100644 index 000000000..db5246a05 --- /dev/null +++ b/include/mapnik/text/icu_shaper.hpp @@ -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 +#include +#include +#include + +// stl +#include + +// icu +#include +#include +#include + + +namespace mapnik +{ + +struct icu_shaper +{ +static void shape_text(text_line & line, + text_itemizer & itemizer, + std::map & 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 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(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 diff --git a/include/mapnik/text/itemizer.hpp b/include/mapnik/text/itemizer.hpp new file mode 100644 index 000000000..4f881bcd7 --- /dev/null +++ b/include/mapnik/text/itemizer.hpp @@ -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 +#include + +// stl +#include +#include +#include + +// ICU +#include +#include +#include +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 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 line(unsigned i) const; + unsigned num_lines() const; +private: + template struct run + { + run(T data, unsigned start, unsigned end) : start(start), end(end), data(data){} + unsigned start; + unsigned end; + T data; + }; + typedef run format_run_t; + typedef run direction_run_t; + typedef run script_run_t; + typedef std::list format_run_list; + typedef std::list script_run_list; + typedef std::list 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 output_; + template typename T::const_iterator find_run(T const& list, unsigned position); + std::vector forced_line_breaks_; //Positions of \n characters +}; +} //ns mapnik + +#endif // TEXT_ITEMIZER_HPP diff --git a/include/mapnik/text/layout.hpp b/include/mapnik/text/layout.hpp new file mode 100644 index 000000000..9c3eb19e7 --- /dev/null +++ b/include/mapnik/text/layout.hpp @@ -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 +#include +#include +#include +#include +#include +#include + +//stl +#include +#include + +namespace mapnik +{ + +class text_layout +{ +public: + typedef std::vector 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::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 width_map_; + double width_; + double height_; + unsigned glyphs_count_; + + //output + line_vector lines_; +}; +} + +#endif // TEXT_LAYOUT_HPP diff --git a/include/mapnik/text/placement_finder.hpp b/include/mapnik/text/placement_finder.hpp index f3ca20364..92660e7c6 100644 --- a/include/mapnik/text/placement_finder.hpp +++ b/include/mapnik/text/placement_finder.hpp @@ -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 -#include -#include -#include -#include -#include +//mapnik +#include +#include +#include +#include +#include +#include #include -// agg -#include "agg_conv_clip_polyline.h" - -// stl -#include - namespace mapnik { -class text_placement_info; -class string_info; -class text_path; - -typedef agg::conv_clip_polyline clipped_geometry_type; -typedef coord_transform ClippedPathType; -typedef coord_transform PathType; - +class label_collision_detector4; typedef label_collision_detector4 DetectorType; +class feature_impl; +class vertex_cache; -template class placement_finder : mapnik::noncopyable { public: - placement_finder(text_placement_info const& placement_info, - string_info const& info, - DetectorT & detector, - box2d const& extent); + placement_finder(feature_impl const& feature, + DetectorType & detector, + box2d 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 - 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 - 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 > & additional_boxes() { return additional_boxes_;} - std::vector > 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 const& get_extents() const { return extents_; } + placements_list const& placements() const { return placements_; } + void set_marker(marker_info_ptr m, box2d 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 get_placement_offset(std::vector const& path_positions, - std::vector 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 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 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 get_bbox(glyph_info const& glyph, pixel_position const& pos, rotation const& rot); + feature_impl const& feature_; + DetectorType &detector_; + box2d const& extent_; + // Precalculated values for maximum performance + rotation orientation_; + text_layout layout_; + text_placement_info_ptr info_; + bool valid_; - ///General Internals - DetectorT & detector_; - box2d 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 line_breaks_; - std::vector > line_sizes_; - std::queue< box2d > envelopes_; - // Used to return all placements found. */ - placements_type placements_; - // Bounding box of all texts placed. */ - box2d 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 > additional_boxes_; + placements_list placements_; + + //ShieldSymbolizer + bool has_marker_; + marker_info_ptr marker_; + box2d marker_box_; + bool marker_unlocked_; + pixel_position marker_displacement_; }; -} +}//ns mapnik -#endif // MAPNIK_PLACEMENT_FINDER_HPP +#endif // PLACEMENT_FINDER_HPP diff --git a/include/mapnik/text/placements/base.hpp b/include/mapnik/text/placements/base.hpp index d1816093f..a766e5045 100644 --- a/include/mapnik/text/placements/base.hpp +++ b/include/mapnik/text/placements/base.hpp @@ -33,7 +33,7 @@ namespace mapnik typedef std::pair 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_ptr; diff --git a/include/mapnik/text/placements_list.hpp b/include/mapnik/text/placements_list.hpp new file mode 100644 index 000000000..4f436c682 --- /dev/null +++ b/include/mapnik/text/placements_list.hpp @@ -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 +#include +#include +#include +#include + +// agg +#include "agg_trans_affine.h" + +//stl +#include +#include + +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_ptr; + +/** Stores positions of glphys. + * + * The actual glyphs and their format are stored in text_layout. + */ +class glyph_positions +{ +public: + typedef std::vector::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 const & bbox() const; +private: + std::vector data_; + pixel_position base_point_; + marker_info_ptr marker_; + pixel_position marker_pos_; + box2d bbox_; +}; +typedef std::shared_ptr glyph_positions_ptr; + +typedef std::list placements_list; +} +#endif // PLACEMENTS_LIST_HPP diff --git a/include/mapnik/text/processed_text.hpp b/include/mapnik/text/processed_text.hpp deleted file mode 100644 index 6a43f8e16..000000000 --- a/include/mapnik/text/processed_text.hpp +++ /dev/null @@ -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 -#include -#include -#include - -// stl -#include - -namespace mapnik -{ - -// fwd declares -class freetype_engine; -template 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 & 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 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 &font_manager_; - double scale_factor_; - string_info info_; -}; - -} // ns mapnik -#endif // PROCESSED_TEXT_HPP diff --git a/include/mapnik/text/renderer.hpp b/include/mapnik/text/renderer.hpp new file mode 100644 index 000000000..f9d9ac602 --- /dev/null +++ b/include/mapnik/text/renderer.hpp @@ -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 +#include +#include +#include + +//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 +#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_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 +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 +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 diff --git a/include/mapnik/text/rotation.hpp b/include/mapnik/text/rotation.hpp new file mode 100644 index 000000000..d7a0fdd1f --- /dev/null +++ b/include/mapnik/text/rotation.hpp @@ -0,0 +1,23 @@ +#ifndef MAPNIK_ROTATION_HPP +#define MAPNIK_ROTATION_HPP + +#include + +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 diff --git a/include/mapnik/text/scrptrun.hpp b/include/mapnik/text/scrptrun.hpp new file mode 100644 index 000000000..c8acec63b --- /dev/null +++ b/include/mapnik/text/scrptrun.hpp @@ -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 diff --git a/include/mapnik/text/symbolizer_helpers.hpp b/include/mapnik/text/symbolizer_helpers.hpp index f83c5c6a8..b167fdff6 100644 --- a/include/mapnik/text/symbolizer_helpers.hpp +++ b/include/mapnik/text/symbolizer_helpers.hpp @@ -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 #include #include +#include #include -#include -#include - -//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 class placement_finder; -} +#include +#include +#include namespace mapnik { /** Helper object that does all the TextSymbolizer placement finding * work except actually rendering the object. */ -template + class text_symbolizer_helper { public: + template text_symbolizer_helper(text_symbolizer const& sym, feature_impl const& feature, proj_transform const& prj_trans, @@ -65,18 +52,23 @@ public: DetectorT &detector, box2d const& query_extent); - ~text_symbolizer_helper(); - // Return next placement. - // If no more placements are found returns null pointer. - bool next(); + template + 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 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 dims_; box2d 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 geometries_to_process_; - // Geometry currently being processed. + /** Geometry currently being processed. */ std::list::iterator geo_itr_; - // Remaining points to be processed. - std::list points_; - // Point currently being processed. - std::list::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 points_; + /** Point currently being processed. */ + std::list::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 > finder_; -}; + placement_finder finder_; -template -class shield_symbolizer_helper: public text_symbolizer_helper -{ -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 const& query_extent) : - text_symbolizer_helper(sym, feature, prj_trans, width, height, scale_factor, t, font_manager, detector, query_extent), - sym_(sym) - { - this->points_on_line_ = true; - init_marker(); - } - - box2d 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 marker_ext_; - boost::optional marker_; - agg::trans_affine image_transform_; - double marker_w_; - double marker_h_; - double marker_x_; - double marker_y_; - - using text_symbolizer_helper::geometries_to_process_; - using text_symbolizer_helper::placement_; - using text_symbolizer_helper::next_placement; - using text_symbolizer_helper::geo_itr_; - using text_symbolizer_helper::point_itr_; - using text_symbolizer_helper::points_; - using text_symbolizer_helper::font_manager_; - using text_symbolizer_helper::feature_; - using text_symbolizer_helper::t_; - using text_symbolizer_helper::detector_; - using text_symbolizer_helper::dims_; - using text_symbolizer_helper::prj_trans_; - using text_symbolizer_helper::placement_valid_; - using text_symbolizer_helper::point_placement_; - using text_symbolizer_helper::angle_; - using text_symbolizer_helper::finder_; }; + } //namespace #endif // SYMBOLIZER_HELPERS_HPP diff --git a/include/mapnik/text/text_line.hpp b/include/mapnik/text/text_line.hpp new file mode 100644 index 000000000..6426bda33 --- /dev/null +++ b/include/mapnik/text/text_line.hpp @@ -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 +#include +//stl +#include + +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_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 diff --git a/include/mapnik/text/text_path.hpp b/include/mapnik/text/text_path.hpp index 2bfdcfe1f..2c8c5a798 100644 --- a/include/mapnik/text/text_path.hpp +++ b/include/mapnik/text/text_path.hpp @@ -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 diff --git a/include/mapnik/text/text_properties.hpp b/include/mapnik/text/text_properties.hpp index 478c9c8bc..55d7ddec8 100644 --- a/include/mapnik/text/text_properties.hpp +++ b/include/mapnik/text/text_properties.hpp @@ -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 #include #include #include @@ -31,6 +32,7 @@ #include #include #include +#include // stl #include @@ -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 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_; diff --git a/include/mapnik/text/vertex_cache.hpp b/include/mapnik/text/vertex_cache.hpp new file mode 100644 index 000000000..4ed872c57 --- /dev/null +++ b/include/mapnik/text/vertex_cache.hpp @@ -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 +#include + +// agg +#include "agg_basics.h" + +// stl +#include +#include +#include + +namespace mapnik +{ + +class vertex_cache; +typedef std::shared_ptr 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::iterator iterator; + std::vector 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 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 subpaths_; + /** Currently active subpath. */ + std::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::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 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 +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 diff --git a/include/mapnik/text_symbolizer.hpp b/include/mapnik/text_symbolizer.hpp index c0ee7640b..667114fb5 100644 --- a/include/mapnik/text_symbolizer.hpp +++ b/include/mapnik/text_symbolizer.hpp @@ -33,6 +33,7 @@ // boost #include + // stl #include @@ -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); diff --git a/src/agg/process_shield_symbolizer.cpp b/src/agg/process_shield_symbolizer.cpp index f723918ff..cc0e696ab 100644 --- a/src/agg/process_shield_symbolizer.cpp +++ b/src/agg/process_shield_symbolizer.cpp @@ -21,15 +21,10 @@ *****************************************************************************/ // mapnik -#include #include -#include -#include +#include #include -#include -#include - -// boost +#include namespace mapnik { @@ -39,42 +34,24 @@ void agg_renderer::process(shield_symbolizer const& sym, proj_transform const& prj_trans) { box2d clip_box = clipping_extent(); - shield_symbolizer_helper, - label_collision_detector4> helper( + text_symbolizer_helper helper( sym, feature, prj_trans, width_, height_, scale_factor_, t_, font_manager_, *detector_, clip_box); - text_renderer ren(*current_buffer_, - font_manager_, - sym.get_halo_rasterizer(), - sym.comp_op(), - scale_factor_); + agg_text_renderer 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); } } diff --git a/src/agg/process_text_symbolizer.cpp b/src/agg/process_text_symbolizer.cpp index 123404522..416e72682 100644 --- a/src/agg/process_text_symbolizer.cpp +++ b/src/agg/process_text_symbolizer.cpp @@ -21,12 +21,10 @@ *****************************************************************************/ // mapnik -#include #include -#include -#include #include -#include +#include +#include namespace mapnik { @@ -36,28 +34,19 @@ void agg_renderer::process(text_symbolizer const& sym, proj_transform const& prj_trans) { box2d clip_box = clipping_extent(); - text_symbolizer_helper, - 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 ren(*current_buffer_, - font_manager_, - sym.get_halo_rasterizer(), - sym.comp_op(), - scale_factor_); + agg_text_renderer 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); } } diff --git a/src/box2d.cpp b/src/box2d.cpp index 6f7001feb..3af4d1df2 100644 --- a/src/box2d.cpp +++ b/src/box2d.cpp @@ -360,6 +360,15 @@ bool box2d::valid() const return (minx_ <= maxx_ && miny_ <= maxy_) ; } +template +void box2d::move(T x, T y) +{ + minx_ += x; + maxx_ += x; + miny_ += y; + maxy_ += y; +} + template box2d& box2d::operator+=(box2d const& other) { @@ -367,6 +376,22 @@ box2d& box2d::operator+=(box2d const& other) return *this; } +template +box2d box2d::operator+ (T other) const +{ + return box2d(minx_ - other, miny_ - other, maxx_ + other, maxy_ + other); +} + +template +box2d& box2d::operator+= (T other) +{ + minx_ -= other; + miny_ -= other; + maxx_ += other; + maxy_ += other; + return *this; +} + template box2d& box2d::operator*=(T t) diff --git a/src/build.py b/src/build.py index b2bff4379..77ccbcc72 100644 --- a/src/build.py +++ b/src/build.py @@ -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 diff --git a/src/cairo_context.cpp b/src/cairo_context.cpp index de6b8ff38..f6d21d061 100644 --- a/src/cairo_context.cpp +++ b/src/cairo_context.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include @@ -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 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 & 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 diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 8e5f87ba9..08f69440f 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -49,7 +49,6 @@ #include #include #include -#include #include #include #include @@ -61,6 +60,7 @@ #include // boost + #include // 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, - 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, - 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_); } } diff --git a/src/font_engine_freetype.cpp b/src/font_engine_freetype.cpp index fd7994a04..43c9f7c88 100644 --- a/src/font_engine_freetype.cpp +++ b/src/font_engine_freetype.cpp @@ -23,15 +23,6 @@ // mapnik #include #include -#include -#include - -#if defined(GRID_RENDERER) -#include -#endif - -#include -#include #include #include #include @@ -41,20 +32,20 @@ #include // stl -#include #include #include -// icu -#include -#include -#include -#include +// freetype2 +extern "C" +{ +#include +#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 +face_ptr face_manager::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(face, g); - } - - // Final fallback to empty square if nothing better in any font - return std::make_shared(*faces_.begin(), 0); -} - -char_info font_face_set::character_dimensions(unsigned int c) -{ - //Check if char is already in cache - typedef std::map::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 +face_set_ptr face_manager::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(); + if (face_ptr face = get_face(name)) { - info.set_rtl(true); + face_set->add(face); } + return face_set; +} + +template +face_set_ptr face_manager::get_face_set(const font_set &fset) +{ + std::vector const& names = fset.get_face_names(); + face_set_ptr face_set = std::make_shared(); + for (std::vector::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 -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;ibuffer[q*bitmap->width+p]; - if (gray) - { - pixmap.composite_pixel(comp_op, i, j, rgba, gray, opacity); - } - } } + return face_set; } template -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::get_face_set(const std::string &name, boost::optional 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 -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 -text_renderer::text_renderer(pixmap_type & pixmap, - face_manager & 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 -box2d text_renderer::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(bbox.xMin, bbox.yMin, bbox.xMax, bbox.yMax); -} - -template -void text_renderer::render(pixel_position const& pos) -{ - FT_Error error; - FT_Vector start; - int height = pixmap_.height(); - - start.x = static_cast(pos.x * (1 << 6)); - start.y = static_cast((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 -void text_renderer::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(pos.x * (1 << 6)); - start.y = static_cast((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(itr->properties->halo_radius)); - } - } -} -#endif - #ifdef MAPNIK_THREADSAFE std::mutex freetype_engine::mutex_; #endif std::map > freetype_engine::name2file_; std::map freetype_engine::memory_fonts_; +template class face_manager; -template text_renderer::text_renderer(image_32&, - face_manager&, - halo_rasterizer_e, - composite_mode_e, - double); -template box2dtext_renderer::prepare_glyphs(text_path const&); -template void text_renderer::render(pixel_position const&); -#if defined(GRID_RENDERER) -template void text_renderer::render_id(mapnik::value_integer, - pixel_position const&); -template text_renderer::text_renderer(grid&, - face_manager&, - halo_rasterizer_e, - composite_mode_e, double); -template box2dtext_renderer::prepare_glyphs(text_path const& ); -#endif } diff --git a/src/grid/process_shield_symbolizer.cpp b/src/grid/process_shield_symbolizer.cpp index 7f4385a53..c88b80ee1 100644 --- a/src/grid/process_shield_symbolizer.cpp +++ b/src/grid/process_shield_symbolizer.cpp @@ -22,14 +22,9 @@ *****************************************************************************/ // mapnik -#include -#include #include -#include -#include #include -#include -#include +#include // agg #include "agg_trans_affine.h" @@ -41,50 +36,28 @@ void grid_renderer::process(shield_symbolizer const& sym, mapnik::feature_impl & feature, proj_transform const& prj_trans) { - shield_symbolizer_helper, - 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 ren(pixmap_, - font_manager_, - sym.get_halo_rasterizer(), - sym.comp_op(), - scale_factor_); + grid_text_renderer 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::process(shield_symbolizer const&, diff --git a/src/grid/process_text_symbolizer.cpp b/src/grid/process_text_symbolizer.cpp index 39a682692..1d3c4fdc7 100644 --- a/src/grid/process_text_symbolizer.cpp +++ b/src/grid/process_text_symbolizer.cpp @@ -21,10 +21,9 @@ *****************************************************************************/ // mapnik -#include #include #include -#include +#include namespace mapnik { @@ -33,32 +32,22 @@ void grid_renderer::process(text_symbolizer const& sym, mapnik::feature_impl & feature, proj_transform const& prj_trans) { - text_symbolizer_helper, - 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 ren(pixmap_, - font_manager_, - sym.get_halo_rasterizer(), - sym.comp_op(), - scale_factor_); + grid_text_renderer 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::process(text_symbolizer const&, @@ -66,4 +55,3 @@ template void grid_renderer::process(text_symbolizer const&, proj_transform const&); } - diff --git a/src/load_map.cpp b/src/load_map.cpp index b491a4405..c3c771c2d 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -1192,10 +1192,10 @@ void map_parser::parse_text_symbolizer(rule & rule, xml_node const& sym) placement_finder = std::make_shared(); 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); diff --git a/src/save_map.cpp b/src/save_map.cpp index 1f8bb857d..b56ce02ab 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -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()) { diff --git a/src/shield_symbolizer.cpp b/src/shield_symbolizer.cpp index 8ef650fee..c682e4c3f 100644 --- a/src/shield_symbolizer.cpp +++ b/src/shield_symbolizer.cpp @@ -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_; } diff --git a/src/text/face.cpp b/src/text/face.cpp new file mode 100644 index 000000000..d3344dd1a --- /dev/null +++ b/src/text/face.cpp @@ -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 +#include + +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::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(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 diff --git a/src/text/formatting/expression.cpp b/src/text/formatting/expression.cpp index f8a885640..c351af700 100644 --- a/src/text/formatting/expression.cpp +++ b/src/text/formatting/expression.cpp @@ -27,10 +27,12 @@ #include #include #include +#include #include #include -// boost +//boost + #include @@ -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(*p); + if (face_name) new_properties->face_name = boost::apply_visitor(evaluate(feature), *face_name).to_string(); - if (text_size) new_properties.text_size = + if (text_size) new_properties->text_size = boost::apply_visitor(evaluate(feature), *text_size).to_double(); - if (character_spacing) new_properties.character_spacing = + if (character_spacing) new_properties->character_spacing = boost::apply_visitor(evaluate(feature), *character_spacing).to_double(); - if (line_spacing) new_properties.line_spacing = + if (line_spacing) new_properties->line_spacing = boost::apply_visitor(evaluate(feature), *line_spacing).to_double(); - if (text_opacity) new_properties.text_opacity = + if (text_opacity) new_properties->text_opacity = boost::apply_visitor(evaluate(feature), *text_opacity).to_double(); - if (wrap_before) new_properties.wrap_before = - boost::apply_visitor(evaluate(feature), *wrap_before).to_bool(); - if (wrap_char) new_properties.wrap_char = + if (wrap_char) new_properties->wrap_char = boost::apply_visitor(evaluate(feature), *character_spacing).to_unicode()[0]; -// if (fill) new_properties.fill = -// boost::apply_visitor(evaluate(feature), *fill).to_color(); -// if (halo_fill) new_properties.halo_fill = -// boost::apply_visitor(evaluate(feature), *halo_fill).to_color(); - if (halo_radius) new_properties.halo_radius = + if (fill) new_properties->fill = parse_color( + boost::apply_visitor(evaluate(feature), *fill).to_string()); + if (halo_fill) new_properties->halo_fill = parse_color( + boost::apply_visitor(evaluate(feature), *halo_fill).to_string()); + if (halo_radius) new_properties->halo_radius = boost::apply_visitor(evaluate(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); diff --git a/src/text/formatting/format.cpp b/src/text/formatting/format.cpp index 812614edf..998b0f392 100644 --- a/src/text/formatting/format.cpp +++ b/src/text/formatting/format.cpp @@ -27,7 +27,8 @@ #include #include -// boost +//boost + #include namespace mapnik { @@ -67,8 +68,6 @@ node_ptr format_node::from_xml(xml_node const& xml) n->character_spacing = xml.get_opt_attr("character-spacing"); n->line_spacing = xml.get_opt_attr("line-spacing"); n->text_opacity = xml.get_opt_attr("opacity"); - boost::optional wrap = xml.get_opt_attr("wrap-before"); - if (wrap) n->wrap_before = *wrap; n->wrap_char = xml.get_opt_attr("wrap-character"); n->text_transform = xml.get_opt_attr("text-transform"); n->fill = xml.get_opt_attr("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(*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); diff --git a/src/text/formatting/list.cpp b/src/text/formatting/list.cpp index 77d3a57e1..0aa39d30d 100644 --- a/src/text/formatting/list.cpp +++ b/src/text/formatting/list.cpp @@ -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_) { diff --git a/src/text/formatting/text.cpp b/src/text/formatting/text.cpp index 0188622cd..db15545cd 100644 --- a/src/text/formatting/text.cpp +++ b/src/text/formatting/text.cpp @@ -25,13 +25,16 @@ #include #include #include -#include #include -#include +#include + +//boost + // boost #include + namespace mapnik { namespace formatting @@ -51,26 +54,26 @@ node_ptr text_node::from_xml(xml_node const& xml) return std::make_shared(xml.get_value()); } -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), *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); } } diff --git a/src/text/itemizer.cpp b/src/text/itemizer.cpp new file mode 100644 index 000000000..bd97b9a1e --- /dev/null +++ b/src/text/itemizer.cpp @@ -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 +#include +#include + +// stl +#include + +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 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 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 +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::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 diff --git a/src/text/layout.cpp b/src/text/layout.cpp new file mode 100644 index 000000000..6dded99b3 --- /dev/null +++ b/src/text/layout.cpp @@ -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 +#include +#include + +// ICU +#include + +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 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(line.first_char()); + for (unsigned i=line.first_char(); i < line.last_char(); ++i) + { + // TODO: character_spacing + std::map::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(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(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(line.first_char())) + { + break_position = line.first_char(); + } + if (break_position > static_cast(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(line.first_char())) + { + // No line breaks => no reshaping required + add_line(line); + } + else if (last_break_position != static_cast(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 -#include +#include +#include +#include #include -#include -#include -#include -#include +#include +#include +#include +#include // agg -#include "agg_path_length.h" +#include "agg_conv_clip_polyline.h" -// boost -#include -#include - - -//stl -#include +// stl #include -#include - -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif namespace mapnik { -template -std::pair get_position_at_distance(double target_distance, T & shape_path) +class tolerance_iterator { - double x1 = 0.0; - double y1 = 0.0; - double x2 = 0.0; - double y2 = 0.0; - double distance = 0.0; - bool first = true; - unsigned cmd; - double x = 0.0; - double y = 0.0; - shape_path.rewind(0); - while (!agg::is_stop(cmd = shape_path.vertex(&x2,&y2))) +public: + tolerance_iterator(double label_position_tolerance, double spacing) + : tolerance_(label_position_tolerance > 0 ? + label_position_tolerance : spacing/2.0), + tolerance_delta_(std::max(1.0, tolerance_/100.0)), + value_(0), + initialized_(false), + values_tried_(0) { - if (first || agg::is_move_to(cmd)) - { - first = false; - } - else - { - double dx = x2-x1; - double dy = y2-y1; - - double segment_length = std::sqrt(dx*dx + dy*dy); - distance +=segment_length; - - if (distance > target_distance) - { - x = x2 - dx * (distance - target_distance)/segment_length; - y = y2 - dy * (distance - target_distance)/segment_length; - break; - } - } - x1 = x2; - y1 = y2; } - return std::pair(x, y); + + ~tolerance_iterator() + { + //std::cout << "values tried:" << values_tried_ << "\n"; + } + + double get() const + { + return -value_; + } + + bool next() + { + ++values_tried_; + if (values_tried_ > 255) + { + /* This point should not be reached during normal operation. But I can think of + * cases where very bad spacing and or tolerance values are choosen and the + * placement finder tries an excessive number of placements. + * 255 is an arbitrarily chosen limit. + */ + MAPNIK_LOG_WARN(placement_finder) << "Tried a huge number of placements. Please check " + "'label-position-tolerance' and 'spacing' parameters " + "of your TextSymbolizers.\n"; + return false; + } + if (!initialized_) + { + initialized_ = true; + return true; //Always return value 0 as the first value. + } + if (value_ == 0) + { + value_ = tolerance_delta_; + return true; + } + value_ = -value_; + if (value_ > 0) + { + value_ += tolerance_delta_; + } + if (value_ > tolerance_) + { + return false; + } + return true; + } +private: + double tolerance_; + double tolerance_delta_; + double value_; + bool initialized_; + unsigned values_tried_; +}; + + +// Output is centered around (0,0) +static void rotated_box2d(box2d & box, rotation const& rot, double width, double height) +{ + double new_width = width * rot.cos + height * rot.sin; + double new_height = width * rot.sin + height * rot.cos; + box.init(-new_width/2., -new_height/2., new_width/2., new_height/2.); } -template -double get_total_distance(T & shape_path) +pixel_position pixel_position::rotate(rotation const& rot) const { - return agg::path_length(shape_path); + return pixel_position(x * rot.cos - y * rot.sin, x * rot.sin + y * rot.cos); } -template -placement_finder::placement_finder(text_placement_info const& placement_info, - string_info const& info, - DetectorT & detector, - box2d const& extent) - : detector_(detector), - dimensions_(extent), - info_(info), - p(placement_info.properties), - pi(placement_info), - string_width_(0), - string_height_(0), - first_line_space_(0), - valign_(V_AUTO), - halign_(H_AUTO), - line_breaks_(), - line_sizes_(), - collect_extents_(false) +placement_finder::placement_finder(feature_impl const& feature, + DetectorType &detector, + box2d const& extent, + text_placement_info_ptr placement_info, + face_manager_freetype & font_manager, + double scale_factor) + : feature_(feature), + detector_(detector), + extent_(extent), + layout_(font_manager, scale_factor), + info_(placement_info), + valid_(true), + scale_factor_(scale_factor), + placements_(), + has_marker_(false), + marker_(), + marker_box_() { - init_string_size(); +} + +bool placement_finder::next_position() +{ + if (!valid_) + { + MAPNIK_LOG_WARN(placement_finder) << "next_position() called while last call already returned false!\n"; + return false; + } + if (!info_->next()) + { + valid_ = false; + return false; + } + + info_->properties.process(layout_, feature_); + layout_.layout(info_->properties.wrap_width * scale_factor_, info_->properties.text_ratio, info_->properties.wrap_before); + + if (info_->properties.orientation) + { + // https://github.com/mapnik/mapnik/issues/1352 + mapnik::evaluate evaluator(feature_); + orientation_.init( + boost::apply_visitor( + evaluator, + *(info_->properties.orientation)).to_double() * M_PI / 180.0); + } + else + { + orientation_.reset(); + } init_alignment(); + return true; } -template -template -void placement_finder::find_point_placements(T & shape_path) -{ - unsigned cmd; - double new_x = 0.0; - double new_y = 0.0; - double old_x = 0.0; - double old_y = 0.0; - bool first = true; - - double total_distance = get_total_distance(shape_path); - shape_path.rewind(0); - - if (total_distance == 0) //Point data, not a line - { - double x, y; - shape_path.vertex(&x,&y); - find_point_placement(x, y); - return; - } - - int num_labels = 1; - if (p.label_spacing > 0) - num_labels = static_cast (floor(total_distance / pi.get_actual_label_spacing())); - - if (p.force_odd_labels && num_labels % 2 == 0) - num_labels--; - if (num_labels <= 0) - num_labels = 1; - - double distance = 0.0; // distance from last label - double spacing = total_distance / num_labels; - double target_distance = spacing / 2; // first label should be placed at half the spacing - - while (!agg::is_stop(cmd = shape_path.vertex(&new_x,&new_y))) //For each node in the shape - { - - if (first || agg::is_move_to(cmd)) //Don't do any processing if it is the first node - { - first = false; - } - else - { - //Add the length of this segment to the total we have saved up - double segment_length = std::sqrt(std::pow(old_x-new_x,2) + std::pow(old_y-new_y,2)); //Pythagoras - distance += segment_length; - - //While we have enough distance to place text in - while (distance > target_distance) - { - //Try place at the specified place - double new_weight = (segment_length - (distance - target_distance))/segment_length; - find_point_placement(old_x + (new_x-old_x)*new_weight, old_y + (new_y-old_y)*new_weight); - - distance -= target_distance; //Consume the spacing gap we have used up - target_distance = spacing; //Need to reset the target_distance as it is spacing/2 for the first label. - } - } - - old_x = new_x; - old_y = new_y; - } - -} - -template -void placement_finder::init_string_size() -{ - // Get total string size - if (!info_.num_characters()) return; //At least one character is required - for (std::size_t i = 0; i < info_.num_characters(); i++) - { - char_info const& ci = info_.at(i); - if (!ci.width || !ci.line_height) continue; //Skip empty chars (add no character_spacing for them) - string_width_ += ci.width + ci.format->character_spacing; - string_height_ = std::max(string_height_, ci.line_height+ci.format->line_spacing); - first_line_space_ = std::max(first_line_space_, ci.line_height-ci.avg_height); - } - string_width_ -= info_.at(info_.num_characters()-1).format->character_spacing; //Remove last space - string_height_ -= first_line_space_; //First line is a bit smaller -} - - - - -template -void placement_finder::find_line_breaks() -{ - if (!line_sizes_.empty()) return; - bool first_line = true; - // check if we need to wrap the string - double wrap_at = string_width_ + 1.0; - if (p.wrap_width && string_width_ > p.wrap_width) - { - if (p.text_ratio) - { - for (double i = 1.0; ((wrap_at = string_width_/i)/(string_height_*i)) > p.text_ratio && (string_width_/i) > p.wrap_width; i += 1.0) ; - } - else - { - wrap_at = p.wrap_width; - } - } - - // work out where our line breaks need to be and the resultant width to the 'wrapped' string - if ((wrap_at < string_width_) || info_.has_line_breaks()) - { - first_line_space_ = 0.0; - int last_wrap_char_pos = 0; //Position of last char where wrapping is possible - double last_char_spacing = 0.0; - double last_wrap_char_width = 0.0; //Include char_spacing before and after - string_width_ = 0.0; - string_height_ = 0.0; - double line_width = 0.0; - double line_height = 0.0; //Height of tallest char in line - double word_width = 0.0; //Current unfinished word width - double word_height = 0.0; - //line_width and word_width include char width + spacing, but not the spacing after the last char - - for (std::size_t ii = 0; ii < info_.num_characters(); ii++) - { - char_info const& ci = info_.at(ii); - unsigned c = ci.c; - - if ((c == ci.format->wrap_char) || (c == '\n')) - { - last_wrap_char_pos = ii; - //No wrap at previous position - line_width += word_width + last_wrap_char_width; - line_height = std::max(line_height, word_height); - last_wrap_char_width = last_char_spacing + ci.width + ci.format->character_spacing; - last_char_spacing = 0.0; //Current one is included in last_wrap_char_width - word_width = 0.0; - word_height = 0.0; - } else { - //No wrap char - word_width += last_char_spacing + ci.width; - last_char_spacing = ci.format->character_spacing; - word_height = std::max(word_height, ci.line_height + ci.format->line_spacing); - //TODO: I think this calculation could be wrong if height changes for the first word in the second line - if (first_line) first_line_space_ = std::max(first_line_space_, ci.line_height-ci.avg_height); - } - - // wrap text at first wrap_char after (default) the wrap width or immediately before the current word - if ((c == '\n') || - (line_width > 0 && - ((line_width > wrap_at && !ci.format->wrap_before) || - ((line_width + last_wrap_char_width + word_width) > wrap_at && ci.format->wrap_before)) ) - ) - { - add_line(line_width, line_height, first_line); - line_breaks_.push_back(last_wrap_char_pos); - line_width = 0.0; - line_height = 0.0; - last_wrap_char_width = 0; //Wrap char supressed - first_line = false; - } - } - line_width += last_wrap_char_width + word_width; - line_height = std::max(line_height, word_height); - add_line(line_width, line_height, first_line); - } else { - //No linebreaks - line_sizes_.push_back(std::make_pair(string_width_, string_height_)); - } - line_breaks_.push_back(static_cast(info_.num_characters())); -} - -template -void placement_finder::add_line(double width, double height, bool first_line) -{ - if (first_line) height -= first_line_space_; - string_width_ = std::max(string_width_, width); //Total width is the longest line - string_height_ += height; - line_sizes_.push_back(std::make_pair(width, height)); -} - - -template -void placement_finder::init_alignment() +void placement_finder::init_alignment() { + text_symbolizer_properties const& p = info_->properties; valign_ = p.valign; if (valign_ == V_AUTO) { - if (p.displacement.second > 0.0) + if (p.displacement.y > 0.0) { valign_ = V_BOTTOM; - } else if (p.displacement.second < 0.0) + } + else if (p.displacement.y < 0.0) { valign_ = V_TOP; - } else + } + else { valign_ = V_MIDDLE; } } - halign_ = p.halign; - if (halign_ == H_AUTO) + halign_point_ = p.halign; + halign_line_ = p.halign; + if (halign_point_ == H_AUTO) { - if (p.displacement.first > 0.0) + if (p.displacement.x > 0.0) { - halign_ = H_RIGHT; - } else if (p.displacement.first < 0.0) + halign_point_ = H_RIGHT; + halign_line_ = H_LEFT; + } + else if (p.displacement.x < 0.0) { - halign_ = H_LEFT; - } else + halign_point_ = H_LEFT; + halign_line_= H_RIGHT; + } + else { - halign_ = H_MIDDLE; + halign_point_ = H_MIDDLE; + halign_line_ = H_MIDDLE; } } jalign_ = p.jalign; if (jalign_ == J_AUTO) { - if (p.displacement.first > 0.0) + if (p.displacement.x > 0.0) { jalign_ = J_LEFT; - } else if (p.displacement.first < 0.0) + } + else if (p.displacement.x < 0.0) { jalign_ = J_RIGHT; - } else { + } + else + { jalign_ = J_MIDDLE; } } } -template -void placement_finder::adjust_position(text_path *current_placement) +pixel_position placement_finder::alignment_offset() const //TODO { + pixel_position result(0,0); // if needed, adjust for desired vertical alignment if (valign_ == V_TOP) { - current_placement->center.y -= 0.5 * string_height_; // move center up by 1/2 the total height - } else if (valign_ == V_BOTTOM) + result.y = -0.5 * layout_.height(); // move center up by 1/2 the total height + } + else if (valign_ == V_BOTTOM) { - current_placement->center.y += 0.5 * string_height_; // move center down by the 1/2 the total height + result.y = 0.5 * layout_.height(); // move center down by the 1/2 the total height } // set horizontal position to middle of text - if (halign_ == H_LEFT) + if (halign_point_ == H_LEFT) { - current_placement->center.x -= 0.5 * string_width_; // move center left by 1/2 the string width - } else if (halign_ == H_RIGHT) - { - current_placement->center.x += 0.5 * string_width_; // move center right by 1/2 the string width + result.x = -0.5 * layout_.width(); // move center left by 1/2 the string width } - - // adjust text envelope position by user's x-y displacement (dx, dy) - current_placement->center.x += pi.get_scale_factor() * p.displacement.first; - current_placement->center.y += pi.get_scale_factor() * p.displacement.second; - + else if (halign_point_ == H_RIGHT) + { + result.x = 0.5 * layout_.width(); // move center right by 1/2 the string width + } + return result; } -template -void placement_finder::find_point_placement(double label_x, - double label_y, - double angle) +double placement_finder::jalign_offset(double line_width) const //TODO { - find_line_breaks(); + if (jalign_ == J_MIDDLE) return -(line_width / 2.0); + if (jalign_ == J_LEFT) return -(layout_.width() / 2.0); + if (jalign_ == J_RIGHT) return (layout_.width() / 2.0) - line_width; + return 0; +} - double rad = M_PI * angle/180.0; - double cosa = std::cos(rad); - double sina = std::sin(rad); +bool placement_finder::find_point_placement(pixel_position const& pos) +{ + glyph_positions_ptr glyphs = std::make_shared(); - double x, y; - std::unique_ptr current_placement(new text_path(label_x, label_y)); + /* Find text origin. */ + pixel_position displacement = scale_factor_ * info_->properties.displacement + alignment_offset(); + if (info_->properties.rotate_displacement) displacement = displacement.rotate(!orientation_); + glyphs->set_base_point(pos + displacement); + box2d bbox; + rotated_box2d(bbox, orientation_, layout_.width(), layout_.height()); + bbox.re_center(glyphs->get_base_point().x, glyphs->get_base_point().y); - adjust_position(current_placement.get()); - - // presets for first line - std::size_t line_number = 0; - std::size_t index_to_wrap_at = line_breaks_[0]; - double line_width = line_sizes_[0].first; - double line_height = line_sizes_[0].second; + /* For point placements it is faster to just check the bounding box. */ + if (collision(bbox)) return false; + /* add_marker first checks for collision and then updates the detector.*/ + if (has_marker_ && !add_marker(glyphs, pos)) return false; + if (layout_.num_lines()) detector_.insert(bbox, layout_.text()); /* IMPORTANT NOTE: x and y are relative to the center of the text @@ -401,657 +290,395 @@ void placement_finder::find_point_placement(double label_x, x: grows from left to right y: grows from bottom to top (opposite of normal computer graphics) */ + double x, y; - // set for upper left corner of text envelope for the first line, bottom left of first character - y = string_height_ / 2.0 - line_height; - // RTL text is converted to a mirrored representation in get_string_info() - // so we have to fix line break order here - if (info_.get_rtl()) y = -y; + // set for upper left corner of text envelope for the first line, top left of first character + y = layout_.height() / 2.0; + glyphs->reserve(layout_.glyphs_count()); - // adjust for desired justification - if (jalign_ == J_LEFT) - x = -(string_width_ / 2.0); - else if (jalign_ == J_RIGHT) - x = (string_width_ / 2.0) - line_width; - else /* J_MIDDLE */ - x = -(line_width / 2.0); - - // save each character rendering position and build envelope as go thru loop - std::queue< box2d > c_envelopes; - - for (std::size_t i = 0; i < info_.num_characters(); i++) + for ( auto const& line : layout_) { - char_info const& ci = info_.at(i); + y -= line.height(); //Automatically handles first line differently + x = jalign_offset(line.width()); - double cwidth = ci.width + ci.format->character_spacing; - - if (i == index_to_wrap_at) - { - index_to_wrap_at = line_breaks_[++line_number]; - line_width = line_sizes_[line_number].first; - line_height= line_sizes_[line_number].second; - - if (info_.get_rtl()) - { - y += line_height; - } else - { - y -= line_height; // move position down to line start - } - - // reset to begining of line position - if (jalign_ == J_LEFT) - x = -(string_width_ / 2.0); - else if (jalign_ == J_RIGHT) - x = (string_width_ / 2.0) - line_width; - else - x = -(line_width / 2.0); - continue; - } - else + for (auto const& glyph : line) { // place the character relative to the center of the string envelope - double dx = x * cosa - y*sina; - double dy = x * sina + y*cosa; - - current_placement->add_node(&ci, dx, dy, rad); - - // compute the Bounding Box for each character and test for: - // overlap, minimum distance or edge avoidance - exit if condition occurs - box2d e; - /*x axis: left to right, y axis: top to bottom (negative values higher)*/ - e.init(current_placement->center.x + dx, // Bottom Left - current_placement->center.y - dy - ci.ymin, // ymin usually <0 - current_placement->center.x + dx + ci.width, // Top Right - current_placement->center.y - dy - ci.ymax); - - // if there is an overlap with existing envelopes, then exit - no placement - - if (!detector_.extent().intersects(e) || - (!p.allow_overlap && - !detector_.has_point_placement(e, pi.get_actual_minimum_distance()))) + glyphs->push_back(glyph, pixel_position(x, y).rotate(orientation_), orientation_); + if (glyph.width) { - return; + //Only advance if glyph is not part of a multiple glyph sequence + x += glyph.width + glyph.format->character_spacing * scale_factor_; } - - // if avoid_edges test dimensions contains e - if (p.avoid_edges && !dimensions_.contains(e)) - { - return; - } - - if (p.minimum_padding > 0) - { - box2d epad = e; - epad.pad(pi.get_actual_minimum_padding()); - if (!dimensions_.contains(epad)) - { - return; - } - } - - - c_envelopes.push(e); // add character's envelope to temp storage - } - x += cwidth; // move position to next character - } - - // check the placement of any additional envelopes - if (!p.allow_overlap && !additional_boxes_.empty()) - { - for (box2d const& box : additional_boxes_) - { - box2d pt(box.minx() + current_placement->center.x, - box.miny() + current_placement->center.y, - box.maxx() + current_placement->center.x, - box.maxy() + current_placement->center.y); - - // abort the whole placement if the additional envelopes can't be placed. - if (!detector_.has_point_placement(pt, p.minimum_distance)) return; - - c_envelopes.push(pt); } } - - // since there was no early exit, add the character envelopes to the placements' envelopes - while (!c_envelopes.empty()) - { - envelopes_.push(c_envelopes.front()); - c_envelopes.pop(); - } - - placements_.push_back(current_placement.release()); + placements_.push_back(glyphs); + return true; } - -template -template -void placement_finder::find_line_placements(PathT & shape_path) +template +bool placement_finder::find_line_placements(T & path, bool points) { -#ifdef MAPNIK_LOG - if (! line_sizes_.empty()) + if (!layout_.num_lines()) return true; //TODO + vertex_cache pp(path); + + bool success = false; + while (pp.next_subpath()) { - MAPNIK_LOG_WARN(placement_finder) << "Internal error. Text contains line breaks, but line placement is used. Please file a bug report!"; - } -#endif - - unsigned cmd; - double new_x = 0.0; - double new_y = 0.0; - double old_x = 0.0; - double old_y = 0.0; - bool first = true; - - //Pre-Cache all the path_positions and path_distances - //This stops the PathT from having to do multiple re-projections if we need to reposition ourself - // and lets us know how many points are in the shape. - std::vector path_positions; - std::vector path_distances; // distance from node x-1 to node x - double total_distance = 0; - - shape_path.rewind(0); - while (!agg::is_stop(cmd = shape_path.vertex(&new_x,&new_y))) //For each node in the shape - { - if (!first && agg::is_line_to(cmd)) + if (points) { - double dx = old_x - new_x; - double dy = old_y - new_y; - double distance = std::sqrt(dx*dx + dy*dy); - total_distance += distance; - path_distances.push_back(distance); + if (pp.length() <= 0.001) + { + success = find_point_placement(pp.current_position()) || success; + continue; + } } else { - path_distances.push_back(0); + if ((pp.length() < info_->properties.minimum_path_length * scale_factor_) + || + (pp.length() <= 0.001) /* Clipping removed whole geometry */ + || + (pp.length() < layout_.width())) + { + continue; + } } - first = false; - path_positions.push_back(vertex2d(new_x, new_y, cmd)); - old_x = new_x; - old_y = new_y; + + double spacing = get_spacing(pp.length(), points ? 0. : layout_.width()); + + horizontal_alignment_e halign = info_->properties.halign; + if (halign == H_LEFT) + { + // Don't move + } + else if (halign == H_MIDDLE || halign == H_AUTO) + { + pp.forward(spacing/2.0); + } + else if (halign == H_RIGHT) + { + pp.forward(pp.length()); + } + path_move_dx(pp); + do + { + tolerance_iterator tolerance_offset(info_->properties.label_position_tolerance * scale_factor_, spacing); //TODO: Handle halign + while (tolerance_offset.next()) + { + vertex_cache::scoped_state state(pp); + if (pp.move(tolerance_offset.get()) + && ( + (points && find_point_placement(pp.current_position())) + || (!points && single_line_placement(pp, info_->properties.upright)))) + { + success = true; + break; + } + } + } while (pp.forward(spacing)); } - //Now path_positions is full and total_distance is correct - //shape_path shouldn't be used from here + return success; +} - // Ensure lines have a minimum length. - if (total_distance < p.minimum_path_length) - return; +text_upright_e placement_finder::simplify_upright(text_upright_e upright, double angle) const +{ + if (upright == UPRIGHT_AUTO) + { + return (std::fabs(normalize_angle(angle)) > 0.5*M_PI) ? UPRIGHT_LEFT : UPRIGHT_RIGHT; + } + if (upright == UPRIGHT_LEFT_ONLY) + { + return UPRIGHT_LEFT; + } + if (upright == UPRIGHT_RIGHT_ONLY) + { + return UPRIGHT_RIGHT; + } + return upright; +} - double distance = 0.0; - double displacement = p.displacement.second; // displace by dy +bool placement_finder::single_line_placement(vertex_cache &pp, text_upright_e orientation) +{ + /******************************************************************************** + * IMPORTANT NOTE: See note about coordinate systems in find_point_placement()! * + ********************************************************************************/ + vertex_cache::scoped_state s(pp); - //Calculate a target_distance that will place the labels centered evenly rather than offset from the start of the linestring - if (total_distance < string_width_) //Can't place any strings - return; + glyph_positions_ptr glyphs = std::make_shared(); + std::vector > bboxes; + bboxes.reserve(layout_.text().length()); + int upside_down_glyph_count = 0; - //If there is no spacing then just do one label, otherwise calculate how many there should be + text_upright_e real_orientation = simplify_upright(orientation, pp.angle()); + + double sign = (real_orientation == UPRIGHT_LEFT) ? -1 : 1; + double offset = alignment_offset().y + info_->properties.displacement.y * scale_factor_ + sign * layout_.height()/2.; + + glyphs->reserve(layout_.glyphs_count()); + + for (auto const& line : layout_) + { + //Only subtract half the line height here and half at the end because text is automatically + //centered on the line + offset -= sign * line.height()/2; + vertex_cache & off_pp = pp.get_offseted(offset, sign*layout_.width()); + vertex_cache::scoped_state off_state(off_pp); //TODO: Remove this when a clean implementation in vertex_cache::get_offseted was done + + if (!off_pp.move(sign * jalign_offset(line.width()) - alignment_offset().x)) return false; + + double last_cluster_angle = 999; + int current_cluster = -1; + pixel_position cluster_offset; + double angle; + rotation rot; + double last_glyph_spacing = 0.; + + for (auto const& glyph : line) + { + if (current_cluster != static_cast(glyph.char_index)) + { + if (!off_pp.move(sign * (layout_.cluster_width(current_cluster) + last_glyph_spacing))) + { + return false; + } + current_cluster = glyph.char_index; + last_glyph_spacing = glyph.format->character_spacing * scale_factor_; + //Only calculate new angle at the start of each cluster! + angle = normalize_angle(off_pp.angle(sign * layout_.cluster_width(current_cluster))); + rot.init(angle); + if ((info_->properties.max_char_angle_delta > 0) && (last_cluster_angle != 999) && + std::fabs(normalize_angle(angle-last_cluster_angle)) > info_->properties.max_char_angle_delta) + { + return false; + } + cluster_offset.clear(); + last_cluster_angle = angle; + } + if (std::abs(angle) > M_PI/2) ++upside_down_glyph_count; + + pixel_position pos = off_pp.current_position() + cluster_offset; + //Center the text on the line + double char_height = line.max_char_height(); + pos.y = -pos.y - char_height/2.0*rot.cos; + pos.x = pos.x + char_height/2.0*rot.sin; + + cluster_offset.x += rot.cos * glyph.width; + cluster_offset.y -= rot.sin * glyph.width; + + box2d bbox = get_bbox(glyph, pos, rot); + if (collision(bbox)) return false; + bboxes.push_back(bbox); + glyphs->push_back(glyph, pos, rot); + } + //See comment above + offset -= sign * line.height()/2; + } + if (upside_down_glyph_count > (layout_.text().length()/2)) + { + if (orientation == UPRIGHT_AUTO) + { + //Try again with oposite orientation + s.restore(); + return single_line_placement(pp, real_orientation == UPRIGHT_RIGHT ? UPRIGHT_LEFT : UPRIGHT_RIGHT); + } + //upright==left_only or right_only and more than 50% of characters upside down => no placement + if (orientation == UPRIGHT_LEFT_ONLY || orientation == UPRIGHT_RIGHT_ONLY) + { + return false; + } + } + for (box2d const& bbox : bboxes) + { + detector_.insert(bbox, layout_.text()); + } + placements_.push_back(glyphs); + return true; +} + +void placement_finder::path_move_dx(vertex_cache &pp) +{ + double dx = info_->properties.displacement.x * scale_factor_; + if (dx != 0.0) + { + vertex_cache::state state = pp.save_state(); + if (!pp.move(dx)) pp.restore_state(state); + } +} + +double placement_finder::normalize_angle(double angle) +{ + while (angle >= M_PI) + { + angle -= 2.0 * M_PI; + } + while (angle < -M_PI) + { + angle += 2.0 * M_PI; + } + return angle; +} + +double placement_finder::get_spacing(double path_length, double layout_width) const +{ int num_labels = 1; - if (p.label_spacing > 0) - num_labels = static_cast(floor(total_distance / (pi.get_actual_label_spacing() + string_width_))); + if (info_->properties.label_spacing > 0) + { + num_labels = static_cast(floor( + path_length / (info_->properties.label_spacing * scale_factor_ + layout_width))); + } - if (p.force_odd_labels && (num_labels % 2 == 0)) - num_labels--; + if (info_->properties.force_odd_labels && num_labels % 2 == 0) + { + --num_labels; + } if (num_labels <= 0) + { num_labels = 1; - - //Now we know how many labels we are going to place, calculate the spacing so that they will get placed evenly - double spacing = total_distance / num_labels; - double target_distance = (spacing - string_width_) / 2; // first label should be placed at half the spacing - - //Calculate or read out the tolerance - double tolerance_delta, tolerance; - if (p.label_position_tolerance > 0) - { - tolerance = p.label_position_tolerance; - tolerance_delta = std::max ( 1.0, p.label_position_tolerance/100.0 ); - } - else - { - tolerance = spacing/2.0; - tolerance_delta = std::max ( 1.0, spacing/100.0 ); - } - - - first = true; - for (std::size_t index = 0; index < path_positions.size(); index++) //For each node in the shape - { - cmd = path_positions[index].cmd; - new_x = path_positions[index].x; - new_y = path_positions[index].y; - - if (first || agg::is_move_to(cmd)) //Don't do any processing if it is the first node - { - first = false; - } - else - { - //Add the length of this segment to the total we have saved up - double segment_length = path_distances[index]; - distance += segment_length; - - //While we have enough distance to place text in - while (distance > target_distance) - { - for (double diff = 0; diff < tolerance; diff += tolerance_delta) - { - for(int dir = -1; dir < 2; dir+=2) //-1, +1 - { - //Record details for the start of the string placement - int orientation = 0; - std::unique_ptr current_placement = get_placement_offset(path_positions, path_distances, orientation, index, segment_length - (distance - target_distance) + (diff*dir)); - - //We were unable to place here - if (current_placement.get() == nullptr) - continue; - - //Apply displacement - //NOTE: The text is centered on the line in get_placement_offset, so we are offsetting from there - if (displacement != 0) - { - //Average the angle of all characters and then offset them all by that angle - double anglesum = 0; - for (std::size_t i = 0; i < current_placement->nodes_.size(); i++) - { - double angle = current_placement->nodes_[i].angle; - //Normalize angle in range -PI ... PI - while (angle > M_PI) { - angle -= 2*M_PI; - } - anglesum += angle; - } - anglesum /= current_placement->nodes_.size(); //Now it is angle average - double cosa = orientation * std::cos(anglesum); - double sina = orientation * std::sin(anglesum); - - //Offset all the characters by this angle - for (std::size_t i = 0; i < current_placement->nodes_.size(); i++) - { - current_placement->nodes_[i].pos.x -= - pi.get_scale_factor() * displacement * sina; - current_placement->nodes_[i].pos.y += - pi.get_scale_factor() * displacement * cosa; - } - } - - bool status = test_placement(current_placement, orientation); - - if (status) //We have successfully placed one - { - placements_.push_back(current_placement.release()); - update_detector(); - - //Totally break out of the loops - diff = tolerance; - break; - } - else - { - //If we've failed to place, remove all the envelopes we've added up - while (!envelopes_.empty()) - envelopes_.pop(); - } - - //Don't need to loop twice when diff = 0 - if (diff == 0) - break; - } - } - - distance -= target_distance; //Consume the spacing gap we have used up - target_distance = spacing; //Need to reset the target_distance as it is spacing/2 for the first label. - } - } - - old_x = new_x; - old_y = new_y; } + return path_length / num_labels; } -template -std::unique_ptr placement_finder::get_placement_offset(std::vector const& path_positions, - std::vector const& path_distances, - int & orientation, - std::size_t index, - double distance) +bool placement_finder::collision(const box2d &box) const { - //Check that the given distance is on the given index and find the correct index and distance if not - while (distance < 0 && index > 1) + if (!detector_.extent().intersects(box) + || + (info_->properties.avoid_edges && !extent_.contains(box)) + || + (info_->properties.minimum_padding > 0 && + !extent_.contains(box + (scale_factor_ * info_->properties.minimum_padding))) + || + (!info_->properties.allow_overlap && + !detector_.has_point_placement(box, info_->properties.minimum_distance * scale_factor_)) + ) { - index--; - distance += path_distances[index]; + return true; } - if (index <= 1 && distance < 0) //We've gone off the start, fail out - return std::unique_ptr(nullptr); - - //Same thing, checking if we go off the end - while (index < path_distances.size() && distance > path_distances[index]) - { - distance -= path_distances[index]; - index++; - } - if (index >= path_distances.size()) - return std::unique_ptr(nullptr); - - //Keep track of the initial index,distance incase we need to re-call get_placement_offset - const std::size_t initial_index = index; - const double initial_distance = distance; - - double old_x = path_positions[index-1].x; - double old_y = path_positions[index-1].y; - - double new_x = path_positions[index].x; - double new_y = path_positions[index].y; - - double dx = new_x - old_x; - double dy = new_y - old_y; - - double segment_length = path_distances[index]; - if (segment_length == 0) { - // Not allowed to place across on 0 length segments or discontinuities - return std::unique_ptr(nullptr); - } - - std::unique_ptr current_placement( - new text_path((old_x + dx*distance/segment_length), - (old_y + dy*distance/segment_length) - ) - ); - - double angle = atan2(-dy, dx); - - bool orientation_forced = (orientation != 0); // Whether the orientation was set by the caller - if (!orientation_forced) - orientation = (angle > 0.55*M_PI || angle < -0.45*M_PI) ? -1 : 1; - - std::size_t upside_down_char_count = 0; //Count of characters that are placed upside down. - - for (std::size_t i = 0; i < info_.num_characters(); ++i) - { - // grab the next character according to the orientation - char_info const &ci = orientation > 0 ? info_.at(i) : info_.at(info_.num_characters() - i - 1); - double cwidth = ci.width + ci.format->character_spacing; - - double last_character_angle = angle; - - //Coordinates this character will start at - if (segment_length == 0) { - // Not allowed to place across on 0 length segments or discontinuities - return std::unique_ptr(nullptr); - } - double start_x = old_x + dx*distance/segment_length; - double start_y = old_y + dy*distance/segment_length; - //Coordinates this character ends at, calculated below - double end_x = 0; - double end_y = 0; - - if (segment_length - distance >= cwidth) - { - //if the distance remaining in this segment is enough, we just go further along the segment - distance += cwidth; - - end_x = old_x + dx*distance/segment_length; - end_y = old_y + dy*distance/segment_length; - } - else - { - //If there isn't enough distance left on this segment - // then we need to search until we find the line segment that ends further than ci.width away - do - { - old_x = new_x; - old_y = new_y; - index++; - if (index >= path_positions.size()) //Bail out if we run off the end of the shape - { - //MAPNIK_LOG_ERROR(placement_finder) << "FAIL: Out of space"; - return std::unique_ptr(nullptr); - } - new_x = path_positions[index].x; - new_y = path_positions[index].y; - dx = new_x - old_x; - dy = new_y - old_y; - - segment_length = path_distances[index]; - } - while (std::sqrt(std::pow(start_x - new_x, 2) + std::pow(start_y - new_y, 2)) < cwidth); //Distance from start_ to new_ - - //Calculate the position to place the end of the character on - find_line_circle_intersection( - start_x, start_y, cwidth, - old_x, old_y, new_x, new_y, - end_x, end_y); //results are stored in end_x, end_y - - //Need to calculate distance on the new segment - distance = std::sqrt(std::pow(old_x - end_x, 2) + std::pow(old_y - end_y, 2)); - } - - //Calculate angle from the start of the character to the end based on start_/end_ position - angle = fast_atan2(start_y-end_y, end_x-start_x); - - //Test last_character_angle vs angle - // since our rendering angle has changed then check against our - // max allowable angle change. - double angle_delta = last_character_angle - angle; - // normalise between -180 and 180 - while (angle_delta > M_PI) - angle_delta -= 2*M_PI; - while (angle_delta < -M_PI) - angle_delta += 2*M_PI; - if (p.max_char_angle_delta > 0 && - std::fabs(angle_delta) > p.max_char_angle_delta) - { - //MAPNIK_LOG_ERROR(placement_finder) << "FAIL: Too Bendy!"; - return std::unique_ptr(nullptr); - } - - double render_angle = angle; - double cosa = fast_cos(angle); - double sina = fast_sin(angle); - - double render_x = start_x; - double render_y = start_y; - - //Center the text on the line - double char_height = ci.avg_height; - render_x += char_height/2.0*sina; - render_y += char_height/2.0*cosa; - - if (orientation < 0) - { - // rotate in place - render_x += cwidth*cosa - char_height*sina; - render_y -= cwidth*sina + char_height*cosa; - render_angle += M_PI; - } - current_placement->add_node(&ci, - render_x - current_placement->center.x, - -render_y + current_placement->center.y, - render_angle); - - //Normalise to 0 <= angle < 2PI - while (render_angle >= 2*M_PI) - render_angle -= 2*M_PI; - while (render_angle < 0) - render_angle += 2*M_PI; - - if (render_angle > M_PI/2 && render_angle < 1.5*M_PI) - upside_down_char_count++; - } - - //If we placed too many characters upside down - if (upside_down_char_count >= info_.num_characters()/2.0) - { - //if we auto-detected the orientation then retry with the opposite orientation - if (!orientation_forced) - { - orientation = -orientation; - current_placement = get_placement_offset(path_positions, - path_distances, - orientation, - initial_index, - initial_distance); - } - else - { - //Otherwise we have failed to find a placement - //MAPNIK_LOG_ERROR(placement_finder) << "FAIL: Double upside-down!"; - return std::unique_ptr(nullptr); - } - } - - return std::move(current_placement); + return false; } -template -bool placement_finder::test_placement(std::unique_ptr const& current_placement, - int orientation) +void placement_finder::set_marker(marker_info_ptr m, box2d box, bool marker_unlocked, pixel_position const& marker_displacement) { - //Create and test envelopes - bool status = true; - for (std::size_t i = 0; i < info_.num_characters(); ++i) - { - //TODO: I think this can be simplified by taking the char_info from vertex() but this needs to be carefully tested! - // grab the next character according to the orientation - char_info const& ci = orientation > 0 ? info_.at(i) : info_.at(info_.num_characters() - i - 1); - double cwidth = ci.width + ci.format->character_spacing; - char_info_ptr c; - double x, y, angle; - current_placement->vertex(c, x, y, angle); - x = current_placement->center.x + x; - y = current_placement->center.y - y; - - double sina = fast_sin(angle); - double cosa = fast_cos(angle); - if (orientation < 0) - { - // rotate in place - x += cwidth * cosa - string_height_ * sina; - y -= cwidth * sina + string_height_ * cosa; - angle += M_PI; - //sin(x+PI) = -sin(x) - sina = -sina; - cosa = -cosa; - } - - box2d e(x, y, x + cwidth*cosa, y - cwidth*sina); - // put four corners of the letter into envelope - e.expand_to_include(x - ci.height()*sina, - y - ci.height()*cosa); - e.expand_to_include(x + (cwidth*cosa - ci.height()*sina), - y - (cwidth*sina + ci.height()*cosa)); - - if (!detector_.extent().intersects(e) || - (!p.allow_overlap && - !detector_.has_placement(e, info_.get_string(), pi.get_actual_minimum_distance()) - ) - ) - { - //MAPNIK_LOG_ERROR(placement_finder) << "No Intersects:" << !dimensions_.intersects(e) << ": " << e << " @ " << dimensions_; - //MAPNIK_LOG_ERROR(placement_finder) << "No Placements:" << !detector_.has_placement(e, info.get_string(), p.minimum_distance); - status = false; - break; - } - - if (p.avoid_edges && !dimensions_.contains(e)) - { - //MAPNIK_LOG_ERROR(placement_finder) << "Fail avoid edges"; - status = false; - break; - } - if (p.minimum_padding > 0) - { - - box2d epad = e; - epad.pad(pi.get_actual_minimum_padding()); - if (!dimensions_.contains(epad)) - { - status = false; - break; - } - } - envelopes_.push(e); - } - - current_placement->rewind(); - - return status; + marker_ = m; + marker_box_ = box * scale_factor_; + marker_displacement_ = marker_displacement * scale_factor_; + marker_unlocked_ = marker_unlocked; + has_marker_ = true; } -template -void placement_finder::find_line_circle_intersection( - double cx, double cy, double radius, - double x1, double y1, double x2, double y2, - double & ix, double & iy) + +bool placement_finder::add_marker(glyph_positions_ptr glyphs, pixel_position const& pos) const { - double dx = x2 - x1; - double dy = y2 - y1; - - double A = dx * dx + dy * dy; - double B = 2 * (dx * (x1 - cx) + dy * (y1 - cy)); - double C = (x1 - cx) * (x1 - cx) + (y1 - cy) * (y1 - cy) - radius * radius; - - double det = B * B - 4 * A * C; - if (A <= 0.0000001 || det < 0) - { - //Should never happen - //' No real solutions. - return; - } - else if (det == 0) - { - //Could potentially happen.... - //One solution. - double t = -B / (2 * A); - ix = x1 + t * dx; - iy = y1 + t * dy; - return; - } - else - { - //Two solutions. - - //Always use the 1st one - //We only really have one solution here, as we know the line segment will start in the circle and end outside - double t = (-B + std::sqrt(det)) / (2 * A); - ix = x1 + t * dx; - iy = y1 + t * dy; - - //t = (-B - std::sqrt(det)) / (2 * A); - //ix = x1 + t * dx; - //iy = y1 + t * dy; - - return; - } + pixel_position real_pos = (marker_unlocked_ ? pos : glyphs->get_base_point()) + marker_displacement_; + box2d bbox = marker_box_; + bbox.move(real_pos.x, real_pos.y); + glyphs->set_marker(marker_, real_pos); + if (collision(bbox)) return false; + detector_.insert(bbox); + return true; } -template -void placement_finder::update_detector() +box2d placement_finder::get_bbox(glyph_info const& glyph, pixel_position const& pos, rotation const& rot) { - if (collect_extents_) extents_.init(0,0,0,0); - // add the bboxes to the detector and remove from the placement - while (!envelopes_.empty()) - { - box2d e = envelopes_.front(); - detector_.insert(e, info_.get_string()); - envelopes_.pop(); + /* - if (collect_extents_) - { - extents_.expand_to_include(e); - } - } + (0/ymax) (width/ymax) + *************** + * * + (0/0)* * + * * + *************** + (0/ymin) (width/ymin) + Add glyph offset in y direction, but not in x direction (as we use the full cluster width anyways)! + */ + double width = layout_.cluster_width(glyph.char_index); + if (glyph.width <= 0) width = -width; + pixel_position tmp, tmp2; + tmp.set(0, glyph.ymax); + tmp = tmp.rotate(rot); + tmp2.set(width, glyph.ymax); + tmp2 = tmp2.rotate(rot); + box2d bbox(tmp.x, -tmp.y, + tmp2.x, -tmp2.y); + tmp.set(width, glyph.ymin); + tmp = tmp.rotate(rot); + bbox.expand_to_include(tmp.x, -tmp.y); + tmp.set(0, glyph.ymin); + tmp = tmp.rotate(rot); + bbox.expand_to_include(tmp.x, -tmp.y); + pixel_position pos2 = pos + pixel_position(0, glyph.offset.y).rotate(rot); + bbox.move(pos2.x , -pos2.y); + return bbox; } -template -void placement_finder::clear_placements() + +/*********************************************************************************************/ + + +glyph_positions::glyph_positions() + : data_(), + base_point_(), + marker_(), + marker_pos_(), + bbox_() { - placements_.clear(); - while (!envelopes_.empty()) envelopes_.pop(); + } -template class placement_finder; -template void placement_finder::find_point_placements(ClippedPathType &); -template void placement_finder::find_line_placements(ClippedPathType &); -template void placement_finder::find_point_placements(PathType &); -template void placement_finder::find_line_placements(PathType &); -} // namespace +glyph_positions::const_iterator glyph_positions::begin() const +{ + return data_.begin(); +} + +glyph_positions::const_iterator glyph_positions::end() const +{ + return data_.end(); +} + +void glyph_positions::push_back(glyph_info const& glyph, pixel_position const offset, rotation const& rot) +{ + data_.push_back(glyph_position(glyph, offset, rot)); +} + +void glyph_positions::reserve(unsigned count) +{ + data_.reserve(count); +} + +pixel_position const& glyph_positions::get_base_point() const +{ + return base_point_; +} + +void glyph_positions::set_base_point(pixel_position const base_point) +{ + base_point_ = base_point; +} + +void glyph_positions::set_marker(marker_info_ptr marker, pixel_position const& marker_pos) +{ + marker_ = marker; + marker_pos_ = marker_pos; +} + +marker_info_ptr glyph_positions::marker() const +{ + return marker_; +} + +pixel_position const& glyph_positions::marker_pos() const +{ + return marker_pos_; +} + + +/*************************************************************************************/ +typedef agg::conv_clip_polyline clipped_geometry_type; +typedef coord_transform ClippedPathType; +typedef coord_transform PathType; +template bool placement_finder::find_line_placements(ClippedPathType &, bool); +template bool placement_finder::find_line_placements(PathType &, bool); + + +}// ns mapnik diff --git a/src/text/placements/base.cpp b/src/text/placements/base.cpp index 02f1c1283..faa9e02f2 100644 --- a/src/text/placements/base.cpp +++ b/src/text/placements/base.cpp @@ -19,9 +19,11 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ +// mapnik #include 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(*(properties.format)); } } //ns mapnik diff --git a/src/text/placements/list.cpp b/src/text/placements/list.cpp index 39e3682e2..1bfdb191c 100644 --- a/src/text/placements/list.cpp +++ b/src/text/placements/list.cpp @@ -27,7 +27,6 @@ //boost #include - 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(*(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()) diff --git a/src/text/placements/simple.cpp b/src/text/placements/simple.cpp index ee08527a7..f590d9e45 100644 --- a/src/text/placements/simple.cpp +++ b/src/text/placements/simple.cpp @@ -30,6 +30,7 @@ #include #include #include + #include 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"; diff --git a/src/text/processed_text.cpp b/src/text/processed_text.cpp deleted file mode 100644 index 69c3a0493..000000000 --- a/src/text/processed_text.cpp +++ /dev/null @@ -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 -#include -#include -#include - -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 & 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 diff --git a/src/text/renderer.cpp b/src/text/renderer.cpp new file mode 100644 index 000000000..f3047ecee --- /dev/null +++ b/src/text/renderer.cpp @@ -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 +#include +#include +#include +#include +#include + +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( glyph_pos.rot.cos * 0x10000L); + matrix.xy = static_cast(-glyph_pos.rot.sin * 0x10000L); + matrix.yx = static_cast( glyph_pos.rot.sin * 0x10000L); + matrix.yy = static_cast( glyph_pos.rot.cos * 0x10000L); + + pixel_position pos = glyph_pos.pos + glyph.offset.rotate(glyph_pos.rot); + pen.x = static_cast(pos.x * 64); + pen.y = static_cast(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 +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;ibuffer[q*bitmap->width+p]; + if (gray) + { + pixmap.composite_pixel(comp_op, i, j, rgba, gray, opacity); + } + } + } +} + +template +agg_text_renderer::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 +void agg_text_renderer::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(base_point.x * (1 << 6)); + start.y = static_cast((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(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(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(glyph.image); + composite_bitmap(pixmap_, + &bit->bitmap, + format->fill.rgba(), + bit->left, + height - bit->top, + format->text_opacity, + comp_op_); + } + } +} + + +template +void grid_text_renderer::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(base_point.x * (1 << 6)); + start.y = static_cast((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(glyph.image); + render_halo_id(&bit->bitmap, + feature_id, + bit->left, + height - bit->top, + static_cast(halo_radius)); + } + } +} + + +template +void agg_text_renderer::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 +void grid_text_renderer::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 +grid_text_renderer::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; +template class grid_text_renderer; +} diff --git a/src/text/scrptrun.cpp b/src/text/scrptrun.cpp new file mode 100644 index 000000000..37a02e7c8 --- /dev/null +++ b/src/text/scrptrun.cpp @@ -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 +#include + +#include + +#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; +} + diff --git a/src/text/symbolizer_helpers.cpp b/src/text/symbolizer_helpers.cpp index 392bc0f50..56bae9015 100644 --- a/src/text/symbolizer_helpers.cpp +++ b/src/text/symbolizer_helpers.cpp @@ -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 -#include -#include -#include -#include #include #include -#include +#include +#include #include -#include -#include -#include +#include +#include -// agg +//agg #include "agg_conv_clip_polyline.h" namespace mapnik { template -text_symbolizer_helper::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 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 -text_symbolizer_helper::~text_symbolizer_helper() -{} - -template -bool text_symbolizer_helper::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 &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 -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 - //Start again from begin of list - geo_itr_ = geometries_to_process_.begin(); - continue; //Reexecute size check - } - - typedef coord_transform 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 -bool text_symbolizer_helper::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::next_line_placement_clippe typedef agg::conv_clip_polyline clipped_geometry_type; typedef coord_transform 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::next_line_placement_clippe return false; } -template -bool text_symbolizer_helper::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 -void text_symbolizer_helper::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::initialize_geometries() geo_itr_ = geometries_to_process_.begin(); } -template -void text_symbolizer_helper::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::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::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 -bool text_symbolizer_helper::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 evaluator(feature_); - angle_ = boost::apply_visitor( - evaluator, - *(placement_->properties.orientation)).to_double(); - } else { - angle_ = 0.0; - } - - finder_.reset(new placement_finder(*placement_, - text_.get_string_info(), - detector_, dims_)); - placement_valid_ = true; - return true; -} - -template -placements_type const& text_symbolizer_helper::placements() const -{ - return finder_->get_results(); -} - - /*****************************************************************************/ - template -bool shield_symbolizer_helper::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 &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 -bool shield_symbolizer_helper::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 -bool shield_symbolizer_helper::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(-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::next_line_placement_clipped(); - else - return text_symbolizer_helper::next_line_placement(); -} - - -template -void shield_symbolizer_helper::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(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 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 bbox(px0, py0, px1, py1); + bbox.expand_to_include(px2, py2); + bbox.expand_to_include(px3, py3); + finder_.set_marker(std::make_shared(m, trans), bbox, sym.get_unlock_image(), sym.get_shield_displacement()); } -template -pixel_position shield_symbolizer_helper::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 &font_manager, +label_collision_detector4 &detector, const box2d &query_extent); - -template -marker& shield_symbolizer_helper::get_marker() const -{ - return **marker_; -} - -template -agg::trans_affine const& shield_symbolizer_helper::get_image_transform() const -{ - return image_transform_; -} - -template class text_symbolizer_helper, label_collision_detector4>; -template class shield_symbolizer_helper, 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 &font_manager, +label_collision_detector4 &detector, const box2d &query_extent); } //namespace diff --git a/src/text/text_line.cpp b/src/text/text_line.cpp new file mode 100644 index 000000000..86c6d412c --- /dev/null +++ b/src/text/text_line.cpp @@ -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 + +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 diff --git a/src/text/text_properties.cpp b/src/text/text_properties.cpp index 7279f0256..72688b08a 100644 --- a/src/text/text_properties.cpp +++ b/src/text/text_properties.cpp @@ -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 +#include #include #include -#include -#include #include #include #include @@ -31,12 +31,71 @@ #include // boost + #include 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()), 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 wrap_width_ = sym.get_opt_attr("wrap-width"); if (wrap_width_) wrap_width = *wrap_width_; + optional wrap_before_ = sym.get_opt_attr("wrap-before"); + if (wrap_before_) wrap_before = *wrap_before_; optional label_position_tolerance_ = sym.get_opt_attr("label-position-tolerance"); if (label_position_tolerance_) label_position_tolerance = *label_position_tolerance_; optional spacing_ = sym.get_opt_attr("spacing"); @@ -119,10 +183,14 @@ void text_symbolizer_properties::from_xml(xml_node const &sym, fontset_map const if (jalign_) jalign = *jalign_; optional orientation_ = sym.get_opt_attr("orientation"); if (orientation_) orientation = *orientation_; + optional rotate_displacement_ = sym.get_opt_attr("rotate-displacement"); + if (rotate_displacement_) rotate_displacement = *rotate_displacement_; + optional upright_ = sym.get_opt_attr("upright"); + if (upright_) upright = *upright_; optional dx = sym.get_opt_attr("dx"); - if (dx) displacement.first = *dx; + if (dx) displacement.x = *dx; optional dy = sym.get_opt_attr("dy"); - if (dy) displacement.second = *dy; + if (dy) displacement.y = *dy; optional max_char_angle_delta_ = sym.get_opt_attr("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 halo_radius_ = sym.get_opt_attr("halo-radius"); if (halo_radius_) halo_radius = *halo_radius_; - optional wrap_before_ = sym.get_opt_attr("wrap-before"); - if (wrap_before_) wrap_before = *wrap_before_; optional tconvert_ = sym.get_opt_attr("text-transform"); if (tconvert_) text_transform = *tconvert_; optional line_spacing_ = sym.get_opt_attr("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)); diff --git a/src/text/vertex_cache.cpp b/src/text/vertex_cache.cpp new file mode 100644 index 000000000..1ab4ea109 --- /dev/null +++ b/src/text/vertex_cache.cpp @@ -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 +#include + +// 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 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 diff --git a/src/text_symbolizer.cpp b/src/text_symbolizer.cpp index 20eef1120..5c5adc0af 100644 --- a/src/text_symbolizer.cpp +++ b/src/text_symbolizer.cpp @@ -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 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) diff --git a/src/xml_tree.cpp b/src/xml_tree.cpp index 98ccc4b9e..292266aec 100644 --- a/src/xml_tree.cpp +++ b/src/xml_tree.cpp @@ -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); diff --git a/tests/visual_tests/styles/hb-fontsets.xml b/tests/visual_tests/styles/hb-fontsets.xml index b3f111947..d66f48214 100644 --- a/tests/visual_tests/styles/hb-fontsets.xml +++ b/tests/visual_tests/styles/hb-fontsets.xml @@ -6,6 +6,7 @@ csv ../data/points.csv + -0.05, -0.01, 0.95, 0.01