From 537231f60e932b0bc041d0a1481f8f7f8fe8a962 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Thu, 19 Jan 2012 20:27:05 +0100 Subject: [PATCH 01/81] Remove glyph symbolizer. --- bindings/python/mapnik/__init__.py | 1 - bindings/python/mapnik_glyph_symbolizer.cpp | 121 ----------- bindings/python/mapnik_python.cpp | 2 - bindings/python/mapnik_rule.cpp | 2 - bindings/python/mapnik_symbolizer.cpp | 16 -- include/mapnik/agg_renderer.hpp | 5 +- include/mapnik/attribute_collector.hpp | 39 ---- include/mapnik/cairo_renderer.hpp | 3 - include/mapnik/glyph_symbolizer.hpp | 215 -------------------- include/mapnik/grid/grid_renderer.hpp | 3 - include/mapnik/rule.hpp | 9 +- include/mapnik/svg_renderer.hpp | 3 - src/agg/process_glyph_symbolizer.cpp | 100 --------- src/build.py | 5 - src/cairo_renderer.cpp | 64 ------ src/glyph_symbolizer.cpp | 149 -------------- src/grid/process_glyph_symbolizer.cpp | 41 ---- src/load_map.cpp | 116 ----------- src/save_map.cpp | 94 --------- src/svg/process_glyph_symbolizer.cpp | 40 ---- tests/data/good_maps/glyph_symbolizer.xml | 8 - tests/python_tests/glyph_symbolizer_test.py | 110 ---------- utils/upgrade_map_xml/upgrade_map_xml.py | 3 - workspace/bindings.pri | 1 - workspace/mapnik.pro | 5 - 25 files changed, 2 insertions(+), 1153 deletions(-) delete mode 100644 bindings/python/mapnik_glyph_symbolizer.cpp delete mode 100644 include/mapnik/glyph_symbolizer.hpp delete mode 100644 src/agg/process_glyph_symbolizer.cpp delete mode 100644 src/glyph_symbolizer.cpp delete mode 100644 src/grid/process_glyph_symbolizer.cpp delete mode 100644 src/svg/process_glyph_symbolizer.cpp delete mode 100644 tests/data/good_maps/glyph_symbolizer.xml delete mode 100644 tests/python_tests/glyph_symbolizer_test.py diff --git a/bindings/python/mapnik/__init__.py b/bindings/python/mapnik/__init__.py index e5520236d..458f85029 100644 --- a/bindings/python/mapnik/__init__.py +++ b/bindings/python/mapnik/__init__.py @@ -649,7 +649,6 @@ __all__ = [ 'FontEngine', 'FontSet', 'Geometry2d', - 'GlyphSymbolizer', 'Image', 'ImageView', 'Grid', diff --git a/bindings/python/mapnik_glyph_symbolizer.cpp b/bindings/python/mapnik_glyph_symbolizer.cpp deleted file mode 100644 index 70f01ac73..000000000 --- a/bindings/python/mapnik_glyph_symbolizer.cpp +++ /dev/null @@ -1,121 +0,0 @@ -#include - -#include -#include "mapnik_enumeration.hpp" -#include - -using mapnik::glyph_symbolizer; -using mapnik::position; -using mapnik::enumeration_; -using mapnik::angle_mode_e; -using mapnik::AZIMUTH; -using mapnik::TRIGONOMETRIC; -using namespace boost::python; - -namespace { -using namespace boost::python; - -tuple get_displacement(const glyph_symbolizer& s) -{ - boost::tuple pos = s.get_displacement(); - return boost::python::make_tuple(boost::get<0>(pos),boost::get<1>(pos)); -} - -void set_displacement(glyph_symbolizer & s, boost::python::tuple arg) -{ - s.set_displacement(extract(arg[0]),extract(arg[1])); -} - -} - -void export_glyph_symbolizer() -{ - enumeration_("angle_mode") - .value("AZIMUTH", AZIMUTH) - .value("TRIGONOMETRIC", TRIGONOMETRIC) - ; - - class_("GlyphSymbolizer", - init()) - .add_property("face_name", - make_function(&glyph_symbolizer::get_face_name, - return_value_policy()), - &glyph_symbolizer::set_face_name, - "Get/Set the name of the font face (eg:\"DejaVu Sans " - "Book\") which contains the glyph" - ) - .add_property("char", - &glyph_symbolizer::get_char, - &glyph_symbolizer::set_char, - "Get/Set the char expression. The char is the unicode " - "character indexing the glyph in the font referred by " - "face_name." - ) - .add_property("allow_overlap", - &glyph_symbolizer::get_allow_overlap, - &glyph_symbolizer::set_allow_overlap, - "Get/Set the flag which controls if glyphs should " - "overlap any symbols previously rendered" - ) - .add_property("avoid_edges", - &glyph_symbolizer::get_avoid_edges, - &glyph_symbolizer::set_avoid_edges, - "Get/Set the flag which controls if glyphs should be " - "partially drawn beside the edge of a tile." - ) - .add_property("displacement", - &get_displacement, - &set_displacement) - - .add_property("halo_fill", - make_function(&glyph_symbolizer::get_halo_fill, - return_value_policy()), - &glyph_symbolizer::set_halo_fill) - - .add_property("halo_radius", - &glyph_symbolizer::get_halo_radius, - &glyph_symbolizer::set_halo_radius) - - .add_property("size", - &glyph_symbolizer::get_size, - &glyph_symbolizer::set_size, - "Get/Set the size expression used to size the glyph." - ) - - .add_property("angle", - &glyph_symbolizer::get_angle, - &glyph_symbolizer::set_angle, - "Get/Set the angle expression used to rotate the glyph " - "along its center." - ) - .add_property("angle_mode", - &glyph_symbolizer::get_angle_mode, - &glyph_symbolizer::set_angle_mode, - "Get/Set the angle_mode property. This controls how the " - "angle is interpreted. Valid values are AZIMUTH and " - "TRIGONOMETRIC." - ) - .add_property("value", - &glyph_symbolizer::get_value, - &glyph_symbolizer::set_value, - "Get/set the value expression which will be used to " - "retrieve a a value for the colorizer to use to choose " - "a color." - ) - .add_property("color", - &glyph_symbolizer::get_color, - &glyph_symbolizer::set_color, - "Get/Set the color expression used to color the glyph. " - "(See also the 'colorizer' attribute)" - - ) - .add_property("colorizer", - &glyph_symbolizer::get_colorizer, - &glyph_symbolizer::set_colorizer, - "Get/Set the RasterColorizer used to color the glyph " - "depending on the 'value' expression (which must be " - "defined).\n" - "Only needed if no explicit color is provided" - ) - ; -} diff --git a/bindings/python/mapnik_python.cpp b/bindings/python/mapnik_python.cpp index af4d0c836..035209193 100644 --- a/bindings/python/mapnik_python.cpp +++ b/bindings/python/mapnik_python.cpp @@ -66,7 +66,6 @@ void export_projection(); void export_proj_transform(); void export_view_transform(); void export_raster_colorizer(); -void export_glyph_symbolizer(); void export_inmem_metawriter(); void export_label_collision_detector(); @@ -434,7 +433,6 @@ BOOST_PYTHON_MODULE(_mapnik) export_coord(); export_map(); export_raster_colorizer(); - export_glyph_symbolizer(); export_inmem_metawriter(); export_label_collision_detector(); diff --git a/bindings/python/mapnik_rule.cpp b/bindings/python/mapnik_rule.cpp index b6f3e4bc8..62de4e5f7 100644 --- a/bindings/python/mapnik_rule.cpp +++ b/bindings/python/mapnik_rule.cpp @@ -44,7 +44,6 @@ using mapnik::shield_symbolizer; using mapnik::text_symbolizer; using mapnik::building_symbolizer; using mapnik::markers_symbolizer; -using mapnik::glyph_symbolizer; using mapnik::symbolizer; using mapnik::to_expression_string; @@ -162,7 +161,6 @@ void export_rule() implicitly_convertible(); implicitly_convertible(); implicitly_convertible(); - implicitly_convertible(); implicitly_convertible(); class_("Symbolizers",init<>("TODO")) diff --git a/bindings/python/mapnik_symbolizer.cpp b/bindings/python/mapnik_symbolizer.cpp index bd9f1c185..a4c4c3512 100644 --- a/bindings/python/mapnik_symbolizer.cpp +++ b/bindings/python/mapnik_symbolizer.cpp @@ -39,7 +39,6 @@ using mapnik::shield_symbolizer; using mapnik::text_symbolizer; using mapnik::building_symbolizer; using mapnik::markers_symbolizer; -using mapnik::glyph_symbolizer; struct get_symbolizer_type : public boost::static_visitor { @@ -95,12 +94,6 @@ public: { return "markers"; } - - std::string operator () ( const glyph_symbolizer & /*sym*/ ) - { - return "glyph"; - } - }; std::string get_symbol_type(const symbolizer& symbol) @@ -160,11 +153,6 @@ const markers_symbolizer& markers_( const symbolizer& symbol ) return boost::get(symbol); } -const glyph_symbolizer& glyph_( const symbolizer& symbol ) -{ - return boost::get(symbol); -} - void export_symbolizer() { using namespace boost::python; @@ -202,10 +190,6 @@ void export_symbolizer() .def("markers",markers_, return_value_policy()) - - .def("glyph",glyph_, - return_value_policy()) - ; } diff --git a/include/mapnik/agg_renderer.hpp b/include/mapnik/agg_renderer.hpp index 1a74dd461..9fbe0e80c 100644 --- a/include/mapnik/agg_renderer.hpp +++ b/include/mapnik/agg_renderer.hpp @@ -101,10 +101,7 @@ public: proj_transform const& prj_trans); void process(markers_symbolizer const& sym, Feature const& feature, - proj_transform const& prj_trans); - void process(glyph_symbolizer const& sym, - Feature const& feature, - proj_transform const& prj_trans); + proj_transform const& prj_trans); inline bool process(rule::symbolizers const& /*syms*/, Feature const& /*feature*/, proj_transform const& /*prj_trans*/) diff --git a/include/mapnik/attribute_collector.hpp b/include/mapnik/attribute_collector.hpp index e256a6c9e..c43a7105c 100644 --- a/include/mapnik/attribute_collector.hpp +++ b/include/mapnik/attribute_collector.hpp @@ -167,45 +167,6 @@ struct symbolizer_attributes : public boost::static_visitor<> collect_metawriter(sym); } - void operator () (glyph_symbolizer const& sym) - { - expression_ptr const& char_expr = sym.get_char(); - if (char_expr) - { - expression_attributes f_attr(names_); - boost::apply_visitor(f_attr,*char_expr); - } - - expression_ptr const& angle_expr = sym.get_angle(); - if (angle_expr) - { - expression_attributes f_attr(names_); - boost::apply_visitor(f_attr,*angle_expr); - } - - expression_ptr const& value_expr = sym.get_value(); - if (value_expr) - { - expression_attributes f_attr(names_); - boost::apply_visitor(f_attr,*value_expr); - } - - expression_ptr const& size_expr = sym.get_size(); - if (size_expr) - { - expression_attributes f_attr(names_); - boost::apply_visitor(f_attr,*size_expr); - } - - expression_ptr const& color_expr = sym.get_color(); - if (color_expr) - { - expression_attributes f_attr(names_); - boost::apply_visitor(f_attr,*color_expr); - } - collect_metawriter(sym); - } - void operator () (markers_symbolizer const& sym) { collect_metawriter(sym); diff --git a/include/mapnik/cairo_renderer.hpp b/include/mapnik/cairo_renderer.hpp index 821ed1334..ad2e0da90 100644 --- a/include/mapnik/cairo_renderer.hpp +++ b/include/mapnik/cairo_renderer.hpp @@ -111,9 +111,6 @@ public: void process(markers_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans); - void process(glyph_symbolizer const& sym, - Feature const& feature, - proj_transform const& prj_trans); inline bool process(rule::symbolizers const& /*syms*/, Feature const& /*feature*/, proj_transform const& /*prj_trans*/) diff --git a/include/mapnik/glyph_symbolizer.hpp b/include/mapnik/glyph_symbolizer.hpp deleted file mode 100644 index 25daf62b8..000000000 --- a/include/mapnik/glyph_symbolizer.hpp +++ /dev/null @@ -1,215 +0,0 @@ -/***************************************************************************** - * - * This file is part of Mapnik (c++ mapping toolkit) - * - * Copyright (C) 2011 Artem Pavlenko - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - *****************************************************************************/ - -#ifndef MAPNIK_GLYPH_SYMBOLIZER_HPP -#define MAPNIK_GLYPH_SYMBOLIZER_HPP - -// mapnik -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// boost -#include - -namespace mapnik -{ - -typedef boost::tuple position; - -enum angle_mode_enum { - AZIMUTH, - TRIGONOMETRIC, - angle_mode_enum_MAX -}; - -DEFINE_ENUM(angle_mode_e, angle_mode_enum); - -struct MAPNIK_DECL glyph_symbolizer : public symbolizer_base -{ - glyph_symbolizer(std::string face_name, expression_ptr c) - : symbolizer_base(), - face_name_(face_name), - char_(c), - angle_(), - value_(), - size_(), - color_(), - colorizer_(), - allow_overlap_(false), - avoid_edges_(false), - displacement_(0.0, 0.0), - halo_fill_(color(255,255,255)), - halo_radius_(0), - angle_mode_(TRIGONOMETRIC) {} - - std::string const& get_face_name() const - { - return face_name_; - } - void set_face_name(std::string face_name) - { - face_name_ = face_name; - } - - expression_ptr get_char() const - { - return char_; - } - void set_char(expression_ptr c) - { - char_ = c; - } - - bool get_allow_overlap() const - { - return allow_overlap_; - } - void set_allow_overlap(bool allow_overlap) - { - allow_overlap_ = allow_overlap; - } - - bool get_avoid_edges() const - { - return avoid_edges_; - } - void set_avoid_edges(bool avoid_edges) - { - avoid_edges_ = avoid_edges; - } - - void set_displacement(double x, double y) - { - displacement_ = boost::make_tuple(x,y); - } - position const& get_displacement() const - { - return displacement_; - } - - void set_halo_fill(color const& fill) - { - halo_fill_ = fill; - } - color const& get_halo_fill() const - { - return halo_fill_; - } - - void set_halo_radius(unsigned radius) - { - halo_radius_ = radius; - } - - unsigned get_halo_radius() const - { - return halo_radius_; - } - - expression_ptr get_angle() const - { - return angle_; - } - void set_angle(expression_ptr angle) - { - angle_ = angle; - } - - expression_ptr get_value() const - { - return value_; - } - void set_value(expression_ptr value) - { - value_ = value; - } - - expression_ptr get_size() const - { - return size_; - } - void set_size(expression_ptr size) - { - size_ = size; - } - - expression_ptr get_color() const - { - return color_; - } - void set_color(expression_ptr color) - { - color_ = color; - } - - raster_colorizer_ptr get_colorizer() const - { - return colorizer_; - } - void set_colorizer(raster_colorizer_ptr const& colorizer) - { - colorizer_ = colorizer; - } - void set_angle_mode(angle_mode_e angle_mode) - { - angle_mode_ = angle_mode; - } - angle_mode_e get_angle_mode() const - { - return angle_mode_; - } - - - text_path_ptr get_text_path(face_set_ptr const& faces, - Feature const& feature) const; - UnicodeString eval_char(Feature const& feature) const; - double eval_angle(Feature const& feature) const; - unsigned eval_size(Feature const& feature) const; - color eval_color(Feature const& feature) const; - - -private: - std::string face_name_; - expression_ptr char_; - expression_ptr angle_; - expression_ptr value_; - expression_ptr size_; - expression_ptr color_; - raster_colorizer_ptr colorizer_; - bool allow_overlap_; - bool avoid_edges_; - position displacement_; - color halo_fill_; - unsigned halo_radius_; - angle_mode_e angle_mode_; -}; - -} // end mapnik namespace - -#endif // MAPNIK_GLYPH_SYMBOLIZER_HPP diff --git a/include/mapnik/grid/grid_renderer.hpp b/include/mapnik/grid/grid_renderer.hpp index 54b0bae29..4517e4845 100644 --- a/include/mapnik/grid/grid_renderer.hpp +++ b/include/mapnik/grid/grid_renderer.hpp @@ -98,9 +98,6 @@ public: void process(markers_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans); - void process(glyph_symbolizer const& sym, - Feature const& feature, - proj_transform const& prj_trans); inline bool process(rule::symbolizers const& /*syms*/, Feature const& /*feature*/, proj_transform const& /*prj_trans*/) diff --git a/include/mapnik/rule.hpp b/include/mapnik/rule.hpp index d3185bef9..9e811bf3f 100644 --- a/include/mapnik/rule.hpp +++ b/include/mapnik/rule.hpp @@ -33,7 +33,6 @@ #include #include #include -#include #include #include #include @@ -108,11 +107,6 @@ inline bool operator==(markers_symbolizer const& lhs, return (&lhs == &rhs); } -inline bool operator==(glyph_symbolizer const& lhs, - glyph_symbolizer const& rhs) -{ - return (&lhs == &rhs); -} typedef boost::variant symbolizer; + markers_symbolizer> symbolizer; diff --git a/include/mapnik/svg_renderer.hpp b/include/mapnik/svg_renderer.hpp index 3459d0eb3..d4e5e1773 100644 --- a/include/mapnik/svg_renderer.hpp +++ b/include/mapnik/svg_renderer.hpp @@ -83,9 +83,6 @@ namespace mapnik void process(markers_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans); - void process(glyph_symbolizer const& sym, - Feature const& feature, - proj_transform const& prj_trans); /*! * @brief Overload that process the whole set of symbolizers of a rule. diff --git a/src/agg/process_glyph_symbolizer.cpp b/src/agg/process_glyph_symbolizer.cpp deleted file mode 100644 index 4a82cdb4b..000000000 --- a/src/agg/process_glyph_symbolizer.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/***************************************************************************** - * - * This file is part of Mapnik (c++ mapping toolkit) - * - * Copyright (C) 2011 Artem Pavlenko - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - *****************************************************************************/ -//$Id$ - -// mapnik -#include - -// agg -#include "agg_basics.h" -#include "agg_rendering_buffer.h" -#include "agg_pixfmt_rgba.h" -#include "agg_rasterizer_scanline_aa.h" -#include "agg_scanline_u.h" - -namespace mapnik { - -template -void agg_renderer::process(glyph_symbolizer const& sym, - Feature const& feature, - proj_transform const& prj_trans) -{ - face_set_ptr faces = font_manager_.get_face_set(sym.get_face_name()); - stroker_ptr strk = font_manager_.get_stroker(); - if (faces->size() > 0 && strk) - { - // Get x and y from geometry and translate to pixmap coords. - double x, y, z=0.0; - feature.get_geometry(0).label_position(&x, &y); - prj_trans.backward(x,y,z); - t_.forward(&x, &y); - - text_renderer ren(pixmap_, faces, *strk); - - // set fill and halo colors - color fill = sym.eval_color(feature); - ren.set_fill(fill); - if (fill != color("transparent")) { - ren.set_halo_fill(sym.get_halo_fill()); - ren.set_halo_radius(sym.get_halo_radius() * scale_factor_); - } - - // set font size - float size = sym.eval_size(feature); - ren.set_character_size(size * scale_factor_); - faces->set_character_sizes(size * scale_factor_); - - // Get and render text path - // - text_path_ptr path = sym.get_text_path(faces, feature); - // apply displacement - position pos = sym.get_displacement(); - double dx = boost::get<0>(pos); - double dy = boost::get<1>(pos); - path->starting_x = x = x+dx; - path->starting_y = y = y+dy; - - // Prepare glyphs to set internal state and calculate the marker's - // final box so we can check for a valid placement - box2d dim = ren.prepare_glyphs(path.get()); - box2d ext(x-dim.width()/2, y-dim.height()/2, x+dim.width()/2, y+dim.height()/2); - if ((sym.get_allow_overlap() || detector_->has_placement(ext)) && - (!sym.get_avoid_edges() || detector_->extent().contains(ext))) - { - // Placement is valid, render glyph and update detector. - ren.render(x, y); - detector_->insert(ext); - metawriter_with_properties writer = sym.get_metawriter(); - if (writer.first) writer.first->add_box(ext, feature, t_, writer.second); - } - } - else - { - throw config_error( - "Unable to find specified font face in GlyphSymbolizer" - ); - } -} -template void agg_renderer::process(glyph_symbolizer const&, - Feature const&, - proj_transform const&); -} diff --git a/src/build.py b/src/build.py index 62e782106..8fe9d7021 100644 --- a/src/build.py +++ b/src/build.py @@ -145,7 +145,6 @@ source = Split( symbolizer.cpp arrow.cpp unicode.cpp - glyph_symbolizer.cpp markers_symbolizer.cpp metawriter.cpp raster_colorizer.cpp @@ -221,7 +220,6 @@ source += Split( """ agg/agg_renderer.cpp agg/process_building_symbolizer.cpp - agg/process_glyph_symbolizer.cpp agg/process_line_symbolizer.cpp agg/process_line_pattern_symbolizer.cpp agg/process_text_symbolizer.cpp @@ -229,7 +227,6 @@ source += Split( agg/process_polygon_symbolizer.cpp agg/process_polygon_pattern_symbolizer.cpp agg/process_raster_symbolizer.cpp - agg/process_shield_symbolizer.cpp agg/process_markers_symbolizer.cpp """ ) @@ -242,7 +239,6 @@ source += Split( """ grid/grid_renderer.cpp grid/process_building_symbolizer.cpp - grid/process_glyph_symbolizer.cpp grid/process_line_pattern_symbolizer.cpp grid/process_line_symbolizer.cpp grid/process_markers_symbolizer.cpp @@ -262,7 +258,6 @@ if env['SVG_RENDERER']: # svg backend svg/svg_output_attributes.cpp svg/process_symbolizers.cpp svg/process_building_symbolizer.cpp - svg/process_glyph_symbolizer.cpp svg/process_line_pattern_symbolizer.cpp svg/process_line_symbolizer.cpp svg/process_markers_symbolizer.cpp diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 1609aed72..6bc2f88a6 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -1459,70 +1459,6 @@ void cairo_renderer_base::process(markers_symbolizer const& sym, } } -void cairo_renderer_base::process(glyph_symbolizer const& sym, - Feature const& feature, - proj_transform const& prj_trans) -{ - face_set_ptr faces = font_manager_.get_face_set(sym.get_face_name()); - if (faces->size() > 0) - { - // Get x and y from geometry and translate to pixmap coords. - double x, y, z=0.0; - feature.get_geometry(0).label_position(&x, &y); - prj_trans.backward(x,y,z); - t_.forward(&x, &y); - - // set font size - unsigned size = sym.eval_size(feature); - faces->set_character_sizes(size); - - // Get and render text path - // - text_path_ptr path = sym.get_text_path(faces, feature); - // apply displacement - position pos = sym.get_displacement(); - double dx = boost::get<0>(pos); - double dy = boost::get<1>(pos); - path->starting_x = x = x+dx; - path->starting_y = y = y+dy; - - // get fill and halo params - color fill = sym.eval_color(feature); - color halo_fill = sym.get_halo_fill(); - double halo_radius = sym.get_halo_radius(); - if (fill==color("transparent")) - halo_radius = 0; - - double bsize = size/2 + 1; - box2d glyph_ext( - floor(x-bsize), floor(y-bsize), ceil(x+bsize), ceil(y+bsize) - ); - if ((sym.get_allow_overlap() || detector_.has_placement(glyph_ext)) && - (!sym.get_avoid_edges() || detector_.extent().contains(glyph_ext))) - { - // Placement is valid, render glyph and update detector. - cairo_context context(context_); - context.add_text(*path, - face_manager_, - faces, - size, - fill, - halo_radius, - halo_fill - ); - detector_.insert(glyph_ext); - metawriter_with_properties writer = sym.get_metawriter(); - if (writer.first) writer.first->add_box(glyph_ext, feature, t_, writer.second); - } - } - else - { - throw config_error( - "Unable to find specified font face in GlyphSymbolizer" - ); - } -} - void cairo_renderer_base::process(text_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) diff --git a/src/glyph_symbolizer.cpp b/src/glyph_symbolizer.cpp deleted file mode 100644 index 8ef41c638..000000000 --- a/src/glyph_symbolizer.cpp +++ /dev/null @@ -1,149 +0,0 @@ -#include -#include - -namespace mapnik -{ - -static const char * angle_mode_strings[] = { - "azimuth", - "trigonometric", - "" -}; - -IMPLEMENT_ENUM( angle_mode_e, angle_mode_strings ) - -text_path_ptr glyph_symbolizer::get_text_path(face_set_ptr const& faces, - Feature const& feature) const -{ - // Try to evaulate expressions against feature - UnicodeString char_ = eval_char(feature); - double angle = eval_angle(feature); - - // calculate displacement so glyph is rotated along center (default pivot is - // lowerbottom corner) - string_info info(char_); - faces->get_string_info(info); - - // XXX: Perhaps this limitation can be overcomed? - if (info.num_characters() != 1) - { - throw config_error("'char' length must be exactly 1"); - } - - character_info ci = info.at(0); - font_face_set::dimension_t cdim = faces->character_dimensions(ci.character); - double cwidth = static_cast(cdim.width)/2.0; - double cheight = static_cast(cdim.height)/2.0; - double xoff = cwidth*cos(angle) - cheight*sin(angle); - double yoff = cwidth*sin(angle) + cheight*cos(angle); - - // Create text path and add character with displacement and angle - text_path_ptr path_ptr = text_path_ptr(new text_path()); - path_ptr->add_node(ci.character, -xoff, -yoff, angle); - return path_ptr; -} - - - -UnicodeString glyph_symbolizer::eval_char(Feature const& feature) const -{ - expression_ptr expr = get_char(); - if (!expr) - throw config_error("No 'char' expression"); - value_type result = boost::apply_visitor( - evaluate(feature), - *expr - ); -#ifdef MAPNIK_DEBUG - std::clog << "char_result=" << result.to_string() << "\n"; -#endif - return result.to_unicode(); -} - -double glyph_symbolizer::eval_angle(Feature const& feature) const -{ - double angle = 0.0; - expression_ptr expr = get_angle(); - if (expr) { - value_type result = boost::apply_visitor( - evaluate(feature), - *expr - ); -#ifdef MAPNIK_DEBUG - std::clog << "angle_result=" << result.to_string() << "\n"; -#endif - angle = result.to_double(); - // normalize to first rotation in case an expression has made it go past - angle = std::fmod(angle, 360); - angle *= (M_PI/180); // convert to radians - if (get_angle_mode()==AZIMUTH) { - // angle is an azimuth, convert into trigonometric angle - angle = std::atan2(std::cos(angle), std::sin(angle)); - } - if (angle<0) - angle += 2*M_PI; - } - return angle; -} - -unsigned glyph_symbolizer::eval_size(Feature const& feature) const -{ - expression_ptr expr = get_size(); - if (!expr) throw config_error("No 'size' expression"); - value_type result = boost::apply_visitor( - evaluate(feature), - *expr - ); -#ifdef MAPNIK_DEBUG - std::clog << "size_result=" << result.to_string() << "\n"; -#endif - float size = static_cast(result.to_double()); -#ifdef MAPNIK_DEBUG - std::clog << "size=" << size << "\n"; -#endif - return size; -} - -color glyph_symbolizer::eval_color(Feature const& feature) const -{ - raster_colorizer_ptr colorizer = get_colorizer(); - if (colorizer) - { - expression_ptr value_expr = get_value(); - if (!value_expr) - { - throw config_error( - "Must define a 'value' expression to use a colorizer" - ); - } - value_type value_result = boost::apply_visitor( - evaluate(feature), - *value_expr - ); -#ifdef MAPNIK_DEBUG - std::clog << "value_result=" << value_result.to_string() << "\n"; -#endif - return colorizer->get_color((float)value_result.to_double()); - } - else - { - expression_ptr color_expr = get_color(); - if (color_expr) - { - value_type color_result = boost::apply_visitor( - evaluate(feature), - *color_expr - ); -#ifdef MAPNIK_DEBUG - std::clog << "color_result=" << color_result.to_string() << "\n"; -#endif - return color(color_result.to_string()); - } - else - { - return color(0,0,0); // black - } - } -} - -} // end mapnik namespace diff --git a/src/grid/process_glyph_symbolizer.cpp b/src/grid/process_glyph_symbolizer.cpp deleted file mode 100644 index b21453a7a..000000000 --- a/src/grid/process_glyph_symbolizer.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/***************************************************************************** - * - * This file is part of Mapnik (c++ mapping toolkit) - * - * Copyright (C) 2011 Artem Pavlenko - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - *****************************************************************************/ -//$Id$ - -// mapnik -#include -#include - -namespace mapnik { - -template -void grid_renderer::process(glyph_symbolizer const& sym, - Feature const& feature, - proj_transform const& prj_trans) -{ - std::clog << "grid_renderer does not yet support glyph_symbolizer\n"; -} - -template void grid_renderer::process(glyph_symbolizer const&, - Feature const&, - proj_transform const&); -} diff --git a/src/load_map.cpp b/src/load_map.cpp index 41c07c1d4..423d2f80c 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -108,7 +108,6 @@ private: void parse_building_symbolizer(rule & rule, ptree const & sym ); void parse_raster_symbolizer(rule & rule, ptree const & sym ); void parse_markers_symbolizer(rule & rule, ptree const & sym ); - void parse_glyph_symbolizer(rule & rule, ptree const & sym ); void parse_raster_colorizer(raster_colorizer_ptr const& rc, ptree const& node ); void parse_stroke(stroke & strk, ptree const & sym); @@ -846,10 +845,6 @@ void map_parser::parse_rule( feature_type_style & style, ptree const & r ) { parse_markers_symbolizer(rule, sym.second); } - else if ( sym.first == "GlyphSymbolizer") - { - parse_glyph_symbolizer( rule, sym.second ); - } else if ( sym.first != "MinScaleDenominator" && sym.first != "MaxScaleDenominator" && @@ -1994,117 +1989,6 @@ void map_parser::parse_raster_symbolizer( rule & rule, ptree const & sym ) } } -void map_parser::parse_glyph_symbolizer(rule & rule, ptree const & sym) -{ - ensure_attrs(sym, "GlyphSymbolizer", "face-name,char,angle,angle-mode,value,size,color,halo-fill,halo-radius,allow-overlap,avoid-edges,dx,dy,meta-writer,meta-output"); - try - { - // Parse required constructor args - std::string face_name = get_attr(sym, "face-name"); - std::string _char = get_attr(sym, "char"); - - glyph_symbolizer glyph_sym = glyph_symbolizer( - face_name, - parse_expression(_char, "utf8") - ); - - // - // parse and set optional attrs. - // - - // angle - optional angle = - get_opt_attr(sym, "angle"); - if (angle) - glyph_sym.set_angle(parse_expression(*angle, "utf8")); - - angle_mode_e angle_mode = - get_attr(sym, "angle-mode", TRIGONOMETRIC); - glyph_sym.set_angle_mode(angle_mode); - - // value - optional value = - get_opt_attr(sym, "value"); - if (value) - glyph_sym.set_value(parse_expression(*value, "utf8")); - - // size - std::string size = - get_attr(sym, "size"); - glyph_sym.set_size(parse_expression(size, "utf8")); - - // color - optional _color = - get_opt_attr(sym, "color"); - if (_color) - glyph_sym.set_color(parse_expression(*_color, "utf8")); - - // halo_fill - optional halo_fill = get_opt_attr(sym, "halo-fill"); - if (halo_fill) - glyph_sym.set_halo_fill(*halo_fill); - - // halo_radius - optional halo_radius = get_opt_attr( - sym, - "halo-radius"); - if (halo_radius) - glyph_sym.set_halo_radius(*halo_radius); - - // allow_overlap - optional allow_overlap = get_opt_attr( - sym, - "allow-overlap" - ); - if (allow_overlap) - glyph_sym.set_allow_overlap(*allow_overlap); - - // avoid_edges - optional avoid_edges = get_opt_attr( - sym, - "avoid-edges" - ); - if (avoid_edges) - glyph_sym.set_avoid_edges(*avoid_edges); - - // displacement - optional dx = get_opt_attr(sym, "dx"); - optional dy = get_opt_attr(sym, "dy"); - if (dx && dy) - glyph_sym.set_displacement(*dx, *dy); - - // colorizer - ptree::const_iterator childIter = sym.begin(); - ptree::const_iterator endChild = sym.end(); - - for (; childIter != endChild; ++childIter) - { - ptree::value_type const& tag = *childIter; - - if (tag.first == "RasterColorizer") - { - raster_colorizer_ptr colorizer(new raster_colorizer()); - glyph_sym.set_colorizer(colorizer); - parse_raster_colorizer(colorizer, tag.second); - } - else if (tag.first!="" && tag.first!="" ) - { - throw config_error(std::string("Unknown child node. ") + - "Expected 'RasterColorizer' but got '" + - tag.first + "'"); - } - } - - parse_metawriter_in_symbolizer(glyph_sym, sym); - rule.append(glyph_sym); - } - catch (const config_error & ex) - { - ex.append_context("in GlyphSymbolizer"); - throw; - } -} - void map_parser::parse_raster_colorizer(raster_colorizer_ptr const& rc, ptree const& node ) { diff --git a/src/save_map.cpp b/src/save_map.cpp index 3cf56b014..aa7eb4d9e 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -310,100 +310,6 @@ public: add_metawriter_attributes(sym_node, sym); } - void operator () ( glyph_symbolizer const& sym) - { - ptree &node = rule_.push_back( - ptree::value_type("GlyphSymbolizer", ptree()) - )->second; - - glyph_symbolizer dfl("", expression_ptr()); - - // face_name - set_attr( node, "face-name", sym.get_face_name() ); - - // char - if (sym.get_char()) { - const std::string &str = - to_expression_string(*sym.get_char()); - set_attr( node, "char", str ); - } - - // angle - if (sym.get_angle()) { - const std::string &str = - to_expression_string(*sym.get_angle()); - set_attr( node, "angle", str ); - } - - // value - if (sym.get_value()) { - const std::string &str = - to_expression_string(*sym.get_value()); - set_attr( node, "value", str ); - } - - // size - if (sym.get_size()) { - const std::string &str = - to_expression_string(*sym.get_size()); - set_attr( node, "size", str ); - } - - // color - if (sym.get_color()) { - const std::string &str = - to_expression_string(*sym.get_color()); - set_attr( node, "color", str ); - } - - // colorizer - if (sym.get_colorizer()) { - serialize_raster_colorizer(node, sym.get_colorizer(), - explicit_defaults_); - } - - // allow_overlap - if (sym.get_allow_overlap() != dfl.get_allow_overlap() || explicit_defaults_ ) - { - set_attr( node, "allow-overlap", sym.get_allow_overlap() ); - } - // avoid_edges - if (sym.get_avoid_edges() != dfl.get_avoid_edges() || explicit_defaults_ ) - { - set_attr( node, "avoid-edges", sym.get_avoid_edges() ); - } - - // displacement - position displacement = sym.get_displacement(); - if ( displacement.get<0>() != dfl.get_displacement().get<0>() || explicit_defaults_ ) - { - set_attr( node, "dx", displacement.get<0>() ); - } - if ( displacement.get<1>() != dfl.get_displacement().get<1>() || explicit_defaults_ ) - { - set_attr( node, "dy", displacement.get<1>() ); - } - - // halo fill & radius - const color & c = sym.get_halo_fill(); - if ( c != dfl.get_halo_fill() || explicit_defaults_ ) - { - set_attr( node, "halo-fill", c ); - } - - if (sym.get_halo_radius() != dfl.get_halo_radius() || explicit_defaults_ ) - { - set_attr( node, "halo-radius", sym.get_halo_radius() ); - } - - // angle_mode - if (sym.get_angle_mode() != dfl.get_angle_mode() || explicit_defaults_ ) - { - set_attr( node, "angle-mode", sym.get_angle_mode() ); - } - add_metawriter_attributes(node, sym); - } - private: serialize_symbolizer(); diff --git a/src/svg/process_glyph_symbolizer.cpp b/src/svg/process_glyph_symbolizer.cpp deleted file mode 100644 index 7ac35bb4d..000000000 --- a/src/svg/process_glyph_symbolizer.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/***************************************************************************** - * - * This file is part of Mapnik (c++ mapping toolkit) - * - * Copyright (C) 2011 Artem Pavlenko - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - *****************************************************************************/ -//$Id$ - -// mapnik -#include - -namespace mapnik -{ - template - void svg_renderer::process(glyph_symbolizer const& sym, - Feature const& feature, - proj_transform const& prj_trans) - { - // nothing yet. - } - - template void svg_renderer >::process(glyph_symbolizer const& sym, - Feature const& feature, - proj_transform const& prj_trans); -} diff --git a/tests/data/good_maps/glyph_symbolizer.xml b/tests/data/good_maps/glyph_symbolizer.xml deleted file mode 100644 index 62f73d999..000000000 --- a/tests/data/good_maps/glyph_symbolizer.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - \ No newline at end of file diff --git a/tests/python_tests/glyph_symbolizer_test.py b/tests/python_tests/glyph_symbolizer_test.py deleted file mode 100644 index bdf6d8a8c..000000000 --- a/tests/python_tests/glyph_symbolizer_test.py +++ /dev/null @@ -1,110 +0,0 @@ -#encoding: utf8 -#!/usr/bin/env python - -from nose.tools import * - -from utilities import execution_path, save_data, contains_word - -import os, mapnik - -def test_renders_with_agg(): - sym = mapnik.GlyphSymbolizer("DejaVu Sans Condensed", - mapnik.Expression("'í'")) - sym.allow_overlap = True - sym.angle = mapnik.Expression("[azimuth]+90") #+90 so the top of the glyph points upwards - sym.size = mapnik.Expression("[value]") - sym.color = mapnik.Expression("'#ff0000'") - - _map = create_map_and_append_symbolyzer(sym) - if _map: - im = mapnik.Image(_map.width,_map.height) - mapnik.render(_map, im) - save_data('agg_glyph_symbolizer.png', im.tostring('png')) - assert contains_word('\xff\x00\x00\xff', im.tostring()) - -def test_renders_with_cairo(): - if not mapnik.has_pycairo(): - return - sym = mapnik.GlyphSymbolizer("DejaVu Sans Condensed", - mapnik.Expression("'í'")) - sym.allow_overlap = True - sym.angle = mapnik.Expression("[azimuth]+90") #+90 so the top of the glyph points upwards - sym.size = mapnik.Expression("[value]") - sym.color = mapnik.Expression("'#ff0000'") - _map = create_map_and_append_symbolyzer(sym) - if _map: - from cStringIO import StringIO - import cairo - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 256, 256) - mapnik.render(_map, surface) - im = mapnik.Image.from_cairo(surface) - save_data('cairo_glyph_symbolizer.png', im.tostring('png')) - assert contains_word('\xff\x00\x00\xff', im.tostring()) - -def test_load_save_load_map(): - map = mapnik.Map(256,256) - in_map = "../data/good_maps/glyph_symbolizer.xml" - try: - mapnik.load_map(map, in_map) - style = map.find_style('arrows') - sym = style.rules[0].symbols[0] - assert isinstance(sym, mapnik.GlyphSymbolizer) - assert sym.angle_mode == mapnik.angle_mode.AZIMUTH - - out_map = mapnik.save_map_to_string(map).decode('utf8') - map = mapnik.Map(256,256) - mapnik.load_map_from_string(map, out_map.encode('utf8')) - assert 'GlyphSymbolizer' in out_map - # make sure non-ascii characters are well supported since most interesting - # glyphs for symbology are usually in that range - assert u'í' in out_map, out_map - except RuntimeError, e: - # only test datasources that we have installed - if not 'Could not create datasource' in str(e): - raise RuntimeError(e) - -# -# Utilities and setup code -# - -def setup(): - # All of the paths used are relative, if we run the tests - # from another directory we need to chdir() - os.chdir(execution_path('.')) - -def create_map_and_append_symbolyzer(sym): - srs = '+init=epsg:32630' - lyr = mapnik.Layer('arrows') - try: - lyr.datasource = mapnik.Shapefile( - file = '../data/shp/arrows.shp', - ) - lyr.srs = srs - _map = mapnik.Map(256,256, srs) - style = mapnik.Style() - rule = mapnik.Rule() - rule.symbols.append(sym) - - # put a test symbolizer to see what is the azimuth being read - ts = mapnik.TextSymbolizer(mapnik.Expression('[azimuth]'), - "DejaVu Sans Book", - 10, - mapnik.Color("black")) - ts.allow_overlap = True - rule.symbols.append(ts) - - style.rules.append(rule) - _map.append_style('foo', style) - lyr.styles.append('foo') - _map.layers.append(lyr) - _map.zoom_to_box(mapnik.Box2d(0,0,8,8)) - return _map - except RuntimeError, e: - # only test datasources that we have installed - if not 'Could not create datasource' in str(e): - raise RuntimeError(e) - -if __name__ == "__main__": - setup() - [eval(run)() for run in dir() if 'test_' in run] - diff --git a/utils/upgrade_map_xml/upgrade_map_xml.py b/utils/upgrade_map_xml/upgrade_map_xml.py index 1a69450d0..64fa17f87 100755 --- a/utils/upgrade_map_xml/upgrade_map_xml.py +++ b/utils/upgrade_map_xml/upgrade_map_xml.py @@ -204,9 +204,6 @@ def upgrade(input_xml,output_xml=None,indent_xml=True): for sym in rule.findall('BuildingSymbolizer') or []: fixup_sym_attributes(sym) underscore2dash(sym) - for sym in rule.findall('GlyphSymbolizer') or []: - fixup_sym_attributes(sym) - underscore2dash(sym) for sym in rule.findall('MarkersSymbolizer') or []: fixup_sym_attributes(sym) underscore2dash(sym) diff --git a/workspace/bindings.pri b/workspace/bindings.pri index acd4f6753..2798142e7 100644 --- a/workspace/bindings.pri +++ b/workspace/bindings.pri @@ -17,7 +17,6 @@ SOURCES += \ $$PWD/../bindings/python/mapnik_featureset.cpp \ $$PWD/../bindings/python/mapnik_font_engine.cpp \ $$PWD/../bindings/python/mapnik_geometry.cpp \ - $$PWD/../bindings/python/mapnik_glyph_symbolizer.cpp \ $$PWD/../bindings/python/mapnik_grid.cpp \ $$PWD/../bindings/python/mapnik_grid_view.cpp \ $$PWD/../bindings/python/mapnik_image.cpp \ diff --git a/workspace/mapnik.pro b/workspace/mapnik.pro index 666252555..a297e8fc9 100644 --- a/workspace/mapnik.pro +++ b/workspace/mapnik.pro @@ -80,7 +80,6 @@ HEADERS += \ ../include/mapnik/geometry.hpp \ ../include/mapnik/geom_util.hpp \ ../include/mapnik/global.hpp \ - ../include/mapnik/glyph_symbolizer.hpp \ ../include/mapnik/gradient.hpp \ ../include/mapnik/graphics.hpp \ ../include/mapnik/hextree.hpp \ @@ -162,7 +161,6 @@ HEADERS += \ SOURCES += \ ../src/agg/agg_renderer.cpp \ ../src/agg/process_building_symbolizer.cpp \ - ../src/agg/process_glyph_symbolizer.cpp \ ../src/agg/process_line_pattern_symbolizer.cpp \ ../src/agg/process_line_symbolizer.cpp \ ../src/agg/process_markers_symbolizer.cpp \ @@ -174,7 +172,6 @@ SOURCES += \ ../src/agg/process_text_symbolizer.cpp \ ../src/grid/grid_renderer.cpp \ ../src/grid/process_building_symbolizer.cpp \ - ../src/grid/process_glyph_symbolizer.cpp \ ../src/grid/process_line_pattern_symbolizer.cpp \ ../src/grid/process_line_symbolizer.cpp \ ../src/grid/process_markers_symbolizer.cpp \ @@ -188,7 +185,6 @@ SOURCES += \ ../src/svg/svg_output_attributes.cpp \ ../src/svg/process_symbolizers.cpp \ ../src/svg/process_building_symbolizer.cpp \ - ../src/svg/process_glyph_symbolizer.cpp \ ../src/svg/process_line_pattern_symbolizer.cpp \ ../src/svg/process_line_symbolizer.cpp \ ../src/svg/process_markers_symbolizer.cpp \ @@ -210,7 +206,6 @@ SOURCES += \ ../src/filter_factory.cpp \ ../src/font_engine_freetype.cpp \ ../src/font_set.cpp \ - ../src/glyph_symbolizer.cpp \ ../src/gradient.cpp \ ../src/graphics.cpp \ ../src/image_reader.cpp \ From e977df778a67a4fd60e3dc21baab7d1df18819d9 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 20 Jan 2012 00:09:25 +0100 Subject: [PATCH 02/81] Remove unused "anchor" attribute from Text/ShieldSymbolizer. --- bindings/python/mapnik_shield_symbolizer.cpp | 14 ---------- bindings/python/mapnik_text_symbolizer.cpp | 28 +++----------------- include/mapnik/text_symbolizer.hpp | 3 --- src/text_symbolizer.cpp | 15 +---------- 4 files changed, 5 insertions(+), 55 deletions(-) diff --git a/bindings/python/mapnik_shield_symbolizer.cpp b/bindings/python/mapnik_shield_symbolizer.cpp index 4bfe0fe61..e3180845e 100644 --- a/bindings/python/mapnik_shield_symbolizer.cpp +++ b/bindings/python/mapnik_shield_symbolizer.cpp @@ -64,17 +64,6 @@ void set_text_displacement(shield_symbolizer & t, boost::python::tuple arg) t.set_displacement(extract(arg[0]),extract(arg[1])); } -tuple get_anchor(const shield_symbolizer& t) -{ - boost::tuple pos = t.get_anchor(); - return boost::python::make_tuple(boost::get<0>(pos),boost::get<1>(pos)); -} - -void set_anchor(shield_symbolizer & t, boost::python::tuple arg) -{ - t.set_anchor(extract(arg[0]),extract(arg[1])); -} - const std::string get_filename(shield_symbolizer const& t) { return path_processor_type::to_string(*t.get_filename()); @@ -137,9 +126,6 @@ void export_shield_symbolizer() path_expression_ptr>("TODO") ) //.def_pickle(shield_symbolizer_pickle_suite()) - .add_property("anchor", - &get_anchor, - &set_anchor) .add_property("allow_overlap", &shield_symbolizer::get_allow_overlap, &shield_symbolizer::set_allow_overlap, diff --git a/bindings/python/mapnik_text_symbolizer.cpp b/bindings/python/mapnik_text_symbolizer.cpp index 00c8d8e5d..32feb5fbb 100644 --- a/bindings/python/mapnik_text_symbolizer.cpp +++ b/bindings/python/mapnik_text_symbolizer.cpp @@ -48,17 +48,6 @@ void set_text_displacement(text_symbolizer & t, boost::python::tuple arg) t.set_displacement(extract(arg[0]),extract(arg[1])); } -tuple get_anchor(const text_symbolizer& t) -{ - position pos = t.get_anchor(); - return boost::python::make_tuple(boost::get<0>(pos),boost::get<1>(pos)); -} - -void set_anchor(text_symbolizer & t, boost::python::tuple arg) -{ - t.set_anchor(extract(arg[0]),extract(arg[1])); -} - } struct text_symbolizer_pickle_suite : boost::python::pickle_suite @@ -76,7 +65,6 @@ struct text_symbolizer_pickle_suite : boost::python::pickle_suite getstate(const text_symbolizer& t) { boost::python::tuple disp = get_text_displacement(t); - boost::python::tuple anchor = get_anchor(t); // so we do not exceed max args accepted by make_tuple, // lets put the increasing list of parameters in a list @@ -95,7 +83,7 @@ struct text_symbolizer_pickle_suite : boost::python::pickle_suite return boost::python::make_tuple(disp,t.get_label_placement(), t.get_vertical_alignment(),t.get_halo_radius(),t.get_halo_fill(),t.get_text_ratio(), t.get_wrap_width(),t.get_label_spacing(),t.get_minimum_distance(),t.get_allow_overlap(), - anchor,t.get_force_odd_labels(),t.get_max_char_angle_delta(),extras + t.get_force_odd_labels(),t.get_max_char_angle_delta(),extras ); } @@ -136,15 +124,10 @@ struct text_symbolizer_pickle_suite : boost::python::pickle_suite t.set_allow_overlap(extract(state[9])); - tuple anch = extract(state[10]); - double x = extract(anch[0]); - double y = extract(anch[1]); - t.set_anchor(x,y); + t.set_force_odd_labels(extract(state[10])); - t.set_force_odd_labels(extract(state[11])); - - t.set_max_char_angle_delta(extract(state[12])); - list extras = extract(state[13]); + t.set_max_char_angle_delta(extract(state[11])); + list extras = extract(state[12]); t.set_wrap_char_from_string(extract(extras[0])); t.set_line_spacing(extract(extras[1])); t.set_character_spacing(extract(extras[2])); @@ -211,9 +194,6 @@ void export_text_symbolizer() */ //.def_pickle(text_symbolizer_pickle_suite()) - .add_property("anchor", - &get_anchor, - &set_anchor) .add_property("allow_overlap", &text_symbolizer::get_allow_overlap, &text_symbolizer::set_allow_overlap, diff --git a/include/mapnik/text_symbolizer.hpp b/include/mapnik/text_symbolizer.hpp index d6c023499..8f853020b 100644 --- a/include/mapnik/text_symbolizer.hpp +++ b/include/mapnik/text_symbolizer.hpp @@ -98,8 +98,6 @@ struct MAPNIK_DECL text_symbolizer : public symbolizer_base label_placement_e get_label_placement() const; void set_vertical_alignment(vertical_alignment_e valign); vertical_alignment_e get_vertical_alignment() const; - void set_anchor(double x, double y); - position const& get_anchor() const; void set_displacement(double x, double y); void set_displacement(position const& p); position const& get_displacement() const; @@ -143,7 +141,6 @@ private: color halo_fill_; double halo_radius_; label_placement_e label_p_; - position anchor_; bool avoid_edges_; double minimum_distance_; double minimum_padding_; diff --git a/src/text_symbolizer.cpp b/src/text_symbolizer.cpp index 00a91401d..e10ae4e44 100644 --- a/src/text_symbolizer.cpp +++ b/src/text_symbolizer.cpp @@ -107,7 +107,6 @@ text_symbolizer::text_symbolizer(expression_ptr name, std::string const& face_na halo_fill_(color(255,255,255)), halo_radius_(0.0), label_p_(POINT_PLACEMENT), - anchor_(0.0,0.5), avoid_edges_(false), minimum_distance_(0.0), minimum_padding_(0.0), @@ -140,7 +139,6 @@ text_symbolizer::text_symbolizer(expression_ptr name, float size, color const& f halo_fill_(color(255,255,255)), halo_radius_(0.0), label_p_(POINT_PLACEMENT), - anchor_(0.0,0.5), avoid_edges_(false), minimum_distance_(0.0), minimum_padding_(0.0), @@ -173,7 +171,7 @@ text_symbolizer::text_symbolizer(text_symbolizer const& rhs) halo_fill_(rhs.halo_fill_), halo_radius_(rhs.halo_radius_), label_p_(rhs.label_p_), - anchor_(rhs.anchor_), + avoid_edges_(rhs.avoid_edges_), minimum_distance_(rhs.minimum_distance_), minimum_padding_(rhs.minimum_padding_), @@ -205,7 +203,6 @@ text_symbolizer& text_symbolizer::operator=(text_symbolizer const& other) halo_fill_ = other.halo_fill_; halo_radius_ = other.halo_radius_; label_p_ = other.label_p_; - anchor_ = other.anchor_; avoid_edges_ = other.avoid_edges_; minimum_distance_ = other.minimum_distance_; minimum_padding_ = other.minimum_padding_; @@ -428,16 +425,6 @@ label_placement_e text_symbolizer::get_label_placement() const return label_p_; } -void text_symbolizer::set_anchor(double x, double y) -{ - anchor_ = boost::make_tuple(x,y); -} - -position const& text_symbolizer::get_anchor() const -{ - return anchor_; -} - void text_symbolizer::set_displacement(double x, double y) { placement_options_->set_default_displacement(boost::make_tuple(x,y)); From a1c80d1a4c5e9762ba05c58e34d62a446a97a603 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 20 Jan 2012 00:18:37 +0100 Subject: [PATCH 03/81] Add full set of tests for text placement. --- tests/data/placement/list-100-reference.png | Bin 0 -> 1690 bytes tests/data/placement/list-150-reference.png | Bin 0 -> 2723 bytes tests/data/placement/list-200-reference.png | Bin 0 -> 3710 bytes tests/data/placement/list-250-reference.png | Bin 0 -> 3632 bytes tests/data/placement/list-300-reference.png | Bin 0 -> 3663 bytes tests/data/placement/list-400-reference.png | Bin 0 -> 4469 bytes tests/data/placement/list-600-reference.png | Bin 0 -> 4224 bytes tests/data/placement/list-800-reference.png | Bin 0 -> 4148 bytes tests/data/placement/list.xml | 27 +++++++++++ tests/data/placement/points.dbf | Bin 0 -> 889 bytes tests/data/placement/points.osm | 43 ++++++++++++++++++ tests/data/placement/points.shp | Bin 0 -> 380 bytes tests/data/placement/simple-100-reference.png | Bin 0 -> 2269 bytes tests/data/placement/simple-150-reference.png | Bin 0 -> 3484 bytes tests/data/placement/simple-200-reference.png | Bin 0 -> 3909 bytes tests/data/placement/simple-250-reference.png | Bin 0 -> 4362 bytes tests/data/placement/simple-300-reference.png | Bin 0 -> 4645 bytes tests/data/placement/simple-400-reference.png | Bin 0 -> 5922 bytes tests/data/placement/simple-600-reference.png | Bin 0 -> 5600 bytes tests/data/placement/simple-800-reference.png | Bin 0 -> 4402 bytes tests/data/placement/simple.xml | 24 ++++++++++ 21 files changed, 94 insertions(+) create mode 100644 tests/data/placement/list-100-reference.png create mode 100644 tests/data/placement/list-150-reference.png create mode 100644 tests/data/placement/list-200-reference.png create mode 100644 tests/data/placement/list-250-reference.png create mode 100644 tests/data/placement/list-300-reference.png create mode 100644 tests/data/placement/list-400-reference.png create mode 100644 tests/data/placement/list-600-reference.png create mode 100644 tests/data/placement/list-800-reference.png create mode 100644 tests/data/placement/list.xml create mode 100644 tests/data/placement/points.dbf create mode 100644 tests/data/placement/points.osm create mode 100644 tests/data/placement/points.shp create mode 100644 tests/data/placement/simple-100-reference.png create mode 100644 tests/data/placement/simple-150-reference.png create mode 100644 tests/data/placement/simple-200-reference.png create mode 100644 tests/data/placement/simple-250-reference.png create mode 100644 tests/data/placement/simple-300-reference.png create mode 100644 tests/data/placement/simple-400-reference.png create mode 100644 tests/data/placement/simple-600-reference.png create mode 100644 tests/data/placement/simple-800-reference.png create mode 100644 tests/data/placement/simple.xml diff --git a/tests/data/placement/list-100-reference.png b/tests/data/placement/list-100-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..3b3cedbb2c5c6545c372cc0a155f8011dc777930 GIT binary patch literal 1690 zcmcJQ`8yK~0LPUh??|rKF;|XqWE7>EV~ja+G=!RYji|9rHMB-ZMfDaVM$DC?hUPxf zVK;WQ*y^Y&m2mGg~ z$X;ImOuir>AmZm>V|C$H7KegylD#S$&sT#`MybVXPe-X9r9tBD)VFCR3CZk3N|BF& zZMPhpc3g3T&lKDv4S}$86g~SkH^jX=dD5~qf1#uH?s^YiKiSW7v^vI;t+D(SMLhwH)e^eB4TDn5jDxYWpTvZ}NSG#9=Wwo%pS4Reeu z+G0-?%-JPJXnORFJjS_w)53O-JsfqcyqMddn(=4GV(iD2>Jz*Dx1ACwC8po3_=4i( z5!OO$$(qL~xk-%Tf^FG&w!*%_f7YvMyL%kyjjyAQ*G=Hk7DL0pw=dPiElI%Kf}R#_ zwH%5s-AI#bw8EY>ErkitpLR)e!10ziyl*cSvQc z(Jaiv%E)Fw^-!64{~KI?A3Sf!jw#!YvK|(3B(8%^=a2NRa?0(!V|%nuf9Gcd}yHfP78Sqt3>fKi`xVD|axyap?)`wIn<9baTiERhlg1hpf&z~-0y81U*_;2+M zemHAo7|VUZbasVtD!;#dJvmzezFb?@XjQ8J#t%cH@i-JIBV>;JB5fPM7X}&XX#yfS zkswy`mmr9(IHk+Q!Pzly{bWht_PXGih1-HYACu0YL znT`SY!ZBd?{D58$#SSt!m=SF}8I~iTnU(NRMVEwTqv1!kNleSpqwOxtyM@Bf!clTZ zch7u0z2^0T#=IXTBgqYXs5iG<+o~Bq@ct&%Y>dr{a}YZ*o33~*(KvNe%!mdCB!dWz^K`1L)OlMo|p`}-}HkaZ8#=` zyruSfB^&=OTn75X&gQ1V_3x=G`*#{P)ZvO(ee8Wn8aEs9gFxDd&03vcao-^%VWk6+ zeT7X0>N6MMLQT{F$WPCePHj1sBv4VYT!%B0SfnuYLihaY%T9K+xBPq05_2uDI-N^1 z&9$669XQZxOJp;z{En$E;z|cD^z2WlGO;-WCb)f$K~xXmwcrC4VlZH6QN6QbZ)lB$ znz;H9DTUls0?sp!C4NVPx&dJcFQ^8?A;#-YGWS!nAB!!{B?gGOb{ zYjwWaNFLt428`+Gv>QJ){Vr4xH1ATChLJ;{1L!6dyL@@eVg}(+Zi}z|DvUEmpRhpV zC3KN5dGArn%X0lmG~8)ET6k}y`l{&~JYgIe20x}h%m=cP!X`h=Yn7uATB&xFF0MS( zCpMT9GO*rfXslki@@iNb9S)nR`EWJkSbr|6M*r@jL(HWv@Me62$3RMdFevgx)wu}o zpjfd~rBSSzX3UYKv-|A-7+AX0Gs~Yq3k}k{6X3So1AWT}tk3%{dm=uWrT=VNJub9E pcPd}!ZdM=r67#pHU;OW+9a8DJ5r6|#$nAZOfCJ3MrVNU@`!9Z_B^3Yw literal 0 HcmV?d00001 diff --git a/tests/data/placement/list-150-reference.png b/tests/data/placement/list-150-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..61dc7964dedc354d03f0e101b37204bb63339a53 GIT binary patch literal 2723 zcmchZ=Q|q;1I2|}sk9fhqDHH!5xcdiQHr{%Xb6hfr7=)Ly-s8vbK zc&XUqqO>7Ks#fp#`u+j;!#U?U&vQPX=lAyUBSR1n3}j$n02v$UTl|w5|Hze%%jL0_kEuA!-k*#c{#YyR3onItpVz~kzPvv_D@3rzdbemJ(>`XgtXgE|?j ziFYNRxQ}UgWO}o^TrR(<>#@Xq3KRlwjEE2Z{`+>;&V!I+Mgn2F)xL9K zy11CJtcoK{R)G_GY>vL!4up7lXxVzs|fOwk^=j{90f* zXE9O(&3JJN=tMKQx$V|?zJi63h$Py`n#Y7jhf3)mW}nmIEw9gq|FyiQaL>D|9TY3_ z#;Vaytz{7#3Kf1EIxynDLdNmusL#}U+xmco&yHQ*w|IipNB3MaN@QukO43W&%~bcn z;eq6gzJ>;R#M*}~EOvEj2I4uv(1uZ<&CWIVMvE{ux&tz0%Uzh6dl%Y!;U)XL(hLbA ztQ~=OfT~>Yl}+>b60QT@Z~#t^0HtQupSidq*|{SPVxP!~S2=*LYweKkND(uSeesg> zqD6Mo=pQo_{??$}Kp}7BMOKnBz*6N=AQGyV(#5>p4i1|huBT446&a}|Iq&Y>YZkm3 zc9G;w$z9`_8K84dLOb}l!p(Y5NL!VVCT=U!mc9|0I|^QOiEk;FE`OW%#r1^Y0w1`9 z9Pri#MdE ziGa@*-3!fknIAP&DcQRkmi#_7W>T_be?L(~qlGFmB_epmSN_;P_64W1B!`DwUbXuk zZ*3hBO5|)6AN&4L(99RvAs6xeH7u3d(&f#T2sG3+#swO`jGC^=Ta9*!jN?F%cnbrc ziYB*6S?)k&|^J?Cxl@UNlKf;Ls`t?<8nmbr|J8FzS zHBzpJK|Ro7*Uax`)lfXHPp?_dhn63b77|8>AzopfQB?3m88JSl&Z;H=wtRyWjTp)h zSn^lgt*=Cv1!d$wwGwke2lEJpPm|bF+xjkN%BhZ_9ar!9hh~IJ3xP_NdLmPBaD_*fHvUxU zB5E3s-^a+Bp9!i|2><@`++S4Df?DfXOT015Mylvh)X8wTFChzka1nF5X&T@R{nK$+ zWwEgBzRF2Y_l~#pQuAz%?_t&`s{OoSJv8ba5vcDIKR|oEdgCxC)*7y8l4SErQ^7)h zeB|ajhGlNl(?hC#aD?~k5BpXxzWN4gd)pr;M={+#$;gq4f=d01MZ1>U7-MwBwdSctG%JLJf z9E6y3au2=J^Jj8jX>k|d7z@*8G_21Ve0Azz3P`}pf#}<9sX6dR%{blus&<` zw5V--Cb@c4;XT!FkQq?(7`!>#?y}iLk!h}PHh8)$fJY+~Wz|t@rw$@9R6=TxK#}&~ zV2!MswPUcO>z6AXb2iw@Z}sU1GlP+fB0a(4|Nrl7l2vteb&;df%$_E8kKs)p>z;*( zQ7qt1)6S$%8k3As22DCnoHCE$`j@ZqPWFKyRa1X3X}8||3;&v+;vY1xdREB&*+?`l zw8tT*iA!MfNgs{C6B4T)a-c(3M2`!-HC5O-+~ z({tLr)O2meabT4n;cbn8nXH!|pJ`s4hl{FRpFLBexriVfKO8@agr?>ESc}CHA2?lr zj6?(^s;DKn3s`}!qby@eHY3lMk%ELMh~rVuKFdTYh}O%r!+lgGy&QaE3)lN;3ohTk&@5XCv zr3#~y4P$wwo~g-zV&Y+wHe4u3^6m?J_6_b3N=k}e=Avvhn@MqtdH_S|BUt^1l-hF- zT}GrPhTQC3iPdIz&FFO(r~9RBmO|igNh;16|KMa?y9+PG{(OLKWw(=Q9dzrGS-!hx z00n;$fKROle)U_oy)nWgU2BsSG|Z& z(h|j-7SRRo9%c?Yowu&`!{i`FN9k<|KQV=6Wv5Cz58>zqbFJYAH}@eGB<;oOR;kJM z>gd$FDl(t?=C}c_zbvT*N<%-|zp+;C4862Oo(b;4;l3W9(?G^_kSm-`YIE-li4Jjh7vWJ$ z{OD`QgY5rFNTZkzz|Q1b9&SmQ?@_1QXoO&{JFJ+V z`Ds5+U?rKPW;T=>ycQa0*b z*rfDOQ8iO=m*SemWMg)JMm^+cve#d+@jyC9bU4z^3W+4^gQ_+_cpfXy=g_L`oz>f* z`pES)-*zYXvo`RWlky#cEm=qfJ$n(*D${71WLu zyGD%GY7twk7^Oz=@BjMyZolWdc;9n#F3$75=XuU^;%`}*aIjutWnf_7Ff%o@`HS6u z#{+o&?{33l*ccc%jm!-7?}TPd{+6%C|fQ}b*J3aCnjaY`d&&bcYNg~snCr}`g+H|-J|V6=4D@;cl*{JGB_ee|EA zKkF#+*?J{8a{rWeQhf&HFnDom_;dpRw0`?aFODD<4Rq57aFb$~#N(JR7y{XNUU2bC za$Qz16*IV-@_$bN@^+UxH>jZdeSc?+fo`8_GHahszcQW4su70S_qUJ9Hz%P|=@G8$ zL0K0Y~8DN)h8Nk3u-M+ zLV!irPPN^sK=v~~Ese9PIztW8q{{A12h|JL!JrQ8284Au0k}XG#{BD${qTWiweS(W z1~i%Elb|}2Y!-nsYc?M|i0v#$cY#d{pPgt97UJQT)B&sC&9cr;(MavJF-CR9(hW;Q z1HaaCX6T~%Gtp}|kFhxF~PZPw8yoh@pM3I)7_!_sve>pA?5*YGi+=F861zW=h)7-SIo}&w>_#U?uhr#dO>$S zjNeVlMj6uXHD1h40LFcjJ$o32JpKaVFLl0Kygh!vOkyo@5e{8bq*RilCaa=ti*RhM zLs*W8)jO`!S6z>gl&>REJ|j`N=6DSv6`_57ncIW`q{D;@3{R{a07TM>jLI9$~t(A42Q-WMke z20lOORn|114AsH5mNHs2XBa}(-dneSKp2LUC+SZ9_Fc$Li~5bkN3MKoL0~4zAa6xt z2}c=t9jGcjn3Kd(>-xAaEDj+?TyBlEgu{nFgyjW#m(>DRh8VjCtd=-}%VT!_{%WP| zND{dxQ%oT8cCVFX5F_~Wvo0NKw5^sIhbJ25(9(ZgOr0sfjpjIXRqN9qYX$pA`w2cV zZ-q~({vnRr7JD~G@c+RzLBEIk(gCwukwZp#sNiJPPK3paSe=_C4xQ?9JwkE&J}?=b zmHO(PHw>7QI>PhS)F1L$s?B;UcDjcivlYXsv&2#~3O1JGXhdS-d8664`Ci=%+260u zk>z=z*E||0lujJD0Bvs=n+jb^zW8{5lUg}46!LJaoqO#3cFioc*}|kPnuBfNm`qD% z>C{O%a55BwI|dV^G!MDL`&ObM*k4=^uWCd__kCc?40>6J1n+CDKXW>8g(G2PL2uqF zs%5fV3gN%jmn=RFLM=(#-Qej6(XI4`)cJb6OLwIM>Q2Mw=MH+QCE8Dve8;Ei&pkcn zuM+&LRjF=O4mMM}ri2!}A(JvUDcUognK()NuB0Z{UOwyJ_H(ED)9_jd@s|`MwZsnHODT(rQ~M{wyZ-R0Af<;fYw= zQeV2qQB``n*IYkY(Sd>E(mSmAL4cO#r{=W|OU%Nn2Y?F=rHVQLw>?cww4Zn*^He@XQo6E6ONqo`mGj`|=x4=y05v~$HDvg=#a_I| ze=Q-EnZI6>oerMdd3#H&dcVuI*q9L_^=kxoC@59IJ&L{N@Y|a7Ea!y3&C%7nDRAvN zhUM^aum9be5a+?MTt>7=X>};t&q{iNa7JC59G2j+!wA7geH|*Hxg1!(*QmNo((#^s z7j(j!J~O#(7HUYM5|v(L;la)F(?O^nVZWa*SDEH zZ|?7lqj_Ge_hczz?kRu#i%jC^3VY)XpVvzNTYBxg9W;+%4rXVRj3%TL(ViquO|kAQ zlGeFtMRMUc$1lE4>n4)=q}(?17V6M_@2i%LMokZ`?s#vr!E!J~BALnR+7wQ&ShUke`_O|D#rS$Jc&%i1kuV`1QEE@) zWa$7B*k3Jzz%426zgDlg>a}NN?~A&Y-=lT+Y6VcY+3P%t9S?Y^LH{JFi_lH*uV-ycQC_giLHBUuYzj?<;i{p!0Yf1}ew zZ+vn&Bhk;b>Y-l4lb&D=Y%J%M7!h^WTAJ=Iik*?qbW^mY-CTIjU!E^oW$DthZv_TK z%}%x2L$8b0$ZdE0bKxN~Xu8<5i_lB@uuC8bh^im+#uZ|k65$ zXYPTr0i7}AOY7qR?TQEZ4UZ>mrNaME= zn{e2|^Nu%G&k{NWf-F#s87cs2Mp>7Lz$;ej^YSXmkfV6gEFA5uT;h<KixVwY++Oc7$d>*FH1g}{ljQu z6E*YH8-U+J8$V5C0&8?;rCvoW{I+0|=oC}Rxywu%ifL4vuTkx;MF-@@Q*nK5Wctq} zVN11d5r~%fJHWUcHc?(@8Iq}<5Qo6f>$YoxT7z$TIEq`b`i>b-^;??h!=Hl}lXr#H}dkKxW4Ou*6=rDjD);7Z8A&5q63Da zm1K>2!{-V_(o5u{hw`DzVr)Xqp@l}D(C6%;RoZz+r~oEmKi(U+_cBR8PufxiP*zBO=6smBy`gn*xOrCuE0LM%#jkhm@1t( zqdM2b23@qsprCJZK3#~j>_nzH~4l(8NeE> zrvmVBH5G5GAU>%V1R}CzrKvn~I@h$RGuB6lN>nbIb9&)P@yPNwp+Fb})bSkRi`SA4 zffs>@dnA#}qDM{)v){PZQi{x-_U0=T5F@R}UboD$D!I|xvZHKd>hzU&^{Du9b6&^D z=m7oL#rs{REA>~Ll${kdWlo!n%K>g};amFi+At7kza`DtYr2snlGunl76xeql*ZI< z>F=O;blD?m7*GP(ar&np%qQaUb2bEo^z}T)%gMP4x$8sWy;JoeL#V;emwwX3@Y3n= zl{HP{_jf}ii*Gg!v>ywvMdxI(JtH65=H6I}BpFN_moM@Gvvo}To65S`o>^$Vee8;m z+`-r_+~9$n{A*-{;BTiL#PXOVtLJisKe;)}$_a`3)2Mo;;oGu4MO{6#Kh=5|WR$X< sH{V1Wx+(W~BVh%z%liM)s7^s1Q4V6?=i2@L_DTjbBP+vF1J{`U0M#=G=Kufz literal 0 HcmV?d00001 diff --git a/tests/data/placement/list-250-reference.png b/tests/data/placement/list-250-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..a88f4707b6ca3547a0022e9158f9f246e2a6ad67 GIT binary patch literal 3632 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 R^Y2w5s+V!9{>OV?-(220|3~R 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 I8o)yT2cJ;}EdT%j literal 0 HcmV?d00001 diff --git a/tests/data/placement/list-400-reference.png b/tests/data/placement/list-400-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..caee65ba51dbb2e401430b9f986361f0da6b1643 GIT binary patch literal 4469 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 literal 0 HcmV?d00001 diff --git a/tests/data/placement/list-600-reference.png b/tests/data/placement/list-600-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..46a5c7198665b1d7f1c8837fde43fea1352b3fd0 GIT binary patch literal 4224 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 AGXMYp literal 0 HcmV?d00001 diff --git a/tests/data/placement/list-800-reference.png b/tests/data/placement/list-800-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..b362592ef8cf609beb997853c66b50d9d4c99fbc GIT binary patch literal 4148 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 N)zZPD{<8nw{{RM3z~cY_ literal 0 HcmV?d00001 diff --git a/tests/data/placement/list.xml b/tests/data/placement/list.xml new file mode 100644 index 000000000..93ef3726d --- /dev/null +++ b/tests/data/placement/list.xml @@ -0,0 +1,27 @@ + + + + + + My Style + + + shape + points.shp + + + + + + diff --git a/tests/data/placement/points.dbf b/tests/data/placement/points.dbf new file mode 100644 index 0000000000000000000000000000000000000000..ca544e92a08c9bbb634df1471ce88de35dc1280f GIT binary patch literal 889 zcma))O$)*x7{`SUK@oKB*sPv! z76_py+P(MhCAy|&Cz)O)5np~x8v?I|Ge0Yrtd2drBTkV! z-dHFUiw=~V^p`5UC6tmy+2z`MhbBciakhat${?6)rY + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/placement/points.shp b/tests/data/placement/points.shp new file mode 100644 index 0000000000000000000000000000000000000000..e2fb4a604d7ac56c58e6b4c968a7b504c686e425 GIT binary patch literal 380 zcmZQzQ0HR64)(oZW?*0h%FUVy0z2(t%Fdnvfj4kIOdJmlGKLF8U;#`(F|ZuEJwS1w z3y|ebBHPIVlQRZ`%gA!9Fgb{sH<9JoU~)hj2p%BIu>%FbZh@Hj7+H=3CYJ^V&yeLf TVR8`rULwnZ!WV2Ndbj}qP8u%M literal 0 HcmV?d00001 diff --git a/tests/data/placement/simple-100-reference.png b/tests/data/placement/simple-100-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..e70c9c0f67583539ea89da4a4ce6ea775a7fde51 GIT binary patch literal 2269 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 abjMe-tgtT%G-h literal 0 HcmV?d00001 diff --git a/tests/data/placement/simple-200-reference.png b/tests/data/placement/simple-200-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..aad33f40564e8c2dea4f4fed6f47e791fee5e6e1 GIT binary patch literal 3909 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 LFx5xs+DHEjaiEq0 literal 0 HcmV?d00001 diff --git a/tests/data/placement/simple-300-reference.png b/tests/data/placement/simple-300-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..f753151da4ff10351e5a768028fc9d472829453b GIT binary patch literal 4645 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 YwA-Z)Nzifxdfy}(k9Abal+1(v3)u*7O#lD@ literal 0 HcmV?d00001 diff --git a/tests/data/placement/simple-600-reference.png b/tests/data/placement/simple-600-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..ffc72053a5ff3126b37252e830ae214e3067b8f7 GIT binary patch literal 5600 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 literal 0 HcmV?d00001 diff --git a/tests/data/placement/simple-800-reference.png b/tests/data/placement/simple-800-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..19c7acd32e862bd85229d9229f18943d06fddfc3 GIT binary patch literal 4402 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 W+~wOxn?oB&U%FstS#{p~?!N=LYH_6i literal 0 HcmV?d00001 diff --git a/tests/data/placement/simple.xml b/tests/data/placement/simple.xml new file mode 100644 index 000000000..cc008f598 --- /dev/null +++ b/tests/data/placement/simple.xml @@ -0,0 +1,24 @@ + + + + + + My Style + + + shape + points.shp + + + + + + From 047cda5d1b500655da747570647c054d112c888a Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 20 Jan 2012 00:45:15 +0100 Subject: [PATCH 04/81] Fix some tests. --- tests/data/good_maps/also_and_else_filter.xml | 2 +- tests/data/good_maps/sqlite_attachdb.xml | 2 +- tests/data/good_maps/unique_filter_map.xml | 2 +- tests/python_tests/object_test.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/data/good_maps/also_and_else_filter.xml b/tests/data/good_maps/also_and_else_filter.xml index c4acbe946..10d7c3ac5 100644 --- a/tests/data/good_maps/also_and_else_filter.xml +++ b/tests/data/good_maps/also_and_else_filter.xml @@ -14,4 +14,4 @@ - \ No newline at end of file + diff --git a/tests/data/good_maps/sqlite_attachdb.xml b/tests/data/good_maps/sqlite_attachdb.xml index 2703bd165..476e4c853 100644 --- a/tests/data/good_maps/sqlite_attachdb.xml +++ b/tests/data/good_maps/sqlite_attachdb.xml @@ -33,4 +33,4 @@ - \ No newline at end of file + diff --git a/tests/data/good_maps/unique_filter_map.xml b/tests/data/good_maps/unique_filter_map.xml index acb3485e8..796d90c6d 100644 --- a/tests/data/good_maps/unique_filter_map.xml +++ b/tests/data/good_maps/unique_filter_map.xml @@ -22,4 +22,4 @@ shape - \ No newline at end of file + diff --git a/tests/python_tests/object_test.py b/tests/python_tests/object_test.py index 9313bc507..a4caf1c3a 100644 --- a/tests/python_tests/object_test.py +++ b/tests/python_tests/object_test.py @@ -29,7 +29,7 @@ def test_shieldsymbolizer_init(): eq_(s.allow_overlap, False) eq_(s.avoid_edges, False) eq_(s.character_spacing,0) - eq_(str(s.name), str(mapnik.Expression('[Field Name]'))) + #eq_(str(s.name), str(mapnik2.Expression('[Field Name]'))) name field is no longer supported eq_(s.face_name, 'DejaVu Sans Bold') eq_(s.allow_overlap, False) eq_(s.fill, mapnik.Color('#000000')) @@ -198,7 +198,7 @@ def test_linesymbolizer_init(): def test_textsymbolizer_init(): ts = mapnik.TextSymbolizer(mapnik.Expression('[Field_Name]'), 'Font Name', 8, mapnik.Color('black')) - eq_(str(ts.name), str(mapnik.Expression('[Field_Name]'))) +# eq_(str(ts.name), str(mapnik2.Expression('[Field_Name]'))) name field is no longer supported eq_(ts.face_name, 'Font Name') eq_(ts.text_size, 8) eq_(ts.fill, mapnik.Color('black')) From 6efb7a863db831313852986f98d08de685fe16f0 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 20 Jan 2012 00:56:20 +0100 Subject: [PATCH 05/81] Add default constructor for shield symbolizer. --- src/shield_symbolizer.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/shield_symbolizer.cpp b/src/shield_symbolizer.cpp index fcf54a73e..5de905935 100644 --- a/src/shield_symbolizer.cpp +++ b/src/shield_symbolizer.cpp @@ -34,6 +34,15 @@ namespace mapnik { +shield_symbolizer::shield_symbolizer(text_placements_ptr placements) + : text_symbolizer(placements), + symbolizer_with_image(), + unlock_image_(false), + no_text_(false), + shield_displacement_(boost::make_tuple(0,0)) +{ +} + shield_symbolizer::shield_symbolizer( expression_ptr name, std::string const& face_name, From 7d8924921cb09f192d40b64e56323a2aaa270ebe Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 20 Jan 2012 22:14:08 +0100 Subject: [PATCH 06/81] Fix header for ShieldSymbolizer (6efb7a863d). Add more default constructors. --- include/mapnik/shield_symbolizer.hpp | 2 ++ include/mapnik/symbolizer.hpp | 2 +- include/mapnik/text_symbolizer.hpp | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/mapnik/shield_symbolizer.hpp b/include/mapnik/shield_symbolizer.hpp index 847bff663..b5aaf94e8 100644 --- a/include/mapnik/shield_symbolizer.hpp +++ b/include/mapnik/shield_symbolizer.hpp @@ -36,6 +36,8 @@ namespace mapnik struct MAPNIK_DECL shield_symbolizer : public text_symbolizer, public symbolizer_with_image { + shield_symbolizer(text_placements_ptr placements = text_placements_ptr( + boost::make_shared())); shield_symbolizer(expression_ptr name, std::string const& face_name, float size, diff --git a/include/mapnik/symbolizer.hpp b/include/mapnik/symbolizer.hpp index 546c4c1fe..062b5b781 100644 --- a/include/mapnik/symbolizer.hpp +++ b/include/mapnik/symbolizer.hpp @@ -96,7 +96,7 @@ public: void set_opacity(float opacity); float get_opacity() const; protected: - symbolizer_with_image(path_expression_ptr filename); + symbolizer_with_image(path_expression_ptr filename = path_expression_ptr()); symbolizer_with_image(symbolizer_with_image const& rhs); path_expression_ptr image_filename_; float opacity_; diff --git a/include/mapnik/text_symbolizer.hpp b/include/mapnik/text_symbolizer.hpp index 8f853020b..c1ae0a8ca 100644 --- a/include/mapnik/text_symbolizer.hpp +++ b/include/mapnik/text_symbolizer.hpp @@ -45,6 +45,9 @@ struct MAPNIK_DECL text_symbolizer : public symbolizer_base { // Note - we do not use boost::make_shared below as VC2008 and VC2010 are // not able to compile make_shared used within a constructor + text_symbolizer(text_placements_ptr placements = text_placements_ptr( + boost::make_shared()) + ); text_symbolizer(expression_ptr name, std::string const& face_name, float size, color const& fill, text_placements_ptr placements = text_placements_ptr(new text_placements_dummy) From 7a4dda929a011dc8a360e843d9959759d9d77a30 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 20 Jan 2012 22:19:48 +0100 Subject: [PATCH 07/81] Add H_AUTO to python bindings. --- bindings/python/mapnik_text_symbolizer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/python/mapnik_text_symbolizer.cpp b/bindings/python/mapnik_text_symbolizer.cpp index 32feb5fbb..31515f11a 100644 --- a/bindings/python/mapnik_text_symbolizer.cpp +++ b/bindings/python/mapnik_text_symbolizer.cpp @@ -163,6 +163,7 @@ void export_text_symbolizer() .value("LEFT",H_LEFT) .value("MIDDLE",H_MIDDLE) .value("RIGHT",H_RIGHT) + .value("AUTO",H_AUTO) ; enumeration_("justify_alignment") From 2a95f7271b0ef196fdba708586aa37c61b77d94e Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 20 Jan 2012 22:22:14 +0100 Subject: [PATCH 08/81] Add char_info.hpp --- include/mapnik/char_info.hpp | 52 ++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 include/mapnik/char_info.hpp diff --git a/include/mapnik/char_info.hpp b/include/mapnik/char_info.hpp new file mode 100644 index 000000000..a1417735f --- /dev/null +++ b/include/mapnik/char_info.hpp @@ -0,0 +1,52 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2011 Artem Pavlenko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + *****************************************************************************/ + +#ifndef CHAR_INFO_HPP +#define CHAR_INFO_HPP + +#include + +namespace mapnik { +struct char_properties; + +class char_info { +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_) + { + } + char_info() + : c(0), width(0), line_height(0), ymin(0), ymax(0) + { + } + + unsigned c; + double width; + double line_height; + double ymin; + double ymax; + double avg_height; + char_properties *format; + double height() const { return ymax-ymin; } +}; +} +#endif From b14e6c57db62de6b20e4c597751aa3b0715001cf Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 20 Jan 2012 22:22:42 +0100 Subject: [PATCH 09/81] Remove unused include. --- include/mapnik/grid/grid.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/mapnik/grid/grid.hpp b/include/mapnik/grid/grid.hpp index 801422bfd..37c7172af 100644 --- a/include/mapnik/grid/grid.hpp +++ b/include/mapnik/grid/grid.hpp @@ -24,7 +24,6 @@ #define MAPNIK_GRID_HPP // mapnik -#include #include #include #include From e553dbcd828f9326a2e24e220419e63f5a7b2352 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 20 Jan 2012 22:23:14 +0100 Subject: [PATCH 10/81] Add missing includes. --- include/mapnik/jpeg_io.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/mapnik/jpeg_io.hpp b/include/mapnik/jpeg_io.hpp index f5d96bc71..0f681d017 100644 --- a/include/mapnik/jpeg_io.hpp +++ b/include/mapnik/jpeg_io.hpp @@ -27,8 +27,11 @@ #include +#include + extern "C" { +#include #include } From abae1a1fec490645fa17b1ab278fb6bc6deef74f Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 20 Jan 2012 22:28:06 +0100 Subject: [PATCH 11/81] Remove unused file. --- include/mapnik/label_placement.hpp | 53 ------------------------------ workspace/mapnik.pro | 1 - 2 files changed, 54 deletions(-) delete mode 100644 include/mapnik/label_placement.hpp diff --git a/include/mapnik/label_placement.hpp b/include/mapnik/label_placement.hpp deleted file mode 100644 index a667516a9..000000000 --- a/include/mapnik/label_placement.hpp +++ /dev/null @@ -1,53 +0,0 @@ -/***************************************************************************** - * - * This file is part of Mapnik (c++ mapping toolkit) - * - * Copyright (C) 2011 Artem Pavlenko - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - *****************************************************************************/ - -#ifndef MAPNIK_LABEL_PLACEMENT_HPP -#define MAPNIK_LABEL_PLACEMENT_HPP - -namespace mapnik -{ -struct point_ -{ - double x; - double y; - point_() - : x(0),y(0) {} - point_(double x_,double y_) - : x(x_),y(y_) {} -}; - -class label_placement -{ -private: - point_ anchor_; - point_ displacement_; - double rotation_; -public: - label_placement() - : anchor_(), - displacement_(), - rotation_(0.0) {} - -}; -} - -#endif // MAPNIK_LABEL_PLACEMENT_HPP diff --git a/workspace/mapnik.pro b/workspace/mapnik.pro index a297e8fc9..5f9f9c615 100644 --- a/workspace/mapnik.pro +++ b/workspace/mapnik.pro @@ -91,7 +91,6 @@ HEADERS += \ ../include/mapnik/image_view.hpp \ ../include/mapnik/jpeg_io.hpp \ ../include/mapnik/label_collision_detector.hpp \ - ../include/mapnik/label_placement.hpp \ ../include/mapnik/layer.hpp \ ../include/mapnik/libxml2_loader.hpp \ ../include/mapnik/line_pattern_symbolizer.hpp \ From 6a50f91a10537f797fef215defd55b6a802f99e8 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 20 Jan 2012 22:30:12 +0100 Subject: [PATCH 12/81] Add text_processing.hpp/cpp --- include/mapnik/text_processing.hpp | 125 ++++++++ src/text_processing.cpp | 447 +++++++++++++++++++++++++++++ 2 files changed, 572 insertions(+) create mode 100644 include/mapnik/text_processing.hpp create mode 100644 src/text_processing.cpp diff --git a/include/mapnik/text_processing.hpp b/include/mapnik/text_processing.hpp new file mode 100644 index 000000000..1dce42c93 --- /dev/null +++ b/include/mapnik/text_processing.hpp @@ -0,0 +1,125 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2011 Artem Pavlenko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + *****************************************************************************/ +#ifndef MAPNIK_TEXT_PROCESSING_HPP +#define MAPNIK_TEXT_PROCESSING_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +namespace mapnik +{ + +enum text_transform +{ + NONE = 0, + UPPERCASE, + LOWERCASE, + CAPITALIZE, + text_transform_MAX +}; + +DEFINE_ENUM( text_transform_e, text_transform ); + +struct char_properties +{ + char_properties(); + void set_values_from_xml(boost::property_tree::ptree const &sym, std::map const & fontsets); + void to_xml(boost::property_tree::ptree &node, bool explicit_defaults, char_properties const &dfl=char_properties()) const; + std::string face_name; + font_set fontset; + unsigned text_size; + 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; + color halo_fill; + double halo_radius; +}; + +class abstract_token; +class processed_text; + +class processed_expression +{ +public: + processed_expression(char_properties const& properties, UnicodeString const& text) : + p(properties), str(text) {} + char_properties p; + UnicodeString str; +private: + friend class processed_text; +}; + +class processed_text +{ +public: + processed_text(face_manager & font_manager, double scale_factor); + void push_back(processed_expression const& exp); + unsigned 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(); + expression_list::const_iterator end(); + string_info &get_string_info(); +private: + expression_list expr_list_; + face_manager & font_manager_; + double scale_factor_; + string_info info_; +}; + + +class text_processor +{ +public: + text_processor(); + void from_xml(boost::property_tree::ptree const& pt, std::map const &fontsets); + void to_xml(boost::property_tree::ptree &node, bool explicit_defaults, text_processor const& dfl) const; + void process(processed_text &output, Feature const& feature); + void set_old_style_expression(expression_ptr expr); + void push_back(abstract_token *token); + std::set get_all_expressions() const; + char_properties defaults; +protected: + void from_xml_recursive(boost::property_tree::ptree const& pt, std::map const &fontsets); +private: + std::list list_; + bool clear_on_write; //Clear list once +}; + +} /* namespace */ + +#endif diff --git a/src/text_processing.cpp b/src/text_processing.cpp new file mode 100644 index 000000000..30408316f --- /dev/null +++ b/src/text_processing.cpp @@ -0,0 +1,447 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2011 Artem Pavlenko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + *****************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace mapnik { +using boost::property_tree::ptree; +using boost::optional; + +class abstract_token +{ +public: + virtual ~abstract_token() {} + virtual ptree *to_xml(ptree *node) = 0; +}; + +class abstract_formating_token : public abstract_token +{ +public: + virtual void apply(char_properties &p, Feature const& feature) = 0; +}; + +class abstract_text_token : public abstract_token +{ +public: + virtual UnicodeString to_string(Feature const& feature) = 0; +}; + +class end_format_token : public abstract_token +{ +public: + end_format_token() {} + ptree *to_xml(ptree *node); +}; + +class expression_token: public abstract_text_token +{ +public: + expression_token(expression_ptr text); + UnicodeString to_string(Feature const& feature); + ptree *to_xml(ptree *node); + void set_expression(expression_ptr text); + expression_ptr get_expression(); +private: + expression_ptr text_; +}; + +class fixed_formating_token : public abstract_formating_token +{ +public: + fixed_formating_token(); + virtual void apply(char_properties &p, Feature const& feature); + ptree* to_xml(ptree *node); + void from_xml(ptree const& node); + void set_face_name(optional face_name); + void set_text_size(optional text_size); + void set_character_spacing(optional character_spacing); + void set_line_spacing(optional line_spacing); + void set_text_opacity(optional opacity); + void set_wrap_before(optional wrap_before); + void set_wrap_char(optional wrap_char); + void set_text_transform(optional text_trans); + void set_fill(optional fill); + void set_halo_fill(optional halo_fill); + void set_halo_radius(optional radius); +private: + boost::optional face_name_; +// font_set fontset; + boost::optional text_size_; + boost::optional character_spacing_; + boost::optional line_spacing_; + boost::optional text_opacity_; + boost::optional wrap_before_; + boost::optional wrap_char_; + boost::optional text_transform_; + boost::optional fill_; + boost::optional halo_fill_; + boost::optional halo_radius_; +}; + +/************************************************************/ + +expression_token::expression_token(expression_ptr text): + text_(text) +{ +} + +void expression_token::set_expression(expression_ptr text) +{ + text_ = text; +} + +expression_ptr expression_token::get_expression() +{ + return text_; +} + +UnicodeString expression_token::to_string(const Feature &feature) +{ + value_type result = boost::apply_visitor(evaluate(feature), *text_); + return result.to_unicode(); +} + +ptree *expression_token::to_xml(ptree *node) +{ + ptree &new_node = node->push_back(ptree::value_type( + "", ptree()))->second; + new_node.put_value(to_expression_string(*text_)); + return &new_node; +} + +/************************************************************/ + +fixed_formating_token::fixed_formating_token(): + fill_() +{ +} + +void fixed_formating_token::apply(char_properties &p, const Feature &feature) +{ + if (face_name_) p.face_name = *face_name_; + if (text_size_) p.text_size = *text_size_; + if (character_spacing_) p.character_spacing = *character_spacing_; + if (line_spacing_) p.line_spacing = *line_spacing_; + if (text_opacity_) p.text_opacity = *text_opacity_; + if (wrap_before_) p.wrap_before = *wrap_before_; + if (wrap_char_) p.wrap_char = *wrap_char_; + if (text_transform_) p.text_transform = *text_transform_; + if (fill_) p.fill = *fill_; + if (halo_fill_) p.halo_fill = *halo_fill_; + if (halo_radius_) p.halo_radius = *halo_radius_; +} + +ptree *fixed_formating_token::to_xml(ptree *node) +{ + + ptree &new_node = node->push_back(ptree::value_type("Format", ptree()))->second; + if (face_name_) set_attr(new_node, "face-name", face_name_); + if (text_size_) set_attr(new_node, "size", text_size_); + if (character_spacing_) set_attr(new_node, "character-spacing", character_spacing_); + if (line_spacing_) set_attr(new_node, "line-spacing", line_spacing_); + if (text_opacity_) set_attr(new_node, "opacity", text_opacity_); + if (wrap_before_) set_attr(new_node, "wrap-before", wrap_before_); + if (wrap_char_) set_attr(new_node, "wrap-character", wrap_char_); + if (text_transform_) set_attr(new_node, "text-transform", text_transform_); + if (fill_) set_attr(new_node, "fill", fill_); + if (halo_fill_) set_attr(new_node, "halo-fill", halo_fill_); + if (halo_radius_) set_attr(new_node, "halo-radius", halo_radius_); + return &new_node; +} + +void fixed_formating_token::from_xml(ptree const& node) +{ + set_face_name(get_opt_attr(node, "face-name")); + /*TODO: Fontset is problematic. We don't have the fontsets pointer here... */ + set_text_size(get_opt_attr(node, "size")); + set_character_spacing(get_opt_attr(node, "character-spacing")); + set_line_spacing(get_opt_attr(node, "line-spacing")); + set_text_opacity(get_opt_attr(node, "opactity")); + set_wrap_before(get_opt_attr(node, "wrap-before")); + set_wrap_char(get_opt_attr(node, "wrap-character")); + set_text_transform(get_opt_attr(node, "text-transform")); + set_fill(get_opt_attr(node, "fill")); + set_halo_fill(get_opt_attr(node, "halo-fill")); + set_halo_radius(get_opt_attr(node, "halo-radius")); +} + +void fixed_formating_token::set_face_name(optional face_name) +{ + face_name_ = face_name; +} + +void fixed_formating_token::set_text_size(optional text_size) +{ + text_size_ = text_size; +} + +void fixed_formating_token::set_character_spacing(optional character_spacing) +{ + character_spacing_ = character_spacing; +} + +void fixed_formating_token::set_line_spacing(optional line_spacing) +{ + line_spacing_ = line_spacing; +} + +void fixed_formating_token::set_text_opacity(optional text_opacity) +{ + text_opacity_ = text_opacity; +} + +void fixed_formating_token::set_wrap_before(optional wrap_before) +{ + wrap_before_ = wrap_before; +} + +void fixed_formating_token::set_wrap_char(optional wrap_char) +{ + wrap_char_ = wrap_char; +} + +void fixed_formating_token::set_text_transform(optional text_transform) +{ + text_transform_ = text_transform; +} + +void fixed_formating_token::set_fill(optional c) +{ + fill_ = c; +} + +void fixed_formating_token::set_halo_fill(optional c) +{ + halo_fill_ = c; +} + +void fixed_formating_token::set_halo_radius(optional radius) +{ + halo_radius_ = radius; +} + +/************************************************************/ + +ptree *end_format_token::to_xml(ptree *node) +{ + return 0; +} + +/************************************************************/ + +text_processor::text_processor(): + list_(), clear_on_write(false) +{ +} + +void text_processor::push_back(abstract_token *token) +{ + if (clear_on_write) list_.clear(); + clear_on_write = false; + list_.push_back(token); +} + +void text_processor::from_xml(const boost::property_tree::ptree &pt, std::map const &fontsets) +{ + clear_on_write = true; + defaults.set_values_from_xml(pt, fontsets); + from_xml_recursive(pt, fontsets); +} + +void text_processor::from_xml_recursive(const boost::property_tree::ptree &pt, std::map const &fontsets) +{ + ptree::const_iterator itr = pt.begin(); + ptree::const_iterator end = pt.end(); + for (; itr != end; ++itr) { + if (itr->first == "") { + std::string data = itr->second.data(); + boost::trim(data); + if (data.empty()) continue; + expression_token *token = new expression_token(parse_expression(data, "utf8")); + push_back(token); + } else if (itr->first == "Format") { + fixed_formating_token *token = new fixed_formating_token(); + token->from_xml(itr->second); + push_back(token); + from_xml_recursive(itr->second, fontsets); /* Parse children, making a list out of a tree. */ + push_back(new end_format_token()); + } else if (itr->first != "" && itr->first != "" && itr->first != "Placement") { + std::cerr << "Unknown item" << itr->first; + } + } +} + +void text_processor::to_xml(boost::property_tree::ptree &node, bool explicit_defaults, text_processor const& dfl) const +{ + defaults.to_xml(node, explicit_defaults, dfl.defaults); + std::list::const_iterator itr = list_.begin(); + std::list::const_iterator end = list_.end(); + std::stack nodes; + ptree *current_node = &node; + for (; itr != end; ++itr) { + abstract_token *token = *itr; + ptree *new_node = token->to_xml(current_node); + if (dynamic_cast(token)) { + nodes.push(current_node); + current_node = new_node; + } else if (dynamic_cast(token)) { + current_node = nodes.top(); + nodes.pop(); + } + } +} + +void text_processor::process(processed_text &output, Feature const& feature) +{ + std::list::const_iterator itr = list_.begin(); + std::list::const_iterator end = list_.end(); + std::stack formats; + formats.push(defaults); + + for (; itr != end; ++itr) { + abstract_text_token *text = dynamic_cast(*itr); + abstract_formating_token *format = dynamic_cast(*itr);; + end_format_token *end = dynamic_cast(*itr);; + if (text) { + UnicodeString text_str = text->to_string(feature); + char_properties const& p = formats.top(); + /* TODO: Make a class out of text_transform which does the work! */ + if (p.text_transform == UPPERCASE) + { + text_str = text_str.toUpper(); + } + else if (p.text_transform == LOWERCASE) + { + text_str = text_str.toLower(); + } + else if (p.text_transform == CAPITALIZE) + { + text_str = text_str.toTitle(NULL); + } + if (text_str.length() > 0) { + output.push_back(processed_expression(p, text_str)); + } else { +#ifdef MAPNIK_DEBUG + std::cerr << "Warning: Empty expression.\n"; +#endif + } + } else if (format) { + char_properties next_properties = formats.top(); + format->apply(next_properties, feature); + formats.push(next_properties); + } else if (end) { + /* Always keep at least the defaults_ on stack. */ + if (formats.size() > 1) { + formats.pop(); + } else { + std::cerr << "Warning: Internal mapnik error. More elements popped than pushed in text_processor::process()\n"; + output.clear(); + return; + } + } + } + if (formats.size() != 1) { + std::cerr << "Warning: Internal mapnik error. Less elements popped than pushed in text_processor::process()\n"; + } +} + +std::set text_processor::get_all_expressions() const +{ + std::set result; + std::list::const_iterator itr = list_.begin(); + std::list::const_iterator end = list_.end(); + for (; itr != end; ++itr) { + expression_token *text = dynamic_cast(*itr); + if (text) result.insert(text->get_expression()); + } + return result; +} + +void text_processor::set_old_style_expression(expression_ptr expr) +{ + list_.push_back(new expression_token(expr)); +} + +/************************************************************/ + +void processed_text::push_back(processed_expression const& exp) +{ + expr_list_.push_back(exp); +} + +processed_text::expression_list::const_iterator processed_text::begin() +{ + return expr_list_.begin(); +} + +processed_text::expression_list::const_iterator processed_text::end() +{ + 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 &processed_text::get_string_info() +{ + //info_.clear(); TODO: 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) + { + throw config_error("Unable to find specified font face '" + p.face_name + "'"); + } + faces->set_pixel_sizes(p.text_size * scale_factor_); + faces->get_string_info(info_, itr->str, &(itr->p)); + info_.add_text(itr->str); + } + return info_; +} + + + +} /* namespace */ From 21a58b7b7ada866ae7a45761d40ddcedaa6ec333 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 20 Jan 2012 22:43:05 +0100 Subject: [PATCH 13/81] Add missing includes. --- bindings/python/mapnik_python.cpp | 1 + include/mapnik/octree.hpp | 1 + src/agg/agg_renderer.cpp | 1 + src/agg/process_line_pattern_symbolizer.cpp | 1 + src/agg/process_line_symbolizer.cpp | 1 + src/agg/process_markers_symbolizer.cpp | 1 + src/agg/process_polygon_symbolizer.cpp | 1 + src/grid/grid_renderer.cpp | 3 +++ src/grid/process_line_pattern_symbolizer.cpp | 1 + src/grid/process_line_symbolizer.cpp | 1 + src/grid/process_markers_symbolizer.cpp | 1 + src/grid/process_point_symbolizer.cpp | 1 + src/grid/process_polygon_pattern_symbolizer.cpp | 1 + src/grid/process_polygon_symbolizer.cpp | 1 + 14 files changed, 16 insertions(+) diff --git a/bindings/python/mapnik_python.cpp b/bindings/python/mapnik_python.cpp index 035209193..bb9198c2e 100644 --- a/bindings/python/mapnik_python.cpp +++ b/bindings/python/mapnik_python.cpp @@ -83,6 +83,7 @@ void export_label_collision_detector(); #include #include #include +#include #include "python_grid_utils.hpp" #include "mapnik_value_converter.hpp" #include "python_optional.hpp" diff --git a/include/mapnik/octree.hpp b/include/mapnik/octree.hpp index 755fbb3cc..bdba82890 100644 --- a/include/mapnik/octree.hpp +++ b/include/mapnik/octree.hpp @@ -34,6 +34,7 @@ #include #include #include +#include namespace mapnik { diff --git a/src/agg/agg_renderer.cpp b/src/agg/agg_renderer.cpp index a2ef94c34..6309758b8 100644 --- a/src/agg/agg_renderer.cpp +++ b/src/agg/agg_renderer.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include diff --git a/src/agg/process_line_pattern_symbolizer.cpp b/src/agg/process_line_pattern_symbolizer.cpp index 17f9cd191..11c633259 100644 --- a/src/agg/process_line_pattern_symbolizer.cpp +++ b/src/agg/process_line_pattern_symbolizer.cpp @@ -27,6 +27,7 @@ #include #include #include +#include // agg #include "agg_basics.h" diff --git a/src/agg/process_line_symbolizer.cpp b/src/agg/process_line_symbolizer.cpp index eaef2e7e8..df0c977b3 100644 --- a/src/agg/process_line_symbolizer.cpp +++ b/src/agg/process_line_symbolizer.cpp @@ -24,6 +24,7 @@ // mapnik #include #include +#include // agg #include "agg_basics.h" diff --git a/src/agg/process_markers_symbolizer.cpp b/src/agg/process_markers_symbolizer.cpp index abe8983b8..a4dc9de28 100644 --- a/src/agg/process_markers_symbolizer.cpp +++ b/src/agg/process_markers_symbolizer.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "agg_basics.h" #include "agg_rendering_buffer.h" diff --git a/src/agg/process_polygon_symbolizer.cpp b/src/agg/process_polygon_symbolizer.cpp index 2427cdc3f..991d1018a 100644 --- a/src/agg/process_polygon_symbolizer.cpp +++ b/src/agg/process_polygon_symbolizer.cpp @@ -24,6 +24,7 @@ // mapnik #include #include +#include // agg #include "agg_basics.h" diff --git a/src/grid/grid_renderer.cpp b/src/grid/grid_renderer.cpp index cf6c645d9..c4bada5c7 100644 --- a/src/grid/grid_renderer.cpp +++ b/src/grid/grid_renderer.cpp @@ -28,6 +28,7 @@ #include #include + #include #include #include @@ -35,10 +36,12 @@ #include #include #include +#include #include #include #include + // boost #include diff --git a/src/grid/process_line_pattern_symbolizer.cpp b/src/grid/process_line_pattern_symbolizer.cpp index f7510e35b..1d8a9c577 100644 --- a/src/grid/process_line_pattern_symbolizer.cpp +++ b/src/grid/process_line_pattern_symbolizer.cpp @@ -27,6 +27,7 @@ #include #include #include +#include // agg #include "agg_rasterizer_scanline_aa.h" diff --git a/src/grid/process_line_symbolizer.cpp b/src/grid/process_line_symbolizer.cpp index b04999bff..212da4a1e 100644 --- a/src/grid/process_line_symbolizer.cpp +++ b/src/grid/process_line_symbolizer.cpp @@ -27,6 +27,7 @@ #include #include #include +#include // agg #include "agg_rasterizer_scanline_aa.h" diff --git a/src/grid/process_markers_symbolizer.cpp b/src/grid/process_markers_symbolizer.cpp index 347324326..ba4bed42d 100644 --- a/src/grid/process_markers_symbolizer.cpp +++ b/src/grid/process_markers_symbolizer.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include diff --git a/src/grid/process_point_symbolizer.cpp b/src/grid/process_point_symbolizer.cpp index 88f94a7d8..e3fd66e1c 100644 --- a/src/grid/process_point_symbolizer.cpp +++ b/src/grid/process_point_symbolizer.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include diff --git a/src/grid/process_polygon_pattern_symbolizer.cpp b/src/grid/process_polygon_pattern_symbolizer.cpp index 0f3b63f8b..7cce368ca 100644 --- a/src/grid/process_polygon_pattern_symbolizer.cpp +++ b/src/grid/process_polygon_pattern_symbolizer.cpp @@ -27,6 +27,7 @@ #include #include #include +#include // agg #include "agg_rasterizer_scanline_aa.h" diff --git a/src/grid/process_polygon_symbolizer.cpp b/src/grid/process_polygon_symbolizer.cpp index 6b410a4eb..0beaab3f7 100644 --- a/src/grid/process_polygon_symbolizer.cpp +++ b/src/grid/process_polygon_symbolizer.cpp @@ -27,6 +27,7 @@ #include #include #include +#include // agg #include "agg_rasterizer_scanline_aa.h" From e177cd52a75dceead5b839541e978aec39e526b2 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 20 Jan 2012 22:45:47 +0100 Subject: [PATCH 14/81] Rename opacity to image_opacity. --- include/mapnik/symbolizer.hpp | 2 +- src/symbolizer.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/mapnik/symbolizer.hpp b/include/mapnik/symbolizer.hpp index 062b5b781..159664617 100644 --- a/include/mapnik/symbolizer.hpp +++ b/include/mapnik/symbolizer.hpp @@ -99,7 +99,7 @@ protected: symbolizer_with_image(path_expression_ptr filename = path_expression_ptr()); symbolizer_with_image(symbolizer_with_image const& rhs); path_expression_ptr image_filename_; - float opacity_; + float image_opacity_; transform_type matrix_; }; } diff --git a/src/symbolizer.cpp b/src/symbolizer.cpp index 4eba20036..e954aa5a1 100644 --- a/src/symbolizer.cpp +++ b/src/symbolizer.cpp @@ -72,7 +72,7 @@ metawriter_with_properties symbolizer_base::get_metawriter() const symbolizer_with_image::symbolizer_with_image(path_expression_ptr file) : image_filename_( file ), - opacity_(1.0f) + image_opacity_(1.0f) { matrix_[0] = 1.0; @@ -85,7 +85,7 @@ symbolizer_with_image::symbolizer_with_image(path_expression_ptr file) symbolizer_with_image::symbolizer_with_image( symbolizer_with_image const& rhs) : image_filename_(rhs.image_filename_), - opacity_(rhs.opacity_), + image_opacity_(rhs.image_opacity_), matrix_(rhs.matrix_) {} path_expression_ptr symbolizer_with_image::get_filename() const @@ -120,12 +120,12 @@ std::string const symbolizer_with_image::get_transform_string() const void symbolizer_with_image::set_opacity(float opacity) { - opacity_ = opacity; + image_opacity_ = opacity; } float symbolizer_with_image::get_opacity() const { - return opacity_; + return image_opacity_; } } // end of namespace mapnik From 533b95f0e664a2ff56fce006d12760bedf17f626 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sat, 21 Jan 2012 00:02:44 +0100 Subject: [PATCH 15/81] Enable XML parser to return multiple nodes. --- include/mapnik/ptree_helpers.hpp | 23 +++++++++++++++++------ src/libxml2_loader.cpp | 10 ++++++++-- src/load_map.cpp | 2 +- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/include/mapnik/ptree_helpers.hpp b/include/mapnik/ptree_helpers.hpp index c97ce4d55..c3790ebdf 100644 --- a/include/mapnik/ptree_helpers.hpp +++ b/include/mapnik/ptree_helpers.hpp @@ -258,7 +258,7 @@ T get(const boost::property_tree::ptree & node, const std::string & name, bool i } else { - str = node.get_optional(name ); + str = node.get_optional(name+"."); } if ( str ) { @@ -289,7 +289,7 @@ inline color get(boost::property_tree::ptree const& node, std::string const& nam } else { - str = node.get_optional(name ); + str = node.get_optional(name+"."); } if ( str ) @@ -322,7 +322,7 @@ T get(const boost::property_tree::ptree & node, const std::string & name, bool i } else { - str = node.get_optional(name); + str = node.get_optional(name+"."); } if ( ! str ) { @@ -348,7 +348,18 @@ T get_value(const boost::property_tree::ptree & node, const std::string & name) { try { - return node.get_value(); + /* NOTE: get_child works as long as there is only one child with that name. + If this function is used this used this condition must always be satisfied. + */ + return node.get_child("").get_value(); + } + catch (boost::property_tree::ptree_bad_path) + { + /* If the XML parser did not find any non-empty data element the is no + node. But we don't want to fail here but simply return a + default constructed value of the requested type. + */ + return T(); } catch (...) { @@ -369,7 +380,7 @@ boost::optional get_optional(const boost::property_tree::ptree & node, const } else { - str = node.get_optional(name); + str = node.get_optional(name+"."); } boost::optional result; @@ -403,7 +414,7 @@ inline boost::optional get_optional(const boost::property_tree::ptree & n } else { - str = node.get_optional(name); + str = node.get_optional(name+"."); } boost::optional result; diff --git a/src/libxml2_loader.cpp b/src/libxml2_loader.cpp index f935243ce..fedfbfd44 100644 --- a/src/libxml2_loader.cpp +++ b/src/libxml2_loader.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -200,8 +201,13 @@ private: } break; case XML_TEXT_NODE: - pt.put_value( (char*) cur_node->content ); - break; + { + std::string trimmed = boost::algorithm::trim_copy(std::string((char*)cur_node->content)); + if (trimmed.empty()) break; + ptree::iterator it = pt.push_back(ptree::value_type("", ptree())); + it->second.put_value(trimmed); + } + break; case XML_COMMENT_NODE: { ptree::iterator it = pt.push_back( diff --git a/src/load_map.cpp b/src/load_map.cpp index 423d2f80c..fffd85d19 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -652,7 +652,7 @@ void map_parser::parse_layer( Map & map, ptree const & lay ) if (child.first == "StyleName") { ensure_attrs(child.second, "StyleName", "none"); - std::string style_name = child.second.data(); + std::string style_name = get_value(child.second, "style name"); if (style_name.empty()) { std::ostringstream ss; From 34405a5d9e959e8a2cb4d5fb394e7c2ebef47352 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sat, 21 Jan 2012 00:35:24 +0100 Subject: [PATCH 16/81] Replace dimension_t by char_info. --- include/mapnik/font_engine_freetype.hpp | 11 ++--------- include/mapnik/text_path.hpp | 4 ++++ src/font_engine_freetype.cpp | 23 ++++++++++------------- src/metawriter.cpp | 16 ++++++++-------- 4 files changed, 24 insertions(+), 30 deletions(-) diff --git a/include/mapnik/font_engine_freetype.hpp b/include/mapnik/font_engine_freetype.hpp index e72dc4538..560e0d404 100644 --- a/include/mapnik/font_engine_freetype.hpp +++ b/include/mapnik/font_engine_freetype.hpp @@ -145,13 +145,6 @@ private: class MAPNIK_DECL font_face_set : private boost::noncopyable { public: - class dimension_t { - public: - dimension_t(unsigned width_, int ymax_, int ymin_) : width(width_), height(ymax_-ymin_), ymin(ymin_) {} - unsigned width, height; - int ymin; - }; - font_face_set(void) : faces_() {} @@ -179,7 +172,7 @@ public: return boost::make_shared(*faces_.begin(), 0); } - dimension_t character_dimensions(const unsigned c); + char_info character_dimensions(const unsigned c); void get_string_info(string_info & info); @@ -200,7 +193,7 @@ public: } private: std::vector faces_; - std::map dimension_cache_; + std::map dimension_cache_; }; // FT_Stroker wrapper diff --git a/include/mapnik/text_path.hpp b/include/mapnik/text_path.hpp index eeebd97c4..0f2628859 100644 --- a/include/mapnik/text_path.hpp +++ b/include/mapnik/text_path.hpp @@ -23,9 +23,13 @@ #ifndef MAPNIK_TEXT_PATH_HPP #define MAPNIK_TEXT_PATH_HPP +// mapnik +#include + // boost #include #include +#include // uci #include diff --git a/src/font_engine_freetype.cpp b/src/font_engine_freetype.cpp index 188aaa59d..7b8a76fd9 100644 --- a/src/font_engine_freetype.cpp +++ b/src/font_engine_freetype.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ // mapnik #include @@ -192,9 +191,10 @@ stroker_ptr freetype_engine::create_stroker() return stroker_ptr(); } -font_face_set::dimension_t font_face_set::character_dimensions(const unsigned c) +char_info font_face_set::character_dimensions(const unsigned c) { - std::map::const_iterator itr; + //Check if char is already in cache + std::map::const_iterator itr; itr = dimension_cache_.find(c); if (itr != dimension_cache_.end()) { return itr->second; @@ -222,21 +222,18 @@ font_face_set::dimension_t font_face_set::character_dimensions(const unsigned c) error = FT_Load_Glyph (face, glyph->get_index(), FT_LOAD_NO_HINTING); if ( error ) - return dimension_t(0, 0, 0); + return char_info(); error = FT_Get_Glyph(face->glyph, &image); if ( error ) - return dimension_t(0, 0, 0); + 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; - - //std::clog << "glyph: " << glyph_index << " x: " << tempx << " y: " << tempy << std::endl; - dimension_t dim(tempx, glyph_bbox.yMax, glyph_bbox.yMin); - //dimension_cache_[c] = dim; would need an default constructor for dimension_t - dimension_cache_.insert(std::pair(c, dim)); + char_info dim(c, tempx, glyph_bbox.yMax, glyph_bbox.yMin, face->size->metrics.height/64.0 /* >> 6 */); + dimension_cache_.insert(std::pair(c, dim)); return dim; } @@ -270,10 +267,10 @@ void font_face_set::get_string_info(string_info & info) StringCharacterIterator iter(shaped); for (iter.setToStart(); iter.hasNext();) { UChar ch = iter.nextPostInc(); - dimension_t char_dim = character_dimensions(ch); - info.add_info(ch, char_dim.width, char_dim.height); + char_info char_dim = character_dimensions(ch); + info.add_info(ch, char_dim.width, char_dim.height()); width += char_dim.width; - height = (char_dim.height > height) ? char_dim.height : height; + height = (char_dim.height() > height) ? char_dim.height() : height; } } diff --git a/src/metawriter.cpp b/src/metawriter.cpp index 51a56b247..8d1fe5713 100644 --- a/src/metawriter.cpp +++ b/src/metawriter.cpp @@ -218,12 +218,12 @@ void metawriter_json_stream::add_text(placement const& p, double minx = INT_MAX, miny = INT_MAX, maxx = INT_MIN, maxy = INT_MIN; for (int i = 0; i < current_placement.num_nodes(); ++i) { current_placement.vertex(&c, &x, &y, &angle); - font_face_set::dimension_t ci = face->character_dimensions(c); + char_info ci = face->character_dimensions(c); if (x < minx) minx = x; if (x+ci.width > maxx) maxx = x+ci.width; - if (y+ci.height+ci.ymin > maxy) maxy = y+ci.height+ci.ymin; + if (y+ci.height()+ci.ymin > maxy) maxy = y+ci.height()+ci.ymin; if (y+ci.ymin < miny) miny = y+ci.ymin; - // std::cout << (char) c << " height:" << ci.height << " ymin:" << ci.ymin << " y:" << y << " miny:"<< miny << " maxy:"<< maxy <<"\n"; + // std::cout << (char) c << " height:" << ci.height() << " ymin:" << ci.ymin << " y:" << y << " miny:"<< miny << " maxy:"<< maxy <<"\n"; } add_box(box2d(current_placement.starting_x+minx, @@ -242,7 +242,7 @@ void metawriter_json_stream::add_text(placement const& p, } current_placement.vertex(&c, &x, &y, &angle); if (c == ' ') continue; - font_face_set::dimension_t ci = face->character_dimensions(c); + char_info ci = face->character_dimensions(c); double x0, y0, x1, y1, x2, y2, x3, y3; double sina = sin(angle); @@ -251,10 +251,10 @@ void metawriter_json_stream::add_text(placement const& p, y0 = current_placement.starting_y - y - cosa*ci.ymin; x1 = x0 + ci.width * cosa; y1 = y0 - ci.width * sina; - x2 = x0 + (ci.width * cosa - ci.height * sina); - y2 = y0 - (ci.width * sina + ci.height * cosa); - x3 = x0 - ci.height * sina; - y3 = y0 - ci.height * cosa; + x2 = x0 + (ci.width * cosa - ci.height() * sina); + y2 = y0 - (ci.width * sina + ci.height() * cosa); + x3 = x0 - ci.height() * sina; + y3 = y0 - ci.height() * cosa; *f_ << "\n [["; write_point(t, x0, y0); From 5fd703552773287d73fec2e0365d83d36b520fa4 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sat, 21 Jan 2012 01:47:02 +0100 Subject: [PATCH 17/81] Move font engine functions from .hpp to .cpp. --- include/mapnik/font_engine_freetype.hpp | 226 +++--------------------- src/agg/process_shield_symbolizer.cpp | 2 +- src/agg/process_text_symbolizer.cpp | 2 +- src/cairo_renderer.cpp | 4 +- src/font_engine_freetype.cpp | 197 ++++++++++++++++++++- src/grid/process_shield_symbolizer.cpp | 2 +- src/grid/process_text_symbolizer.cpp | 2 +- 7 files changed, 224 insertions(+), 211 deletions(-) diff --git a/include/mapnik/font_engine_freetype.hpp b/include/mapnik/font_engine_freetype.hpp index 560e0d404..5bcd345c5 100644 --- a/include/mapnik/font_engine_freetype.hpp +++ b/include/mapnik/font_engine_freetype.hpp @@ -174,7 +174,7 @@ public: char_info character_dimensions(const unsigned c); - void get_string_info(string_info & info); + void get_string_info(string_info & info, UnicodeString const& ustr, char_properties *format); void set_pixel_sizes(unsigned size) { @@ -306,6 +306,18 @@ public: return face_set; } + face_set_ptr get_face_set(std::string const& name, font_set const& fset) + { + if (fset.size() > 0) + { + return get_face_set(fset); + } + else + { + return get_face_set(name); + } + } + stroker_ptr get_stroker() { return stroker_; @@ -323,21 +335,18 @@ struct text_renderer : private boost::noncopyable struct glyph_t : boost::noncopyable { FT_Glyph image; - glyph_t(FT_Glyph image_) : image(image_) {} + char_properties *properties; + glyph_t(FT_Glyph image_, char_properties *properties_) : image(image_), properties(properties_) {} ~glyph_t () { FT_Done_Glyph(image);} }; typedef boost::ptr_vector glyphs_t; typedef T pixmap_type; - text_renderer (pixmap_type & pixmap, face_set_ptr faces, stroker & s) - : pixmap_(pixmap), - faces_(faces), - stroker_(s), - fill_(0,0,0), - halo_fill_(255,255,255), - halo_radius_(0.0), - opacity_(1.0) {} + text_renderer (pixmap_type & pixmap, face_set_ptr faces, stroker & s); + box2d prepare_glyphs(text_path *path); + void render(double x0, double y0); + void render_id(int feature_id,double x0, double y0, double min_radius=1.0); void set_pixel_size(unsigned size) @@ -370,200 +379,8 @@ struct text_renderer : private boost::noncopyable opacity_=opacity; } - box2d prepare_glyphs(text_path *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 (int i = 0; i < path->num_nodes(); i++) - { - int c; - double x, y, angle; - - path->vertex(&c, &x, &y, &angle); - -#ifdef MAPNIK_DEBUG - // TODO Enable when we have support for setting verbosity - //std::clog << "prepare_glyphs: " << c << "," << x << - // "," << y << "," << angle << std::endl; -#endif - - FT_BBox glyph_bbox; - FT_Glyph image; - - pen.x = int(x * 64); - pen.y = int(y * 64); - - glyph_ptr glyph = faces_->get_glyph(unsigned(c)); - FT_Face face = glyph->get_face()->get_face(); - - matrix.xx = (FT_Fixed)( cos( angle ) * 0x10000L ); - matrix.xy = (FT_Fixed)(-sin( angle ) * 0x10000L ); - matrix.yx = (FT_Fixed)( sin( angle ) * 0x10000L ); - matrix.yy = (FT_Fixed)( 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)); - } - - return box2d(bbox.xMin, bbox.yMin, bbox.xMax, bbox.yMax); - } - - void render(double x0, double y0) - { - FT_Error error; - FT_Vector start; - unsigned height = pixmap_.height(); - - start.x = static_cast(x0 * (1 << 6)); - start.y = static_cast((height - y0) * (1 << 6)); - - // now render transformed glyphs - typename glyphs_t::iterator pos; - - //make sure we've got reasonable values. - if (halo_radius_ > 0.0 && halo_radius_ < 1024.0) - { - stroker_.init(halo_radius_); - for ( pos = glyphs_.begin(); pos != glyphs_.end();++pos) - { - FT_Glyph g; - error = FT_Glyph_Copy(pos->image, &g); - if (!error) - { - FT_Glyph_Transform(g,0,&start); - FT_Glyph_Stroke(&g,stroker_.get(),1); - error = FT_Glyph_To_Bitmap( &g,FT_RENDER_MODE_NORMAL,0,1); - if ( ! error ) - { - - FT_BitmapGlyph bit = (FT_BitmapGlyph)g; - render_bitmap(&bit->bitmap, halo_fill_.rgba(), - bit->left, - height - bit->top); - } - } - FT_Done_Glyph(g); - } - } - //render actual text - for ( pos = glyphs_.begin(); pos != glyphs_.end();++pos) - { - - FT_Glyph_Transform(pos->image,0,&start); - - error = FT_Glyph_To_Bitmap( &(pos->image),FT_RENDER_MODE_NORMAL,0,1); - if ( ! error ) - { - - FT_BitmapGlyph bit = (FT_BitmapGlyph)pos->image; - render_bitmap(&bit->bitmap, fill_.rgba(), - bit->left, - height - bit->top); - } - } - } - - void render_id(int feature_id,double x0, double y0, double min_radius=1.0) - { - FT_Error error; - FT_Vector start; - unsigned height = pixmap_.height(); - - start.x = static_cast(x0 * (1 << 6)); - start.y = static_cast((height - y0) * (1 << 6)); - - // now render transformed glyphs - typename glyphs_t::iterator pos; - - stroker_.init(std::max(halo_radius_,min_radius)); - for ( pos = glyphs_.begin(); pos != glyphs_.end();++pos) - { - FT_Glyph g; - error = FT_Glyph_Copy(pos->image, &g); - if (!error) - { - FT_Glyph_Transform(g,0,&start); - FT_Glyph_Stroke(&g,stroker_.get(),1); - error = FT_Glyph_To_Bitmap( &g,FT_RENDER_MODE_NORMAL,0,1); - //error = FT_Glyph_To_Bitmap( &g,FT_RENDER_MODE_MONO,0,1); - if ( ! error ) - { - - FT_BitmapGlyph bit = (FT_BitmapGlyph)g; - render_bitmap_id(&bit->bitmap, feature_id, - bit->left, - height - bit->top); - } - } - FT_Done_Glyph(g); - } - } - private: - - // unused currently, stroker is the new method for drawing halos - /* - void render_halo(FT_Bitmap *bitmap,unsigned rgba,int x,int y,int radius) - { - 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) - { - for (int n=-halo_radius_; n <=halo_radius_; ++n) - for (int m=-halo_radius_;m <= halo_radius_; ++m) - pixmap_.blendPixel2(i+m,j+n,rgba,gray,opacity_); - } - } - } - } - */ - - void render_bitmap(FT_Bitmap *bitmap,unsigned rgba,int x,int y) + void render_bitmap(FT_Bitmap *bitmap, unsigned rgba, int x, int y, double opacity) { int x_max=x+bitmap->width; int y_max=y+bitmap->rows; @@ -576,7 +393,7 @@ private: int gray=bitmap->buffer[q*bitmap->width+p]; if (gray) { - pixmap_.blendPixel2(i,j,rgba,gray,opacity_); + pixmap_.blendPixel2(i, j, rgba, gray, opacity); } } } @@ -611,6 +428,7 @@ private: glyphs_t glyphs_; double opacity_; }; +typedef face_manager face_manager_freetype; } #endif // MAPNIK_FONT_ENGINE_FREETYPE_HPP diff --git a/src/agg/process_shield_symbolizer.cpp b/src/agg/process_shield_symbolizer.cpp index 7d2c02741..55265ff2b 100644 --- a/src/agg/process_shield_symbolizer.cpp +++ b/src/agg/process_shield_symbolizer.cpp @@ -139,7 +139,7 @@ void agg_renderer::process(shield_symbolizer const& sym, string_info info(text); - faces->get_string_info(info); + faces->get_string_info(info, text, 0); metawriter_with_properties writer = sym.get_metawriter(); diff --git a/src/agg/process_text_symbolizer.cpp b/src/agg/process_text_symbolizer.cpp index e460fa322..677c40af6 100644 --- a/src/agg/process_text_symbolizer.cpp +++ b/src/agg/process_text_symbolizer.cpp @@ -116,7 +116,7 @@ void agg_renderer::process(text_symbolizer const& sym, string_info info(text); - faces->get_string_info(info); + faces->get_string_info(info, text, 0); metawriter_with_properties writer = sym.get_metawriter(); BOOST_FOREACH( geometry_type * geom, geometries_to_process ) diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 6bc2f88a6..37f29423d 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -1146,7 +1146,7 @@ void cairo_renderer_base::process(shield_symbolizer const& sym, placement_finder finder(detector_); faces->set_character_sizes(placement_options->text_size); - faces->get_string_info(info); + faces->get_string_info(info, text, 0); int w = (*marker)->width(); int h = (*marker)->height(); @@ -1508,7 +1508,7 @@ void cairo_renderer_base::process(text_symbolizer const& sym, string_info info(text); faces->set_character_sizes(placement_options->text_size); - faces->get_string_info(info); + faces->get_string_info(info, text, 0); placement_finder finder(detector_); diff --git a/src/font_engine_freetype.cpp b/src/font_engine_freetype.cpp index 7b8a76fd9..d03f9995b 100644 --- a/src/font_engine_freetype.cpp +++ b/src/font_engine_freetype.cpp @@ -22,6 +22,9 @@ // mapnik #include +#include +#include +#include // boost #include @@ -232,12 +235,13 @@ char_info font_face_set::character_dimensions(const unsigned c) 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 /* >> 6 */); dimension_cache_.insert(std::pair(c, dim)); return dim; } -void font_face_set::get_string_info(string_info & info) +void font_face_set::get_string_info(string_info & info, UnicodeString const& ustr_unused, char_properties *format_unused) { unsigned width = 0; unsigned height = 0; @@ -286,8 +290,199 @@ void font_face_set::get_string_info(string_info & info) info.set_dimensions(width, height); } +template +text_renderer::text_renderer (pixmap_type & pixmap, face_set_ptr faces, stroker & s) + : pixmap_(pixmap), + faces_(faces), + stroker_(s), + fill_(0,0,0), + halo_fill_(255,255,255), + halo_radius_(0.0), + opacity_(1.0) +{ + +} + +template +box2d text_renderer::prepare_glyphs(text_path *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 (int i = 0; i < path->num_nodes(); i++) + { + int c; + double x, y, angle; + + path->vertex(&c, &x, &y, &angle); + +#ifdef MAPNIK_DEBUG + // TODO Enable when we have support for setting verbosity + //std::clog << "prepare_glyphs: " << c << "," << x << + // "," << y << "," << angle << std::endl; +#endif + + FT_BBox glyph_bbox; + FT_Glyph image; + + pen.x = int(x * 64); + pen.y = int(y * 64); + + glyph_ptr glyph = faces_->get_glyph(unsigned(c)); + FT_Face face = glyph->get_face()->get_face(); + + matrix.xx = (FT_Fixed)( cos( angle ) * 0x10000L ); + matrix.xy = (FT_Fixed)(-sin( angle ) * 0x10000L ); + matrix.yx = (FT_Fixed)( sin( angle ) * 0x10000L ); + matrix.yy = (FT_Fixed)( 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, 0)); + } + + return box2d(bbox.xMin, bbox.yMin, bbox.xMax, bbox.yMax); +} + +template +void text_renderer::render(double x0, double y0) +{ + FT_Error error; + FT_Vector start; + unsigned height = pixmap_.height(); + + start.x = static_cast(x0 * (1 << 6)); + start.y = static_cast((height - y0) * (1 << 6)); + + // now render transformed glyphs + typename glyphs_t::iterator pos; + + //make sure we've got reasonable values. + if (halo_radius_ > 0.0 && halo_radius_ < 1024.0) + { + stroker_.init(halo_radius_); + for ( pos = glyphs_.begin(); pos != glyphs_.end();++pos) + { + FT_Glyph g; + error = FT_Glyph_Copy(pos->image, &g); + if (!error) + { + FT_Glyph_Transform(g,0,&start); + FT_Glyph_Stroke(&g,stroker_.get(),1); + error = FT_Glyph_To_Bitmap( &g,FT_RENDER_MODE_NORMAL,0,1); + if ( ! error ) + { + + FT_BitmapGlyph bit = (FT_BitmapGlyph)g; + render_bitmap(&bit->bitmap, halo_fill_.rgba(), + bit->left, + height - bit->top, opacity_); + } + } + FT_Done_Glyph(g); + } + } + //render actual text + for ( pos = glyphs_.begin(); pos != glyphs_.end();++pos) + { + + FT_Glyph_Transform(pos->image,0,&start); + + error = FT_Glyph_To_Bitmap( &(pos->image),FT_RENDER_MODE_NORMAL,0,1); + if ( ! error ) + { + + FT_BitmapGlyph bit = (FT_BitmapGlyph)pos->image; + render_bitmap(&bit->bitmap, fill_.rgba(), + bit->left, + height - bit->top, opacity_); + } + } +} + + +template +void text_renderer::render_id(int feature_id,double x0, double y0, double min_radius) +{ + FT_Error error; + FT_Vector start; + unsigned height = pixmap_.height(); + + start.x = static_cast(x0 * (1 << 6)); + start.y = static_cast((height - y0) * (1 << 6)); + + // now render transformed glyphs + typename glyphs_t::iterator pos; + + stroker_.init(std::max(halo_radius_,min_radius)); + for ( pos = glyphs_.begin(); pos != glyphs_.end();++pos) + { + FT_Glyph g; + error = FT_Glyph_Copy(pos->image, &g); + if (!error) + { + FT_Glyph_Transform(g,0,&start); + FT_Glyph_Stroke(&g,stroker_.get(),1); + error = FT_Glyph_To_Bitmap( &g,FT_RENDER_MODE_NORMAL,0,1); + //error = FT_Glyph_To_Bitmap( &g,FT_RENDER_MODE_MONO,0,1); + if ( ! error ) + { + + FT_BitmapGlyph bit = (FT_BitmapGlyph)g; + render_bitmap_id(&bit->bitmap, feature_id, + bit->left, + height - bit->top); + } + } + FT_Done_Glyph(g); + } +} + #ifdef MAPNIK_THREADSAFE boost::mutex freetype_engine::mutex_; #endif std::map > freetype_engine::name2file_; +template void text_renderer::render(double, double); +template text_renderer::text_renderer(image_32&, face_set_ptr, stroker&); +template box2dtext_renderer::prepare_glyphs(text_path*); + +template void text_renderer::render_id(int, double, double, double); +template text_renderer::text_renderer(grid&, face_set_ptr, stroker&); +template box2dtext_renderer::prepare_glyphs(text_path*); } diff --git a/src/grid/process_shield_symbolizer.cpp b/src/grid/process_shield_symbolizer.cpp index d0f61cd67..fdc2f7b15 100644 --- a/src/grid/process_shield_symbolizer.cpp +++ b/src/grid/process_shield_symbolizer.cpp @@ -120,7 +120,7 @@ void grid_renderer::process(shield_symbolizer const& sym, string_info info(text); - faces->get_string_info(info); + faces->get_string_info(info, text, 0); // TODO- clamp to at least 4 px otherwise interactivity is too small int w = (*marker)->width()/pixmap_.get_resolution(); diff --git a/src/grid/process_text_symbolizer.cpp b/src/grid/process_text_symbolizer.cpp index e88aed6c5..1cf6abd53 100644 --- a/src/grid/process_text_symbolizer.cpp +++ b/src/grid/process_text_symbolizer.cpp @@ -89,7 +89,7 @@ void grid_renderer::process(text_symbolizer const& sym, string_info info(text); - faces->get_string_info(info); + faces->get_string_info(info, text, 0); unsigned num_geom = feature.num_geometries(); for (unsigned i=0; i Date: Sun, 22 Jan 2012 01:08:14 +0100 Subject: [PATCH 18/81] Make text_path use char_info. --- include/mapnik/text_path.hpp | 95 +++++++++++++++++++++--------------- src/cairo_renderer.cpp | 6 ++- src/font_engine_freetype.cpp | 3 +- src/metawriter.cpp | 7 +-- src/placement_finder.cpp | 29 +++++------ 5 files changed, 82 insertions(+), 58 deletions(-) diff --git a/include/mapnik/text_path.hpp b/include/mapnik/text_path.hpp index 0f2628859..31bf69374 100644 --- a/include/mapnik/text_path.hpp +++ b/include/mapnik/text_path.hpp @@ -26,51 +26,60 @@ // mapnik #include +//stl +#include + // boost #include #include -#include // uci #include namespace mapnik { -struct character_info -{ - int character; - double width, height; - - character_info() : character(0), width(0), height(0) {} - character_info(int c_, double width_, double height_) : character(c_), width(width_), height(height_) {} - ~character_info() {} - - character_info(const character_info &ci) - : character(ci.character), width(ci.width), height(ci.height) - { - } - -}; class string_info : private boost::noncopyable { protected: - typedef boost::ptr_vector characters_t; + typedef std::vector characters_t; characters_t characters_; - UnicodeString const& text_; + UnicodeString text_; double width_; double height_; bool is_rtl; public: string_info(UnicodeString const& text) - : text_(text), + : characters_(), + text_(text), width_(0), height_(0), - is_rtl(false) {} + is_rtl(false) + { + + } + + string_info() + : characters_(), + text_(), + is_rtl(false) + { + + } + + void add_info(char_info const& info) + { + characters_.push_back(info); + } + + void add_text(UnicodeString text) + { + text_ += text; + } void add_info(int c, double width, double height) { - characters_.push_back(new character_info(c, width, height)); + characters_.push_back(char_info(c, width, height, 0, height)); //WARNING: Do not use. Only to keep old code compilable. } unsigned num_characters() const @@ -78,15 +87,22 @@ public: return characters_.size(); } - void set_rtl(bool value) {is_rtl = value;} - bool get_rtl() const {return is_rtl;} + void set_rtl(bool value) + { + is_rtl = value; + } + + bool get_rtl() const + { + return is_rtl; + } - character_info at(unsigned i) const + char_info const& at(unsigned i) const { return characters_[i]; } - character_info operator[](unsigned i) const + char_info const& operator[](unsigned i) const { return at(i); } @@ -112,6 +128,13 @@ public: UChar break_char = '\n'; return (text_.indexOf(break_char) >= 0); } + + /** Resets object to initial state. */ + void clear(void) + { + text_ = ""; + characters_.clear(); + } }; struct text_path : boost::noncopyable @@ -120,17 +143,19 @@ struct text_path : boost::noncopyable { int c; double x, y, angle; + char_properties *format; - character_node(int c_, double x_, double y_, double angle_) - : c(c_), x(x_), y(y_), angle(angle_) {} + character_node(int c_, double x_, double y_, double angle_, char_properties *format_) + : c(c_), x(x_), y(y_), angle(angle_), format(format_) {} ~character_node() {} - void vertex(int *c_, double *x_, double *y_, double *angle_) + void vertex(int *c_, double *x_, double *y_, double *angle_, char_properties **format_) { *c_ = c; *x_ = x; *y_ = y; *angle_ = angle; + *format_ = format; } }; @@ -147,22 +172,16 @@ struct text_path : boost::noncopyable starting_y(0), itr_(0) {} - //text_path(text_path const& other) : - // itr_(0), - // nodes_(other.nodes_), - // string_dimensions(other.string_dimensions) - //{} - ~text_path() {} - void add_node(int c, double x, double y, double angle) + void add_node(int c, double x, double y, double angle, char_properties *format) { - nodes_.push_back(character_node(c, x, y, angle)); + nodes_.push_back(character_node(c, x, y, angle, format)); } - void vertex(int *c, double *x, double *y, double *angle) + void vertex(int *c, double *x, double *y, double *angle, char_properties **format) { - nodes_[itr_++].vertex(c, x, y, angle); + nodes_[itr_++].vertex(c, x, y, angle, format); } void rewind() diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 37f29423d..cc1b68e41 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -587,8 +587,9 @@ public: { int c; double x, y, angle; + char_properties *format; - path.vertex(&c, &x, &y, &angle); + path.vertex(&c, &x, &y, &angle, &format); glyph_ptr glyph = faces->get_glyph(c); @@ -624,8 +625,9 @@ public: { int c; double x, y, angle; + char_properties *format; - path.vertex(&c, &x, &y, &angle); + path.vertex(&c, &x, &y, &angle, &format); glyph_ptr glyph = faces->get_glyph(c); diff --git a/src/font_engine_freetype.cpp b/src/font_engine_freetype.cpp index d03f9995b..159e4a942 100644 --- a/src/font_engine_freetype.cpp +++ b/src/font_engine_freetype.cpp @@ -321,8 +321,9 @@ box2d text_renderer::prepare_glyphs(text_path *path) { int c; double x, y, angle; + char_properties *properties; - path->vertex(&c, &x, &y, &angle); + path->vertex(&c, &x, &y, &angle, &properties); #ifdef MAPNIK_DEBUG // TODO Enable when we have support for setting verbosity diff --git a/src/metawriter.cpp b/src/metawriter.cpp index 8d1fe5713..8642d969d 100644 --- a/src/metawriter.cpp +++ b/src/metawriter.cpp @@ -200,11 +200,12 @@ void metawriter_json_stream::add_text(placement const& p, bool inside = false; bool straight = true; int c; double x, y, angle; + char_properties *format; current_placement.rewind(); for (int i = 0; i < current_placement.num_nodes(); ++i) { int cx = current_placement.starting_x; int cy = current_placement.starting_y; - current_placement.vertex(&c, &x, &y, &angle); + current_placement.vertex(&c, &x, &y, &angle, &format); if (cx+x >= 0 && cx+x < width_ && cy-y >= 0 && cy-y < height_) inside = true; if (angle > 0.001 || angle < -0.001) straight = false; if (inside && !straight) break; @@ -217,7 +218,7 @@ void metawriter_json_stream::add_text(placement const& p, //Reduce number of polygons double minx = INT_MAX, miny = INT_MAX, maxx = INT_MIN, maxy = INT_MIN; for (int i = 0; i < current_placement.num_nodes(); ++i) { - current_placement.vertex(&c, &x, &y, &angle); + current_placement.vertex(&c, &x, &y, &angle, &format); char_info ci = face->character_dimensions(c); if (x < minx) minx = x; if (x+ci.width > maxx) maxx = x+ci.width; @@ -240,7 +241,7 @@ void metawriter_json_stream::add_text(placement const& p, if (c != ' ') { *f_ << ","; } - current_placement.vertex(&c, &x, &y, &angle); + current_placement.vertex(&c, &x, &y, &angle, &format); if (c == ' ') continue; char_info ci = face->character_dimensions(c); diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index c965d03cf..136d3a5ae 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -274,12 +274,12 @@ void placement_finder::find_point_placement(placement & p, for (unsigned int ii = 0; ii < p.info.num_characters(); ii++) { - character_info ci; + char_info ci; ci = p.info.at(ii); double cwidth = ci.width + character_spacing; - unsigned c = ci.character; + unsigned c = ci.c; word_width += cwidth; if ((c == p.wrap_char) || (c == '\n')) @@ -400,12 +400,12 @@ void placement_finder::find_point_placement(placement & p, for (unsigned i = 0; i < p.info.num_characters(); i++) { - character_info ci; + char_info ci; ci = p.info.at(i); double cwidth = ci.width + character_spacing; - unsigned c = ci.character; + unsigned c = ci.c; if (i == index_to_wrap_at) { index_to_wrap_at = line_breaks[++line_number]; @@ -434,7 +434,7 @@ void placement_finder::find_point_placement(placement & p, double dx = x * cosa - y*sina; double dy = x * sina + y*cosa; - current_placement->add_node(c, dx, dy, rad); + current_placement->add_node(c, dx, dy, rad, ci.format); // compute the Bounding Box for each character and test for: // overlap, minimum distance or edge avoidance - exit if condition occurs @@ -738,14 +738,14 @@ std::auto_ptr placement_finder::get_placement_offs for (unsigned i = 0; i < p.info.num_characters(); ++i) { - character_info ci; + char_info ci; unsigned c; double last_character_angle = angle; // grab the next character according to the orientation ci = orientation > 0 ? p.info.at(i) : p.info.at(p.info.num_characters() - i - 1); - c = ci.character; + c = ci.c; //Coordinates this character will start at if (segment_length == 0) { @@ -838,7 +838,7 @@ std::auto_ptr placement_finder::get_placement_offs } current_placement->add_node(c,render_x - current_placement->starting_x, -render_y + current_placement->starting_y, - render_angle); + render_angle, ci.format); //Normalise to 0 <= angle < 2PI while (render_angle >= 2*M_PI) @@ -883,10 +883,11 @@ bool placement_finder::test_placement(placement & p, const std::auto_ for (unsigned i = 0; i < p.info.num_characters(); ++i) { // grab the next character according to the orientation - character_info ci = orientation > 0 ? p.info.at(i) : p.info.at(p.info.num_characters() - i - 1); + char_info ci = orientation > 0 ? p.info.at(i) : p.info.at(p.info.num_characters() - i - 1); int c; double x, y, angle; - current_placement->vertex(&c, &x, &y, &angle); + char_properties *format; + current_placement->vertex(&c, &x, &y, &angle, &format); x = current_placement->starting_x + x; y = current_placement->starting_y - y; if (orientation < 0) @@ -911,10 +912,10 @@ bool placement_finder::test_placement(placement & p, const std::auto_ // put four corners of the letter into envelope e.init(x, y, x + ci.width*cosa, y - ci.width*sina); - e.expand_to_include(x - ci.height*sina, - y - ci.height*cosa); - e.expand_to_include(x + (ci.width*cosa - ci.height*sina), - y - (ci.width*sina + ci.height*cosa)); + e.expand_to_include(x - ci.height()*sina, + y - ci.height()*cosa); + e.expand_to_include(x + (ci.width*cosa - ci.height()*sina), + y - (ci.width*sina + ci.height()*cosa)); } if (!detector_.extent().intersects(e) || From f460f90ec63d995a5adf6523d23d790eab30b88f Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 02:22:55 +0100 Subject: [PATCH 19/81] Add new text_placement functions, but keep old stuff to ensure mapnik compiles. --- include/mapnik/text_placements.hpp | 67 +++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/include/mapnik/text_placements.hpp b/include/mapnik/text_placements.hpp index 383bbcb33..c6f4aee2a 100644 --- a/include/mapnik/text_placements.hpp +++ b/include/mapnik/text_placements.hpp @@ -24,12 +24,17 @@ #define MAPNIK_TEXT_PLACEMENTS_HPP // mapnik -#include -#include +#include +#include +#include +#include +#include // stl #include #include +#include +#include // boost #include @@ -38,6 +43,8 @@ namespace mapnik { +typedef text_path placement_element; + typedef boost::tuple position; enum label_placement_enum { @@ -82,16 +89,32 @@ enum justify_alignment DEFINE_ENUM( justify_alignment_e, justify_alignment ); -enum text_transform +struct text_symbolizer_properties { - NONE = 0, - UPPERCASE, - LOWERCASE, - CAPITALIZE, - text_transform_MAX -}; + text_symbolizer_properties(); + void set_values_from_xml(boost::property_tree::ptree const &sym, std::map const & fontsets); + void to_xml(boost::property_tree::ptree &node, bool explicit_defaults, text_symbolizer_properties const &dfl=text_symbolizer_properties()) const; -DEFINE_ENUM( text_transform_e, text_transform ); + //Per symbolizer options + expression_ptr orientation; + position displacement; + label_placement_e label_placement; + horizontal_alignment_e halign; + justify_alignment_e jalign; + vertical_alignment_e valign; + unsigned label_spacing; // distance between repeated labels on a single geometry + unsigned label_position_tolerance; //distance the label can be moved on the line to fit, if 0 the default is used + bool avoid_edges; + double minimum_distance; + double minimum_padding; + double minimum_path_length; + double max_char_angle_delta; + bool force_odd_labels; //Always try render an odd amount of labels + bool allow_overlap; + unsigned text_ratio; + unsigned wrap_width; + text_processor processor; //Contains expressions and text formats +}; class text_placements; @@ -109,6 +132,24 @@ public: */ virtual bool next_position_only()=0; virtual ~text_placement_info() {} + void init(double scale_factor_, + unsigned w = 0, unsigned h = 0, bool has_dimensions_ = false); + + text_symbolizer_properties properties; + double scale_factor; + bool has_dimensions; + std::pair dimensions; + void set_scale_factor(double factor) { scale_factor = factor; } + double get_scale_factor() { return scale_factor; } + double get_actual_label_spacing() { return scale_factor * properties.label_spacing; } + double get_actual_minimum_distance() { return scale_factor * properties.minimum_distance; } + double get_actual_minimum_padding() { return scale_factor * properties.minimum_padding; } + + bool collect_extents; + //Output + box2d extents; + std::queue< box2d > envelopes; + boost::ptr_vector placements; /* NOTE: Values are public and non-virtual to avoid any performance problems. */ position displacement; @@ -142,7 +183,13 @@ public: virtual void set_default_valign(vertical_alignment_e const& align) { valign_ = align;} vertical_alignment_e const& get_default_valign() { return valign_; } + /** Get a list of all expressions used in any placement. + * This function is used to collect attributes. + */ + virtual std::set get_all_expressions(); + virtual ~text_placements() {} + text_symbolizer_properties properties; protected: float text_size_; position displacement_; From 5f6258438565efd9992794f98d2fd8c2b954c27e Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 02:23:39 +0100 Subject: [PATCH 20/81] Add header for list placement. (Unused so far.) --- include/mapnik/text_placements_list.hpp | 61 +++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 include/mapnik/text_placements_list.hpp diff --git a/include/mapnik/text_placements_list.hpp b/include/mapnik/text_placements_list.hpp new file mode 100644 index 000000000..e43fe724a --- /dev/null +++ b/include/mapnik/text_placements_list.hpp @@ -0,0 +1,61 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2011 Hermann Kraus + * + * 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 TEXT_PLACEMENTS_LIST_HPP +#define TEXT_PLACEMENTS_LIST_HPP +#include + +namespace mapnik { + +class text_placement_info_list; + + +/** Tries a list of placements. */ +class text_placements_list: public text_placements +{ +public: + text_placements_list(); + text_placement_info_ptr get_placement_info() const; + virtual std::set get_all_expressions(); + text_symbolizer_properties & add(); + text_symbolizer_properties & get(unsigned i); + unsigned size() const; +private: + std::vector list_; + friend class text_placement_info_list; +}; + +/** List placement strategy. + * See parent class for documentation of each function. */ +class text_placement_info_list : public text_placement_info +{ +public: + text_placement_info_list(text_placements_list const* parent) : + text_placement_info(parent), state(0), parent_(parent) {} + bool next(); +private: + unsigned state; + text_placements_list const* parent_; +}; + + +} //namespace +#endif From 6506edebf2ac3e76b8ed849e01acd289cf064ff8 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 02:33:00 +0100 Subject: [PATCH 21/81] Add deprecation warnings. --- include/mapnik/text_symbolizer.hpp | 69 ++++++++++++++++-------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/include/mapnik/text_symbolizer.hpp b/include/mapnik/text_symbolizer.hpp index c1ae0a8ca..88ff573d2 100644 --- a/include/mapnik/text_symbolizer.hpp +++ b/include/mapnik/text_symbolizer.hpp @@ -38,6 +38,13 @@ // stl #include +// Warning disabled for the moment +#if (0 && __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +#define func_deprecated __attribute__ ((deprecated)) +#else +#define func_deprecated +#endif + namespace mapnik { @@ -57,71 +64,71 @@ struct MAPNIK_DECL text_symbolizer : public symbolizer_base ); text_symbolizer(text_symbolizer const& rhs); text_symbolizer& operator=(text_symbolizer const& rhs); - expression_ptr get_name() const; + expression_ptr get_name() const func_deprecated; void set_name(expression_ptr expr); - expression_ptr get_orientation() const; // orienation (rotation angle atm) + expression_ptr get_orientation() const func_deprecated; // orienation (rotation angle atm) void set_orientation(expression_ptr expr); - unsigned get_text_ratio() const; // target ratio for text bounding box in pixels + unsigned get_text_ratio() const func_deprecated; // target ratio for text bounding box in pixels void set_text_ratio(unsigned ratio); - unsigned get_wrap_width() const; // width to wrap text at, or trigger ratio + unsigned get_wrap_width() const func_deprecated; // width to wrap text at, or trigger ratio void set_wrap_width(unsigned ratio); - unsigned char get_wrap_char() const; // character used to wrap lines - std::string get_wrap_char_string() const; // character used to wrap lines as std::string + unsigned char get_wrap_char() const func_deprecated; // character used to wrap lines + std::string get_wrap_char_string() const func_deprecated; // character used to wrap lines as std::string void set_wrap_char(unsigned char character); void set_wrap_char_from_string(std::string const& character); - text_transform_e get_text_transform() const; // text conversion on strings before display + text_transform_e get_text_transform() const func_deprecated; // text conversion on strings before display void set_text_transform(text_transform_e convert); - unsigned get_line_spacing() const; // spacing between lines of text + unsigned get_line_spacing() const func_deprecated; // spacing between lines of text void set_line_spacing(unsigned spacing); - unsigned get_character_spacing() const; // spacing between characters in text + unsigned get_character_spacing() const func_deprecated; // spacing between characters in text void set_character_spacing(unsigned spacing); - unsigned get_label_spacing() const; // spacing between repeated labels on lines + unsigned get_label_spacing() const func_deprecated; // spacing between repeated labels on lines void set_label_spacing(unsigned spacing); - unsigned get_label_position_tolerance() const; //distance the label can be moved on the line to fit, if 0 the default is used + 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; // try render an odd amount of labels + 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; // maximum change in angle between adjacent characters + double get_max_char_angle_delta() const func_deprecated; // maximum change in angle between adjacent characters void set_max_char_angle_delta(double angle); - float get_text_size() const; + float get_text_size() const func_deprecated; void set_text_size(float size); - std::string const& get_face_name() const; + std::string const& get_face_name() const func_deprecated; void set_face_name(std::string face_name); - font_set const& get_fontset() const; + font_set const& get_fontset() const func_deprecated; void set_fontset(font_set const& fset); - color const& get_fill() const; + color const& get_fill() const func_deprecated; void set_fill(color const& fill); void set_halo_fill(color const& fill); - color const& get_halo_fill() const; + color const& get_halo_fill() const func_deprecated; void set_halo_radius(double radius); - double get_halo_radius() const; + double get_halo_radius() const func_deprecated; void set_label_placement(label_placement_e label_p); - label_placement_e get_label_placement() const; + label_placement_e get_label_placement() const func_deprecated; void set_vertical_alignment(vertical_alignment_e valign); - vertical_alignment_e get_vertical_alignment() const; + 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; + position const& get_displacement() const func_deprecated; void set_avoid_edges(bool avoid); - bool get_avoid_edges() const; + bool get_avoid_edges() const func_deprecated; void set_minimum_distance(double distance); - double get_minimum_distance() const; + double get_minimum_distance() const func_deprecated; void set_minimum_padding(double distance); - double get_minimum_padding() const; + double get_minimum_padding() const func_deprecated; void set_minimum_path_length(double size); - double get_minimum_path_length() const; + double get_minimum_path_length() const func_deprecated; void set_allow_overlap(bool overlap); - bool get_allow_overlap() const; + bool get_allow_overlap() const func_deprecated; void set_text_opacity(double opacity); - double get_text_opacity() const; - bool get_wrap_before() const; // wrap text at wrap_char immediately before current work + double get_text_opacity() const func_deprecated; void set_wrap_before(bool wrap_before); + bool get_wrap_before() const func_deprecated; // wrap text at wrap_char immediately before current work void set_horizontal_alignment(horizontal_alignment_e valign); - horizontal_alignment_e get_horizontal_alignment() const; + horizontal_alignment_e get_horizontal_alignment() const func_deprecated; void set_justify_alignment(justify_alignment_e valign); - justify_alignment_e get_justify_alignment() const; + justify_alignment_e get_justify_alignment() const func_deprecated; text_placements_ptr get_placement_options() const; void set_placement_options(text_placements_ptr placement_options); From cc048986d8a23d1e7c66f3421521b2d1288241ff Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 02:38:46 +0100 Subject: [PATCH 22/81] Text size as float. --- include/mapnik/text_processing.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/mapnik/text_processing.hpp b/include/mapnik/text_processing.hpp index 1dce42c93..ab111dc1b 100644 --- a/include/mapnik/text_processing.hpp +++ b/include/mapnik/text_processing.hpp @@ -56,7 +56,7 @@ struct char_properties void to_xml(boost::property_tree::ptree &node, bool explicit_defaults, char_properties const &dfl=char_properties()) const; std::string face_name; font_set fontset; - unsigned text_size; + float text_size; double character_spacing; double line_spacing; //Largest total height (fontsize+line_spacing) per line is chosen double text_opacity; From 6695fa0f5ff7439be5a6b2fdc15520592040d80f Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 02:56:28 +0100 Subject: [PATCH 23/81] Add functions to read and write text_symbolizer_properties and char_properties from/to XML. --- src/text_placements.cpp | 273 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) diff --git a/src/text_placements.cpp b/src/text_placements.cpp index aca06a5ac..a1f62ac7e 100644 --- a/src/text_placements.cpp +++ b/src/text_placements.cpp @@ -22,11 +22,16 @@ #include #include +#include +#include +#include +#include #include #include #include #include +#include namespace mapnik { @@ -36,6 +41,274 @@ using boost::spirit::ascii::space; using phoenix::push_back; using phoenix::ref; using qi::_1; +using boost::optional; + +text_symbolizer_properties::text_symbolizer_properties() : + label_placement(POINT_PLACEMENT), + halign(H_AUTO), + jalign(J_MIDDLE), + valign(V_AUTO), + label_spacing(0), + label_position_tolerance(0), + avoid_edges(false), + minimum_distance(0.0), + minimum_padding(0.0), + max_char_angle_delta(22.5 * M_PI/180.0), + force_odd_labels(false), + allow_overlap(false), + text_ratio(0), + wrap_width(0), + processor() +{ + +} + +void text_symbolizer_properties::set_values_from_xml(boost::property_tree::ptree const &sym, std::map const & fontsets) +{ + optional placement_ = get_opt_attr(sym, "placement"); + if (placement_) label_placement = *placement_; + optional valign_ = get_opt_attr(sym, "vertical-alignment"); + if (valign_) valign = *valign_; + optional text_ratio_ = get_opt_attr(sym, "text-ratio"); + if (text_ratio_) text_ratio = *text_ratio_; + optional wrap_width_ = get_opt_attr(sym, "wrap-width"); + if (wrap_width_) wrap_width = *wrap_width_; + optional label_position_tolerance_ = get_opt_attr(sym, "label-position-tolerance"); + if (label_position_tolerance_) label_position_tolerance = *label_position_tolerance_; + optional spacing_ = get_opt_attr(sym, "spacing"); + if (spacing_) label_spacing = *spacing_; + optional minimum_distance_ = get_opt_attr(sym, "minimum-distance"); + if (minimum_distance_) minimum_distance = *minimum_distance_; + optional min_padding_ = get_opt_attr(sym, "minimum-padding"); + if (min_padding_) minimum_padding = *min_padding_; + optional min_path_length_ = get_opt_attr(sym, "minimum-path-length"); + if (min_path_length_) minimum_path_length = *min_path_length_; + optional avoid_edges_ = get_opt_attr(sym, "avoid-edges"); + if (avoid_edges_) avoid_edges = *avoid_edges_; + optional allow_overlap_ = get_opt_attr(sym, "allow-overlap"); + if (allow_overlap_) allow_overlap = *allow_overlap_; + optional halign_ = get_opt_attr(sym, "horizontal-alignment"); + if (halign_) halign = *halign_; + optional jalign_ = get_opt_attr(sym, "justify-alignment"); + if (jalign_) jalign = *jalign_; + /* Attributes needing special care */ + optional orientation_ = get_opt_attr(sym, "orientation"); + if (orientation_) orientation = parse_expression(*orientation_, "utf8"); + optional dx = get_opt_attr(sym, "dx"); + if (dx) displacement.get<0>() = *dx; + optional dy = get_opt_attr(sym, "dy"); + if (dy) displacement.get<1>() = *dy; + optional max_char_angle_delta_ = get_opt_attr(sym, "max-char-angle-delta"); + if (max_char_angle_delta_) max_char_angle_delta=(*max_char_angle_delta_)*(M_PI/180); + processor.from_xml(sym, fontsets); + optional name_ = get_opt_attr(sym, "name"); + if (name_) { + std::clog << "### WARNING: Using 'name' in TextSymbolizer/ShieldSymbolizer is deprecated!\n"; + processor.set_old_style_expression(parse_expression(*name_, "utf8")); + } +} + +void text_symbolizer_properties::to_xml(boost::property_tree::ptree &node, bool explicit_defaults, text_symbolizer_properties const &dfl) const +{ + if (orientation) + { + const std::string & orientationstr = to_expression_string(*orientation); + if (!dfl.orientation || orientationstr != to_expression_string(*(dfl.orientation)) || explicit_defaults) { + set_attr(node, "orientation", orientationstr); + } + } + + if (displacement.get<0>() != dfl.displacement.get<0>() || explicit_defaults) + { + set_attr(node, "dx", displacement.get<0>()); + } + if (displacement.get<1>() != dfl.displacement.get<1>() || explicit_defaults) + { + set_attr(node, "dy", displacement.get<1>()); + } + if (label_placement != dfl.label_placement || explicit_defaults) + { + set_attr(node, "placement", label_placement); + } + if (valign != dfl.valign || explicit_defaults) + { + set_attr(node, "vertical-alignment", valign); + } + if (text_ratio != dfl.text_ratio || explicit_defaults) + { + set_attr(node, "text-ratio", text_ratio); + } + if (wrap_width != dfl.wrap_width || explicit_defaults) + { + set_attr(node, "wrap-width", wrap_width); + } + if (label_position_tolerance != dfl.label_position_tolerance || explicit_defaults) + { + set_attr(node, "label-position-tolerance", label_position_tolerance); + } + if (label_spacing != dfl.label_spacing || explicit_defaults) + { + set_attr(node, "spacing", label_spacing); + } + if (minimum_distance != dfl.minimum_distance || explicit_defaults) + { + set_attr(node, "minimum-distance", minimum_distance); + } + if (minimum_padding != dfl.minimum_padding || explicit_defaults) + { + set_attr(node, "minimum-padding", minimum_padding); + } + if (minimum_path_length != dfl.minimum_path_length || explicit_defaults) + { + set_attr(node, "minimum-path-length", minimum_path_length); + } + if (allow_overlap != dfl.allow_overlap || explicit_defaults) + { + set_attr(node, "allow-overlap", allow_overlap); + } + if (avoid_edges != dfl.avoid_edges || explicit_defaults) + { + set_attr(node, "avoid-edges", avoid_edges); + } + if (max_char_angle_delta != dfl.max_char_angle_delta || explicit_defaults) + { + set_attr(node, "max-char-angle-delta", max_char_angle_delta); + } + if (halign != dfl.halign || explicit_defaults) + { + set_attr(node, "horizontal-alignment", halign); + } + if (jalign != dfl.jalign || explicit_defaults) + { + set_attr(node, "justify-alignment", jalign); + } + if (valign != dfl.valign || explicit_defaults) + { + set_attr(node, "vertical-alignment", valign); + } + processor.to_xml(node, explicit_defaults, dfl.processor); +} + +char_properties::char_properties() : + text_size(10.0), + character_spacing(0), + line_spacing(0), + text_opacity(1.0), + wrap_before(false), + wrap_char(' '), + text_transform(NONE), + fill(color(0,0,0)), + halo_fill(color(255,255,255)), + halo_radius(0) +{ + +} + +void char_properties::set_values_from_xml(boost::property_tree::ptree const &sym, std::map const & fontsets) +{ + optional text_size_ = get_opt_attr(sym, "size"); + if (text_size_) text_size = *text_size_; + optional character_spacing_ = get_opt_attr(sym, "character-spacing"); + if (character_spacing_) character_spacing = *character_spacing_; + optional fill_ = get_opt_attr(sym, "fill"); + if (fill_) fill = *fill_; + optional halo_fill_ = get_opt_attr(sym, "halo-fill"); + if (halo_fill_) halo_fill = *halo_fill_; + optional halo_radius_ = get_opt_attr(sym, "halo-radius"); + if (halo_radius_) halo_radius = *halo_radius_; + optional wrap_before_ = get_opt_attr(sym, "wrap-before"); + if (wrap_before_) wrap_before = *wrap_before_; + optional tconvert_ = get_opt_attr(sym, "text-transform"); + if (tconvert_) text_transform = *tconvert_; + optional line_spacing_ = get_opt_attr(sym, "line-spacing"); + if (line_spacing_) line_spacing = *line_spacing_; + optional opacity_ = get_opt_attr(sym, "opacity"); + if (opacity_) text_opacity = *opacity_; + optional wrap_char_ = get_opt_attr(sym, "wrap-character"); + if (wrap_char_ && (*wrap_char_).size() > 0) wrap_char = ((*wrap_char_)[0]); + optional face_name_ = get_opt_attr(sym, "face-name"); + if (face_name_) + { + face_name = *face_name_; + } + optional fontset_name_ = get_opt_attr(sym, "fontset-name"); + if (fontset_name_) { + std::map::const_iterator itr = fontsets.find(*fontset_name_); + if (itr != fontsets.end()) + { + fontset = itr->second; + } else + { + throw config_error("Unable to find any fontset named '" + *fontset_name_ + "'"); + } + } + if (!face_name.empty() && !fontset.get_name().empty()) + { + throw config_error(std::string("Can't have both face-name and fontset-name")); + } + if (face_name.empty() && fontset.get_name().empty()) + { + throw config_error(std::string("Must have face-name or fontset-name")); + } +} + +void char_properties::to_xml(boost::property_tree::ptree &node, bool explicit_defaults, char_properties const &dfl) const +{ + const std::string & fontset_name = fontset.get_name(); + const std::string & dfl_fontset_name = dfl.fontset.get_name(); + if (fontset_name != dfl_fontset_name || explicit_defaults) + { + set_attr(node, "fontset-name", fontset_name); + } + + if (face_name != dfl.face_name || explicit_defaults) + { + set_attr(node, "face-name", face_name); + } + + if (text_size != dfl.text_size || explicit_defaults) + { + set_attr(node, "size", text_size); + } + + if (fill != dfl.fill || explicit_defaults) + { + set_attr(node, "fill", fill); + } + if (halo_radius != dfl.halo_radius || explicit_defaults) + { + set_attr(node, "halo-radius", halo_radius); + } + if (halo_fill != dfl.halo_fill || explicit_defaults) + { + 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)); + } + if (text_transform != dfl.text_transform || explicit_defaults) + { + set_attr(node, "text-transform", text_transform); + } + if (line_spacing != dfl.line_spacing || explicit_defaults) + { + set_attr(node, "line-spacing", line_spacing); + } + if (character_spacing != dfl.character_spacing || explicit_defaults) + { + set_attr(node, "character-spacing", character_spacing); + } + // for shield_symbolizer this is later overridden + if (text_opacity != dfl.text_opacity || explicit_defaults) + { + set_attr(node, "opacity", text_opacity); + } +} /************************************************************************/ From c407d8340f8a6184147d1fd8efe2e3e283fbe99c Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 03:01:34 +0100 Subject: [PATCH 24/81] Add default get_all_expressions function. --- src/text_placements.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/text_placements.cpp b/src/text_placements.cpp index a1f62ac7e..7ca09b5b6 100644 --- a/src/text_placements.cpp +++ b/src/text_placements.cpp @@ -310,6 +310,23 @@ void char_properties::to_xml(boost::property_tree::ptree &node, bool explicit_de } } +/************************************************************************/ + +#if 0 +text_placements::text_placements() : properties() +{ +} +#endif + +std::set text_placements::get_all_expressions() +{ + std::set result, tmp; + tmp = properties.processor.get_all_expressions(); + result.insert(tmp.begin(), tmp.end()); + result.insert(properties.orientation); + return result; +} + /************************************************************************/ From 2b26044adb5d1aa691c2ce6f43f25d65468e861f Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 03:04:37 +0100 Subject: [PATCH 25/81] Complete text_placement_info functions. --- src/text_placements.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/text_placements.cpp b/src/text_placements.cpp index 7ca09b5b6..554dd53fb 100644 --- a/src/text_placements.cpp +++ b/src/text_placements.cpp @@ -333,7 +333,11 @@ std::set text_placements::get_all_expressions() text_placement_info::text_placement_info(text_placements const* parent): displacement(parent->displacement_), text_size(parent->text_size_), halign(parent->halign_), jalign(parent->jalign_), - valign(parent->valign_) + valign(parent->valign_), + properties(parent->properties), + scale_factor(1), + has_dimensions(false), + collect_extents(false) { } @@ -357,6 +361,13 @@ text_placement_info_ptr text_placements_dummy::get_placement_info() const return text_placement_info_ptr(new text_placement_info_dummy(this)); } +void text_placement_info::init(double scale_factor_, + unsigned w, unsigned h, bool has_dimensions_) +{ + scale_factor = scale_factor_; + dimensions = std::make_pair(w, h); + has_dimensions = has_dimensions_; +} /************************************************************************/ From 7d01f509abab01eac86c9a06fbb71ac5ccc91d7b Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 03:11:55 +0100 Subject: [PATCH 26/81] Add text_placement_info_list functions. --- include/mapnik/text_placements.hpp | 2 +- src/text_placements.cpp | 66 ++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/include/mapnik/text_placements.hpp b/include/mapnik/text_placements.hpp index c6f4aee2a..14c55367d 100644 --- a/include/mapnik/text_placements.hpp +++ b/include/mapnik/text_placements.hpp @@ -130,7 +130,7 @@ public: * Each class has to return at least one position! * If this functions returns false the placement data should be considered invalid! */ - virtual bool next_position_only()=0; + virtual bool next_position_only() { return false; } virtual ~text_placement_info() {} void init(double scale_factor_, unsigned w = 0, unsigned h = 0, bool has_dimensions_ = false); diff --git a/src/text_placements.cpp b/src/text_placements.cpp index 554dd53fb..d14bef71a 100644 --- a/src/text_placements.cpp +++ b/src/text_placements.cpp @@ -492,4 +492,70 @@ text_placements_simple::text_placements_simple(std::string positions) { set_positions(positions); } + +/***************************************************************************/ + +bool text_placement_info_list::next() +{ + if (state == 0) { + properties = parent_->properties; + } else { + if (state-1 >= parent_->list_.size()) return false; + properties = parent_->list_[state-1]; + } + state++; + return true; +} + +text_symbolizer_properties & text_placements_list::add() +{ + if (list_.size()) { + text_symbolizer_properties &last = list_.back(); + list_.push_back(last); //Preinitialize with old values + } else { + list_.push_back(properties); + } + return list_.back(); +} + +text_symbolizer_properties & text_placements_list::get(unsigned i) +{ + return list_[i]; +} + +/***************************************************************************/ + +text_placement_info_ptr text_placements_list::get_placement_info() const +{ + return text_placement_info_ptr(new text_placement_info_list(this)); +} + +text_placements_list::text_placements_list() : text_placements(), list_(0) +{ + +} + +std::set text_placements_list::get_all_expressions() +{ + std::set result, tmp; + tmp = properties.processor.get_all_expressions(); + result.insert(tmp.begin(), tmp.end()); + result.insert(properties.orientation); + + std::vector::const_iterator it; + for (it=list_.begin(); it != list_.end(); it++) + { + tmp = it->processor.get_all_expressions(); + result.insert(tmp.begin(), tmp.end()); + result.insert(it->orientation); + } + return result; +} + +unsigned text_placements_list::size() const +{ + return list_.size(); +} + + } //namespace From cae0c31ee8f54a8d6d5c4de3854473edbd6cb17e Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 03:23:27 +0100 Subject: [PATCH 27/81] Update text_placement_info_simple to use new functions and locations. --- src/text_placements.cpp | 49 +++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/text_placements.cpp b/src/text_placements.cpp index d14bef71a..f6d81224b 100644 --- a/src/text_placements.cpp +++ b/src/text_placements.cpp @@ -373,56 +373,64 @@ void text_placement_info::init(double scale_factor_, bool text_placement_info_simple::next() { - position_state = 0; - if (state == 0) { - text_size = parent_->text_size_; - } else { - if (state > parent_->text_sizes_.size()) return false; - text_size = parent_->text_sizes_[state-1]; + while (1) { + if (state == 0) { + properties.processor.defaults.text_size = parent_->properties.processor.defaults.text_size; + } else { + if (state > parent_->text_sizes_.size()) return false; + properties.processor.defaults.text_size = parent_->text_sizes_[state-1]; + } + if (!next_position_only()) { + state++; + position_state = 0; + } else { + break; + } } - state++; return true; } bool text_placement_info_simple::next_position_only() { + const position &pdisp = parent_->properties.displacement; + position &displacement = properties.displacement; if (position_state >= parent_->direction_.size()) return false; directions_t dir = parent_->direction_[position_state]; switch (dir) { case EXACT_POSITION: - displacement = parent_->displacement_; + displacement = pdisp; break; case NORTH: - displacement = boost::make_tuple(0, -abs(parent_->displacement_.get<1>())); + displacement = boost::make_tuple(0, -abs(pdisp.get<1>())); break; case EAST: - displacement = boost::make_tuple(abs(parent_->displacement_.get<0>()), 0); + displacement = boost::make_tuple(abs(pdisp.get<0>()), 0); break; case SOUTH: - displacement = boost::make_tuple(0, abs(parent_->displacement_.get<1>())); + displacement = boost::make_tuple(0, abs(pdisp.get<1>())); break; case WEST: - displacement = boost::make_tuple(-abs(parent_->displacement_.get<0>()), 0); + displacement = boost::make_tuple(-abs(pdisp.get<0>()), 0); break; case NORTHEAST: displacement = boost::make_tuple( - abs(parent_->displacement_.get<0>()), - -abs(parent_->displacement_.get<1>())); + abs(pdisp.get<0>()), + -abs(pdisp.get<1>())); break; case SOUTHEAST: displacement = boost::make_tuple( - abs(parent_->displacement_.get<0>()), - abs(parent_->displacement_.get<1>())); + abs(pdisp.get<0>()), + abs(pdisp.get<1>())); break; case NORTHWEST: displacement = boost::make_tuple( - -abs(parent_->displacement_.get<0>()), - -abs(parent_->displacement_.get<1>())); + -abs(pdisp.get<0>()), + -abs(pdisp.get<1>())); break; case SOUTHWEST: displacement = boost::make_tuple( - -abs(parent_->displacement_.get<0>()), - abs(parent_->displacement_.get<1>())); + -abs(pdisp.get<0>()), + abs(pdisp.get<1>())); break; default: std::cerr << "WARNING: Unknown placement\n"; @@ -431,7 +439,6 @@ bool text_placement_info_simple::next_position_only() return true; } - text_placement_info_ptr text_placements_simple::get_placement_info() const { return text_placement_info_ptr(new text_placement_info_simple(this)); From 7d03b460793cecbb15ca2dc002d719f631148614 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 03:25:19 +0100 Subject: [PATCH 28/81] Update attribute_collector to use get_all_expressions(). --- include/mapnik/attribute_collector.hpp | 30 +++++++++----------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/include/mapnik/attribute_collector.hpp b/include/mapnik/attribute_collector.hpp index c43a7105c..8efd91911 100644 --- a/include/mapnik/attribute_collector.hpp +++ b/include/mapnik/attribute_collector.hpp @@ -24,16 +24,11 @@ #define MAPNIK_ATTRIBUTE_COLLECTOR_HPP // mapnik -#include #include -#include -#include - // boost #include #include #include - // stl #include #include @@ -93,18 +88,12 @@ struct symbolizer_attributes : public boost::static_visitor<> void operator () (text_symbolizer const& sym) { - expression_ptr const& name_expr = sym.get_name(); - if (name_expr) + std::set::const_iterator it; + std::set expressions = sym.get_placement_options()->get_all_expressions(); + expression_attributes f_attr(names_); + for (it=expressions.begin(); it != expressions.end(); it++) { - expression_attributes f_attr(names_); - boost::apply_visitor(f_attr,*name_expr); - } - - expression_ptr const& orientation_expr = sym.get_orientation(); - if (orientation_expr) - { - expression_attributes f_attr(names_); - boost::apply_visitor(f_attr,*orientation_expr); + if (*it) boost::apply_visitor(f_attr, **it); } collect_metawriter(sym); } @@ -152,11 +141,12 @@ struct symbolizer_attributes : public boost::static_visitor<> void operator () (shield_symbolizer const& sym) { - expression_ptr const& name_expr = sym.get_name(); - if (name_expr) + std::set::const_iterator it; + std::set expressions = sym.get_placement_options()->get_all_expressions(); + expression_attributes f_attr(names_); + for (it=expressions.begin(); it != expressions.end(); it++) { - expression_attributes name_attr(names_); - boost::apply_visitor(name_attr,*name_expr); + if (*it) boost::apply_visitor(f_attr, **it); } path_expression_ptr const& filename_expr = sym.get_filename(); From 7fd9fb0c88e8ddb91b94845fdf6322cb091cbfa0 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 03:32:21 +0100 Subject: [PATCH 29/81] Small fixes. --- include/mapnik/text_placements.hpp | 9 +++------ src/text_placements.cpp | 4 +--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/include/mapnik/text_placements.hpp b/include/mapnik/text_placements.hpp index 14c55367d..0c462046a 100644 --- a/include/mapnik/text_placements.hpp +++ b/include/mapnik/text_placements.hpp @@ -123,13 +123,11 @@ class text_placement_info : boost::noncopyable public: text_placement_info(text_placements const* parent); /** Get next placement. - * This function is also called before the first placement is tried. */ - virtual bool next()=0; - /** Get next placement position. - * This function is also called before the first position is used. + * This function is also called before the first placement is tried. * Each class has to return at least one position! * If this functions returns false the placement data should be considered invalid! */ + virtual bool next()=0; virtual bool next_position_only() { return false; } virtual ~text_placement_info() {} void init(double scale_factor_, @@ -164,8 +162,7 @@ typedef boost::shared_ptr text_placement_info_ptr; class text_placements { public: - text_placements() : - text_size_(10), halign_(H_MIDDLE), jalign_(J_MIDDLE), valign_(V_MIDDLE) {} + text_placements(); virtual text_placement_info_ptr get_placement_info() const =0; virtual void set_default_text_size(float size) { text_size_ = size; } diff --git a/src/text_placements.cpp b/src/text_placements.cpp index f6d81224b..5eeb769c4 100644 --- a/src/text_placements.cpp +++ b/src/text_placements.cpp @@ -312,11 +312,9 @@ void char_properties::to_xml(boost::property_tree::ptree &node, bool explicit_de /************************************************************************/ -#if 0 text_placements::text_placements() : properties() { } -#endif std::set text_placements::get_all_expressions() { @@ -444,7 +442,7 @@ text_placement_info_ptr text_placements_simple::get_placement_info() const return text_placement_info_ptr(new text_placement_info_simple(this)); } -/** Positiion string: [POS][SIZE] +/** Position string: [POS][SIZE] * [POS] is any combination of * N, E, S, W, NE, SE, NW, SW, X (exact position) (separated by commas) * [SIZE] is a list of font sizes, separated by commas. The first font size From a9ca2f0c408540f4ea69997ed31082e704366c93 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 03:39:59 +0100 Subject: [PATCH 30/81] Add list placement support to load_map. --- src/load_map.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/load_map.cpp b/src/load_map.cpp index fffd85d19..1431a3beb 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -48,6 +48,10 @@ #include #include +#include +#include +#include +#include // boost #include @@ -1241,28 +1245,34 @@ void map_parser::parse_polygon_pattern_symbolizer( rule & rule, void map_parser::parse_text_symbolizer( rule & rule, ptree const & sym ) { - std::stringstream s; - s << "name,face-name,fontset-name,size,fill,orientation," + std::stringstream s_common; + s_common << "name,face-name,fontset-name,size,fill,orientation," << "dx,dy,placement,vertical-alignment,halo-fill," << "halo-radius,text-ratio,wrap-width,wrap-before," << "wrap-character,text-transform,line-spacing," << "label-position-tolerance,character-spacing," << "spacing,minimum-distance,minimum-padding,minimum-path-length," << "avoid-edges,allow-overlap,opacity,max-char-angle-delta," - << "horizontal-alignment,justify-alignment," - << "placements,placement-type," + << "horizontal-alignment,justify-alignment"; + + std::stringstream s_symbolizer; + s_symbolizer << s_common.str() << ",placements,placement-type," << "meta-writer,meta-output"; - ensure_attrs(sym, "TextSymbolizer", s.str()); + ensure_attrs(sym, "TextSymbolizer", s_symbolizer.str()); try { text_placements_ptr placement_finder; + text_placements_list *list = 0; optional placement_type = get_opt_attr(sym, "placement-type"); if (placement_type) { if (*placement_type == "simple") { placement_finder = text_placements_ptr( new text_placements_simple( get_attr(sym, "placements", "X"))); + } else if (*placement_type == "list") { + list = new text_placements_list(); + placement_finder = text_placements_ptr(list); } else if (*placement_type != "dummy" && *placement_type != "") { throw config_error(std::string("Unknown placement type '"+*placement_type+"'")); } From 06ec0c377b204a5f4f0ddecc53d7d76df7bf5406 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 03:53:05 +0100 Subject: [PATCH 31/81] Remove code using old functions in parse_text_symbolizer(). --- src/load_map.cpp | 208 ----------------------------------------------- 1 file changed, 208 deletions(-) diff --git a/src/load_map.cpp b/src/load_map.cpp index 1431a3beb..786d6e1b9 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -1281,214 +1281,6 @@ void map_parser::parse_text_symbolizer( rule & rule, ptree const & sym ) placement_finder = text_placements_ptr(new text_placements_dummy()); } - std::string name; - optional old_name = get_opt_attr(sym, "name"); - if (old_name) { - std::clog << ": ### WARNING: Using 'name' in TextSymbolizer is deprecated (http://trac.mapnik.org/wiki/TextSymbolizer)\n"; - name = *old_name; - } else { - name = get_value(sym, "TextSymbolizer"); - if (name.empty()) throw config_error(std::string("TextSymbolizer needs a non-empty text")); - } - - optional face_name = - get_opt_attr(sym, "face-name"); - - optional fontset_name = - get_opt_attr(sym, "fontset-name"); - - float size = get_attr(sym, "size", 10.0f); - - color c = get_attr(sym, "fill", color(0,0,0)); - - text_symbolizer text_symbol = text_symbolizer(parse_expression(name, "utf8"), size, c, placement_finder); - - optional orientation = get_opt_attr(sym, "orientation"); - if (orientation) - { - text_symbol.set_orientation(parse_expression(*orientation, "utf8")); - } - - if (fontset_name && face_name) - { - throw config_error(std::string("Can't have both face-name and fontset-name")); - } - else if (fontset_name) - { - std::map::const_iterator itr = fontsets_.find(*fontset_name); - if (itr != fontsets_.end()) - { - text_symbol.set_fontset(itr->second); - } - else - { - throw config_error("Unable to find any fontset named '" + *fontset_name + "'"); - } - } - else if (face_name) - { - if ( strict_ ) - { - ensure_font_face(*face_name); - } - text_symbol.set_face_name(*face_name); - } - else - { - throw config_error(std::string("Must have face-name or fontset-name")); - } - - double dx = get_attr(sym, "dx", 0.0); - double dy = get_attr(sym, "dy", 0.0); - text_symbol.set_displacement(dx,dy); - - label_placement_e placement = - get_attr(sym, "placement", POINT_PLACEMENT); - text_symbol.set_label_placement( placement ); - - // vertical alignment - vertical_alignment_e default_vertical_alignment = V_AUTO; - - vertical_alignment_e valign = get_attr(sym, "vertical-alignment", default_vertical_alignment); - text_symbol.set_vertical_alignment(valign); - - // halo fill and radius - optional halo_fill = get_opt_attr(sym, "halo-fill"); - if (halo_fill) - { - text_symbol.set_halo_fill( * halo_fill ); - } - optional halo_radius = - get_opt_attr(sym, "halo-radius"); - if (halo_radius) - { - text_symbol.set_halo_radius(*halo_radius); - } - - // text ratio and wrap width - optional text_ratio = - get_opt_attr(sym, "text-ratio"); - if (text_ratio) - { - text_symbol.set_text_ratio(*text_ratio); - } - - optional wrap_width = - get_opt_attr(sym, "wrap-width"); - if (wrap_width) - { - text_symbol.set_wrap_width(*wrap_width); - } - - optional wrap_before = - get_opt_attr(sym, "wrap-before"); - if (wrap_before) - { - text_symbol.set_wrap_before(*wrap_before); - } - - // character used to break long strings - optional wrap_char = - get_opt_attr(sym, "wrap-character"); - if (wrap_char && (*wrap_char).size() > 0) - { - text_symbol.set_wrap_char((*wrap_char)[0]); - } - - // text conversion before rendering - text_transform_e tconvert = - get_attr(sym, "text-transform", NONE); - text_symbol.set_text_transform(tconvert); - - // spacing between text lines - optional line_spacing = get_opt_attr(sym, "line-spacing"); - if (line_spacing) - { - text_symbol.set_line_spacing(*line_spacing); - } - - // tolerance between label spacing along line - optional label_position_tolerance = get_opt_attr(sym, "label-position-tolerance"); - if (label_position_tolerance) - { - text_symbol.set_label_position_tolerance(*label_position_tolerance); - } - - // spacing between characters in text - optional character_spacing = get_opt_attr(sym, "character-spacing"); - if (character_spacing) - { - text_symbol.set_character_spacing(*character_spacing); - } - - // spacing between repeated labels on lines - optional spacing = get_opt_attr(sym, "spacing"); - if (spacing) - { - text_symbol.set_label_spacing(*spacing); - } - - // minimum distance between labels - optional min_distance = get_opt_attr(sym, "minimum-distance"); - if (min_distance) - { - text_symbol.set_minimum_distance(*min_distance); - } - - // minimum distance from edge of the map - optional min_padding = get_opt_attr(sym, "minimum-padding"); - if (min_padding) - { - text_symbol.set_minimum_padding(*min_padding); - } - - // minimum path length - optional min_path_length = get_opt_attr(sym, "minimum-path-length"); - if (min_path_length) - { - text_symbol.set_minimum_path_length(*min_path_length); - } - - // do not render labels around edges - optional avoid_edges = - get_opt_attr(sym, "avoid-edges"); - if (avoid_edges) - { - text_symbol.set_avoid_edges( * avoid_edges); - } - - // allow_overlap - optional allow_overlap = - get_opt_attr(sym, "allow-overlap"); - if (allow_overlap) - { - text_symbol.set_allow_overlap( * allow_overlap ); - } - - // opacity - optional opacity = - get_opt_attr(sym, "opacity"); - if (opacity) - { - text_symbol.set_text_opacity( * opacity ); - } - - // max_char_angle_delta - optional max_char_angle_delta = - get_opt_attr(sym, "max-char-angle-delta"); - if (max_char_angle_delta) - { - text_symbol.set_max_char_angle_delta( (*max_char_angle_delta)*(M_PI/180)); - } - - // horizontal alignment - horizontal_alignment_e halign = get_attr(sym, "horizontal-alignment", H_AUTO); - text_symbol.set_horizontal_alignment(halign); - - // justify alignment - justify_alignment_e jalign = get_attr(sym, "justify-alignment", J_MIDDLE); - text_symbol.set_justify_alignment(jalign); - parse_metawriter_in_symbolizer(text_symbol, sym); rule.append(text_symbol); } From 8244cd0a05b80f377357c41176d636b5f73a8671 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 03:56:00 +0100 Subject: [PATCH 32/81] Add code using new functions in parse_text_symbolizer(). --- src/load_map.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/load_map.cpp b/src/load_map.cpp index 786d6e1b9..b63344ed8 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -1281,6 +1281,26 @@ void map_parser::parse_text_symbolizer( rule & rule, ptree const & sym ) placement_finder = text_placements_ptr(new text_placements_dummy()); } + text_symbolizer text_symbol = text_symbolizer(placement_finder); + placement_finder->properties.set_values_from_xml(sym, fontsets_); + if (strict_) ensure_font_face(placement_finder->properties.processor.defaults.face_name); + if (list) { + ptree::const_iterator symIter = sym.begin(); + ptree::const_iterator endSym = sym.end(); + for( ;symIter != endSym; ++symIter) { + if (symIter->first.find('<') != std::string::npos) continue; + if (symIter->first != "Placement") + { +// throw config_error("Unknown element '" + symIter->first + "'"); TODO + continue; + } + ensure_attrs(symIter->second, "TextSymbolizer/Placement", s_common.str()); + text_symbolizer_properties & p = list->add(); + p.set_values_from_xml(symIter->second, fontsets_); + if (strict_) ensure_font_face(p.processor.defaults.face_name); + } + } + parse_metawriter_in_symbolizer(text_symbol, sym); rule.append(text_symbol); } From 6ce55a0e949d3024ccde686ad1756b94f82673f0 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 04:21:01 +0100 Subject: [PATCH 33/81] Update parse_shield_symbolizer(). --- src/load_map.cpp | 280 ++++++++++------------------------------------- 1 file changed, 60 insertions(+), 220 deletions(-) diff --git a/src/load_map.cpp b/src/load_map.cpp index b63344ed8..c6e3f47e8 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -1300,7 +1300,6 @@ void map_parser::parse_text_symbolizer( rule & rule, ptree const & sym ) if (strict_) ensure_font_face(p.processor.defaults.face_name); } } - parse_metawriter_in_symbolizer(text_symbol, sym); rule.append(text_symbol); } @@ -1315,7 +1314,6 @@ void map_parser::parse_shield_symbolizer( rule & rule, ptree const & sym ) { std::stringstream s; - //std::string a[] = {"a","b"}; s << "name,face-name,fontset-name,size,fill," << "dx,dy,placement,vertical-alignment,halo-fill," << "halo-radius,text-ratio,wrap-width,wrap-before," @@ -1325,39 +1323,74 @@ void map_parser::parse_shield_symbolizer( rule & rule, ptree const & sym ) << "avoid-edges,allow-overlap,opacity,max-char-angle-delta," << "horizontal-alignment,justify-alignment," // additional for shield - /* transform instead of orientation */ + /* transform instead of orientation */ << "file,base,transform,shield-dx,shield-dy," << "text-opacity,unlock-image,no-text," - << "meta-writer,meta-output"; - + << "meta-writer,meta-output"; + ensure_attrs(sym, "ShieldSymbolizer", s.str()); try { - optional no_text = - get_opt_attr(sym, "no-text"); - std::string name; - optional old_name = get_opt_attr(sym, "name"); - if (old_name) { - std::clog << ": ### WARNING: Using 'name' in ShieldSymbolizer is deprecated (http://trac.mapnik.org/wiki/TextSymbolizer)\n"; - name = *old_name; - } else { - name = get_value(sym, "ShieldSymbolizer"); - if (name.empty() && (!no_text || !*no_text) ) throw config_error(std::string("ShieldSymbolizer needs a non-empty text")); + shield_symbolizer shield_symbol = shield_symbolizer(); + optional transform_wkt = get_opt_attr(sym, "transform"); + if (transform_wkt) + { + agg::trans_affine tr; + if (!mapnik::svg::parse_transform((*transform_wkt).c_str(),tr)) + { + std::stringstream ss; + ss << "Could not parse transform from '" << transform_wkt << "', expected string like: 'matrix(1, 0, 0, 1, 0, 0)'"; + if (strict_) + throw config_error(ss.str()); // value_error here? + else + std::clog << "### WARNING: " << ss << endl; + } + boost::array matrix; + tr.store_to(&matrix[0]); + shield_symbol.set_transform(matrix); + } + // shield displacement + double shield_dx = get_attr(sym, "shield-dx", 0.0); + double shield_dy = get_attr(sym, "shield-dy", 0.0); + shield_symbol.set_shield_displacement(shield_dx,shield_dy); + + // opacity + optional opacity = get_opt_attr(sym, "opacity"); + if (opacity) + { + shield_symbol.set_opacity(*opacity); } - optional face_name = - get_opt_attr(sym, "face-name"); + // text-opacity + // TODO: Could be problematic because it is named opacity in TextSymbolizer but opacity has a diffrent meaning here. + optional text_opacity = + get_opt_attr(sym, "text-opacity"); + if (text_opacity) + { + shield_symbol.set_text_opacity( * text_opacity ); + } - optional fontset_name = - get_opt_attr(sym, "fontset-name"); + // unlock_image + optional unlock_image = + get_opt_attr(sym, "unlock-image"); + if (unlock_image) + { + shield_symbol.set_unlock_image( * unlock_image ); + } - float size = get_attr(sym, "size", 10.0f); - color fill = get_attr(sym, "fill", color(0,0,0)); + // no text + optional no_text = + get_opt_attr(sym, "no-text"); + if (no_text) + { + shield_symbol.set_no_text( * no_text ); + } + + parse_metawriter_in_symbolizer(shield_symbol, sym); std::string image_file = get_attr(sym, "file"); optional base = get_opt_attr(sym, "base"); - - optional transform_wkt = get_opt_attr(sym, "transform"); + try { if( base ) @@ -1370,202 +1403,7 @@ void map_parser::parse_shield_symbolizer( rule & rule, ptree const & sym ) } image_file = ensure_relative_to_xml(image_file); - - shield_symbolizer shield_symbol(parse_expression(name, "utf8"),size,fill,parse_path(image_file)); - - if (fontset_name && face_name) - { - throw config_error(std::string("Can't have both face-name and fontset-name")); - } - else if (fontset_name) - { - std::map::const_iterator itr = fontsets_.find(*fontset_name); - if (itr != fontsets_.end()) - { - shield_symbol.set_fontset(itr->second); - } - else - { - throw config_error("Unable to find any fontset named '" + *fontset_name + "'"); - } - } - else if (face_name) - { - if ( strict_ ) - { - ensure_font_face(*face_name); - } - shield_symbol.set_face_name(*face_name); - } - else - { - throw config_error(std::string("Must have face-name or fontset-name")); - } - // text displacement (relative to shield_displacement) - double dx = get_attr(sym, "dx", 0.0); - double dy = get_attr(sym, "dy", 0.0); - shield_symbol.set_displacement(dx,dy); - // shield displacement - double shield_dx = get_attr(sym, "shield-dx", 0.0); - double shield_dy = get_attr(sym, "shield-dy", 0.0); - shield_symbol.set_shield_displacement(shield_dx,shield_dy); - - label_placement_e placement = - get_attr(sym, "placement", POINT_PLACEMENT); - shield_symbol.set_label_placement( placement ); - - // don't render shields around edges - optional avoid_edges = - get_opt_attr(sym, "avoid-edges"); - if (avoid_edges) - { - shield_symbol.set_avoid_edges( *avoid_edges); - } - - // halo fill and radius - optional halo_fill = get_opt_attr(sym, "halo-fill"); - if (halo_fill) - { - shield_symbol.set_halo_fill( * halo_fill ); - } - optional halo_radius = - get_opt_attr(sym, "halo-radius"); - if (halo_radius) - { - shield_symbol.set_halo_radius(*halo_radius); - } - - // minimum distance between labels - optional min_distance = get_opt_attr(sym, "minimum-distance"); - if (min_distance) - { - shield_symbol.set_minimum_distance(*min_distance); - } - - // minimum distance from edge of the map - optional min_padding = get_opt_attr(sym, "minimum-padding"); - if (min_padding) - { - shield_symbol.set_minimum_padding(*min_padding); - } - - // spacing between repeated labels on lines - optional spacing = get_opt_attr(sym, "spacing"); - if (spacing) - { - shield_symbol.set_label_spacing(*spacing); - } - - // allow_overlap - optional allow_overlap = - get_opt_attr(sym, "allow-overlap"); - if (allow_overlap) - { - shield_symbol.set_allow_overlap( * allow_overlap ); - } - - // vertical alignment - vertical_alignment_e valign = get_attr(sym, "vertical-alignment", V_MIDDLE); - shield_symbol.set_vertical_alignment(valign); - - // horizontal alignment - horizontal_alignment_e halign = get_attr(sym, "horizontal-alignment", H_MIDDLE); - shield_symbol.set_horizontal_alignment(halign); - - // justify alignment - justify_alignment_e jalign = get_attr(sym, "justify-alignment", J_MIDDLE); - shield_symbol.set_justify_alignment(jalign); - - optional wrap_width = - get_opt_attr(sym, "wrap-width"); - if (wrap_width) - { - shield_symbol.set_wrap_width(*wrap_width); - } - - optional wrap_before = - get_opt_attr(sym, "wrap-before"); - if (wrap_before) - { - shield_symbol.set_wrap_before(*wrap_before); - } - - // character used to break long strings - optional wrap_char = - get_opt_attr(sym, "wrap-character"); - if (wrap_char && (*wrap_char).size() > 0) - { - shield_symbol.set_wrap_char((*wrap_char)[0]); - } - - // text conversion before rendering - text_transform_e tconvert = - get_attr(sym, "text-transform", NONE); - shield_symbol.set_text_transform(tconvert); - - // spacing between text lines - optional line_spacing = get_opt_attr(sym, "line-spacing"); - if (line_spacing) - { - shield_symbol.set_line_spacing(*line_spacing); - } - - // spacing between characters in text - optional character_spacing = get_opt_attr(sym, "character-spacing"); - if (character_spacing) - { - shield_symbol.set_character_spacing(*character_spacing); - } - - // opacity - optional opacity = - get_opt_attr(sym, "opacity"); - if (opacity) - { - shield_symbol.set_opacity( * opacity ); - } - - // text-opacity - optional text_opacity = - get_opt_attr(sym, "text-opacity"); - if (text_opacity) - { - shield_symbol.set_text_opacity( * text_opacity ); - } - - if (transform_wkt) - { - agg::trans_affine tr; - if (!mapnik::svg::parse_transform((*transform_wkt).c_str(),tr)) - { - std::stringstream ss; - ss << "Could not parse transform from '" << transform_wkt << "', expected string like: 'matrix(1, 0, 0, 1, 0, 0)'"; - if (strict_) - throw config_error(ss.str()); // value_error here? - else - std::clog << "### WARNING: " << ss << endl; - } - boost::array matrix; - tr.store_to(&matrix[0]); - shield_symbol.set_transform(matrix); - } - - // unlock_image - optional unlock_image = - get_opt_attr(sym, "unlock-image"); - if (unlock_image) - { - shield_symbol.set_unlock_image( * unlock_image ); - } - - // no text - if (no_text) - { - shield_symbol.set_no_text( * no_text ); - } - - parse_metawriter_in_symbolizer(shield_symbol, sym); - rule.append(shield_symbol); + shield_symbol.set_filename(parse_path(image_file)); } catch (image_reader_exception const & ex ) { @@ -1580,7 +1418,9 @@ void map_parser::parse_shield_symbolizer( rule & rule, ptree const & sym ) std::clog << "### WARNING: " << msg << endl; } } - + text_placements_ptr placement_finder = shield_symbol.get_placement_options(); + placement_finder->properties.set_values_from_xml(sym, fontsets_); + rule.append(shield_symbol); } catch (const config_error & ex) { From af42e2decffdaffc651ced61cc4b972b37895a8a Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 16:06:28 +0100 Subject: [PATCH 34/81] Update save_map.cpp. --- src/save_map.cpp | 150 ++++++++--------------------------------------- 1 file changed, 23 insertions(+), 127 deletions(-) diff --git a/src/save_map.cpp b/src/save_map.cpp index aa7eb4d9e..87bc14c4f 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include // boost #include @@ -357,134 +359,28 @@ private: } void add_font_attributes(ptree & node, const text_symbolizer & sym) { - expression_ptr const& expr = sym.get_name(); - const std::string & name = to_expression_string(*expr); - - if (!name.empty()) { - ptree& text_node = node.push_back(ptree::value_type("", ptree()))->second; - text_node.put_value(name); + text_placements_ptr p = sym.get_placement_options(); + p->properties.to_xml(node, explicit_defaults_); + /* Known types: + - text_placements_dummy: no handling required + - text_placements_simple: positions string + - text_placements_list: list string + */ + text_placements_simple *simple = dynamic_cast(p.get()); + text_placements_list *list = dynamic_cast(p.get()); + if (simple) { + set_attr(node, "placment-type", "simple"); + set_attr(node, "placements", simple->get_positions()); } - const std::string & face_name = sym.get_face_name(); - if ( ! face_name.empty() ) { - set_attr( node, "face-name", face_name ); - } - const std::string & fontset_name = sym.get_fontset().get_name(); - if ( ! fontset_name.empty() ) { - set_attr( node, "fontset-name", fontset_name ); - } - - set_attr( node, "size", sym.get_text_size() ); - set_attr( node, "fill", sym.get_fill() ); - - // pseudo-default-construct a text_symbolizer. It is used - // to avoid printing ofattributes with default values without - // repeating the default values here. - // maybe add a real, explicit default-ctor? - // FIXME - text_symbolizer dfl(expression_ptr(), "", - 0, color(0,0,0) ); - - position displacement = sym.get_displacement(); - if ( displacement.get<0>() != dfl.get_displacement().get<0>() || explicit_defaults_ ) - { - set_attr( node, "dx", displacement.get<0>() ); - } - if ( displacement.get<1>() != dfl.get_displacement().get<1>() || explicit_defaults_ ) - { - set_attr( node, "dy", displacement.get<1>() ); - } - - if (sym.get_label_placement() != dfl.get_label_placement() || explicit_defaults_ ) - { - set_attr( node, "placement", sym.get_label_placement() ); - } - - if (sym.get_vertical_alignment() != dfl.get_vertical_alignment() || explicit_defaults_ ) - { - set_attr( node, "vertical-alignment", sym.get_vertical_alignment() ); - } - - if (sym.get_halo_radius() != dfl.get_halo_radius() || explicit_defaults_ ) - { - set_attr( node, "halo-radius", sym.get_halo_radius() ); - } - const color & c = sym.get_halo_fill(); - if ( c != dfl.get_halo_fill() || explicit_defaults_ ) - { - set_attr( node, "halo-fill", c ); - } - if (sym.get_text_ratio() != dfl.get_text_ratio() || explicit_defaults_ ) - { - set_attr( node, "text-ratio", sym.get_text_ratio() ); - } - if (sym.get_wrap_width() != dfl.get_wrap_width() || explicit_defaults_ ) - { - set_attr( node, "wrap-width", sym.get_wrap_width() ); - } - if (sym.get_wrap_before() != dfl.get_wrap_before() || explicit_defaults_ ) - { - set_attr( node, "wrap-before", sym.get_wrap_before() ); - } - if (sym.get_wrap_char() != dfl.get_wrap_char() || explicit_defaults_ ) - { - set_attr( node, "wrap-character", std::string(1, sym.get_wrap_char()) ); - } - if (sym.get_text_transform() != dfl.get_text_transform() || explicit_defaults_ ) - { - set_attr( node, "text-transform", sym.get_text_transform() ); - } - if (sym.get_line_spacing() != dfl.get_line_spacing() || explicit_defaults_ ) - { - set_attr( node, "line-spacing", sym.get_line_spacing() ); - } - if (sym.get_character_spacing() != dfl.get_character_spacing() || explicit_defaults_ ) - { - set_attr( node, "character-spacing", sym.get_character_spacing() ); - } - if (sym.get_label_position_tolerance() != dfl.get_label_position_tolerance() || explicit_defaults_ ) - { - set_attr( node, "label-position-tolerance", sym.get_label_position_tolerance() ); - } - if (sym.get_label_spacing() != dfl.get_label_spacing() || explicit_defaults_ ) - { - set_attr( node, "spacing", sym.get_label_spacing() ); - } - if (sym.get_minimum_distance() != dfl.get_minimum_distance() || explicit_defaults_ ) - { - set_attr( node, "minimum-distance", sym.get_minimum_distance() ); - } - if (sym.get_minimum_padding() != dfl.get_minimum_padding() || explicit_defaults_ ) - { - set_attr( node, "minimum-padding", sym.get_minimum_padding() ); - } - if (sym.get_minimum_path_length() != dfl.get_minimum_path_length() || explicit_defaults_ ) - { - set_attr( node, "minimum-path-length", sym.get_minimum_path_length() ); - } - if (sym.get_allow_overlap() != dfl.get_allow_overlap() || explicit_defaults_ ) - { - set_attr( node, "allow-overlap", sym.get_allow_overlap() ); - } - if (sym.get_avoid_edges() != dfl.get_avoid_edges() || explicit_defaults_ ) - { - set_attr( node, "avoid-edges", sym.get_avoid_edges() ); - } - // for shield_symbolizer this is later overridden - if (sym.get_text_opacity() != dfl.get_text_opacity() || explicit_defaults_ ) - { - set_attr( node, "opacity", sym.get_text_opacity() ); - } - if (sym.get_max_char_angle_delta() != dfl.get_max_char_angle_delta() || explicit_defaults_ ) - { - set_attr( node, "max-char-angle-delta", sym.get_max_char_angle_delta() ); - } - if (sym.get_horizontal_alignment() != dfl.get_horizontal_alignment() || explicit_defaults_ ) - { - set_attr( node, "horizontal-alignment", sym.get_horizontal_alignment() ); - } - if (sym.get_justify_alignment() != dfl.get_justify_alignment() || explicit_defaults_ ) - { - set_attr( node, "justify-alignment", sym.get_justify_alignment() ); + if (list) { + set_attr(node, "placment-type", "list"); + unsigned i; + text_symbolizer_properties *dfl = &(list->properties); + for (i=0; i < list->size(); i++) { + ptree &placement_node = node.push_back(ptree::value_type("Placement", ptree()))->second; + list->get(i).to_xml(placement_node, explicit_defaults_, *dfl); + dfl = &(list->get(i)); + } } } From 4490e1afaf6285411dc3995edf6ed7502a45bfbe Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 16:25:19 +0100 Subject: [PATCH 35/81] Add missing get_positions() function. --- include/mapnik/text_placements_simple.hpp | 1 + src/text_placements.cpp | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/include/mapnik/text_placements_simple.hpp b/include/mapnik/text_placements_simple.hpp index fe0e541ca..7bcbbf8fd 100644 --- a/include/mapnik/text_placements_simple.hpp +++ b/include/mapnik/text_placements_simple.hpp @@ -51,6 +51,7 @@ public: text_placements_simple(std::string positions); text_placement_info_ptr get_placement_info() const; void set_positions(std::string positions); + std::string get_positions(); private: std::string positions_; std::vector direction_; diff --git a/src/text_placements.cpp b/src/text_placements.cpp index 5eeb769c4..40617eecc 100644 --- a/src/text_placements.cpp +++ b/src/text_placements.cpp @@ -498,6 +498,11 @@ text_placements_simple::text_placements_simple(std::string positions) set_positions(positions); } +std::string text_placements_simple::get_positions() +{ + return positions_; //TODO: Build string from data in direction_ and text_sizes_ +} + /***************************************************************************/ bool text_placement_info_list::next() From 2143267db3bb60903f2eba265a06b3ce36db1a2a Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 16:57:41 +0100 Subject: [PATCH 36/81] Remove boost::make_shared(). --- include/mapnik/text_symbolizer.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/mapnik/text_symbolizer.hpp b/include/mapnik/text_symbolizer.hpp index 88ff573d2..d55cd3974 100644 --- a/include/mapnik/text_symbolizer.hpp +++ b/include/mapnik/text_symbolizer.hpp @@ -52,9 +52,7 @@ struct MAPNIK_DECL text_symbolizer : public symbolizer_base { // Note - we do not use boost::make_shared below as VC2008 and VC2010 are // not able to compile make_shared used within a constructor - text_symbolizer(text_placements_ptr placements = text_placements_ptr( - boost::make_shared()) - ); + 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, text_placements_ptr placements = text_placements_ptr(new text_placements_dummy) From 2f02e2f4c5a22006010f064303b0eca0aa5917e1 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 17:27:52 +0100 Subject: [PATCH 37/81] Update text symbolizer to use new locations. --- src/text_symbolizer.cpp | 245 +++++++++++++--------------------------- 1 file changed, 80 insertions(+), 165 deletions(-) diff --git a/src/text_symbolizer.cpp b/src/text_symbolizer.cpp index e10ae4e44..17b7407d5 100644 --- a/src/text_symbolizer.cpp +++ b/src/text_symbolizer.cpp @@ -26,6 +26,7 @@ #include // boost #include +#include namespace mapnik { @@ -85,349 +86,263 @@ static const char * text_transform_strings[] = { IMPLEMENT_ENUM( text_transform_e, text_transform_strings ) +text_symbolizer::text_symbolizer(text_placements_ptr placements) + : symbolizer_base(), + placement_options_(placements) +{ + +} text_symbolizer::text_symbolizer(expression_ptr name, std::string const& face_name, float size, color const& fill, text_placements_ptr placements) : symbolizer_base(), - name_(name), - face_name_(face_name), - //fontset_(default_fontset), - text_ratio_(0), - wrap_width_(0), - wrap_char_(' '), - text_transform_(NONE), - line_spacing_(0), - character_spacing_(0), - label_spacing_(0), - label_position_tolerance_(0), - force_odd_labels_(false), - max_char_angle_delta_(22.5 * M_PI/180.0), - fill_(fill), - halo_fill_(color(255,255,255)), - halo_radius_(0.0), - label_p_(POINT_PLACEMENT), - avoid_edges_(false), - minimum_distance_(0.0), - minimum_padding_(0.0), - minimum_path_length_(0.0), - overlap_(false), - text_opacity_(1.0), - wrap_before_(false), placement_options_(placements) { + set_name(name); + set_face_name(face_name); set_text_size(size); + set_fill(fill); } text_symbolizer::text_symbolizer(expression_ptr name, float size, color const& fill, text_placements_ptr placements) : symbolizer_base(), - name_(name), - //face_name_(""), - //fontset_(default_fontset), - text_ratio_(0), - wrap_width_(0), - wrap_char_(' '), - text_transform_(NONE), - line_spacing_(0), - character_spacing_(0), - label_spacing_(0), - label_position_tolerance_(0), - force_odd_labels_(false), - max_char_angle_delta_(22.5 * M_PI/180.0), - fill_(fill), - halo_fill_(color(255,255,255)), - halo_radius_(0.0), - label_p_(POINT_PLACEMENT), - avoid_edges_(false), - minimum_distance_(0.0), - minimum_padding_(0.0), - minimum_path_length_(0.0), - overlap_(false), - text_opacity_(1.0), - wrap_before_(false), placement_options_(placements) { + set_name(name); set_text_size(size); + set_fill(fill); } text_symbolizer::text_symbolizer(text_symbolizer const& rhs) : symbolizer_base(rhs), - name_(rhs.name_), - orientation_(rhs.orientation_), - face_name_(rhs.face_name_), - fontset_(rhs.fontset_), - text_ratio_(rhs.text_ratio_), - wrap_width_(rhs.wrap_width_), - wrap_char_(rhs.wrap_char_), - text_transform_(rhs.text_transform_), - line_spacing_(rhs.line_spacing_), - character_spacing_(rhs.character_spacing_), - label_spacing_(rhs.label_spacing_), - label_position_tolerance_(rhs.label_position_tolerance_), - force_odd_labels_(rhs.force_odd_labels_), - max_char_angle_delta_(rhs.max_char_angle_delta_), - fill_(rhs.fill_), - halo_fill_(rhs.halo_fill_), - halo_radius_(rhs.halo_radius_), - label_p_(rhs.label_p_), - - avoid_edges_(rhs.avoid_edges_), - minimum_distance_(rhs.minimum_distance_), - minimum_padding_(rhs.minimum_padding_), - minimum_path_length_(rhs.minimum_path_length_), - overlap_(rhs.overlap_), - text_opacity_(rhs.text_opacity_), - wrap_before_(rhs.wrap_before_), - placement_options_(rhs.placement_options_) /*TODO: Copy options! */ {} + placement_options_(rhs.placement_options_) /*TODO: Copy options! */ +{ +} text_symbolizer& text_symbolizer::operator=(text_symbolizer const& other) { if (this == &other) return *this; - name_ = other.name_; - orientation_ = other.orientation_; - face_name_ = other.face_name_; - fontset_ = other.fontset_; - text_ratio_ = other.text_ratio_; - wrap_width_ = other.wrap_width_; - wrap_char_ = other.wrap_char_; - text_transform_ = other.text_transform_; - line_spacing_ = other.line_spacing_; - character_spacing_ = other.character_spacing_; - label_spacing_ = other.label_spacing_; - label_position_tolerance_ = other.label_position_tolerance_; - force_odd_labels_ = other.force_odd_labels_; - max_char_angle_delta_ = other.max_char_angle_delta_; - fill_ = other.fill_; - halo_fill_ = other.halo_fill_; - halo_radius_ = other.halo_radius_; - label_p_ = other.label_p_; - avoid_edges_ = other.avoid_edges_; - minimum_distance_ = other.minimum_distance_; - minimum_padding_ = other.minimum_padding_; - minimum_path_length_ = other.minimum_path_length_; - overlap_ = other.overlap_; - text_opacity_ = other.text_opacity_; - wrap_before_ = other.wrap_before_; - placement_options_ = other.placement_options_; /*TODO?*/ - std::cout << "TODO: Metawriter (text_symbolizer::operator=)\n"; + placement_options_ = other.placement_options_; /*TODO: Copy options? */ + std::clog << "TODO: Metawriter (text_symbolizer::operator=)\n"; return *this; } expression_ptr text_symbolizer::get_name() const { - return name_; + return expression_ptr(); } void text_symbolizer::set_name(expression_ptr name) { - name_ = name; + placement_options_->properties.processor.set_old_style_expression(name); } expression_ptr text_symbolizer::get_orientation() const { - return orientation_; + return placement_options_->properties.orientation; } void text_symbolizer::set_orientation(expression_ptr orientation) { - orientation_ = orientation; + placement_options_->properties.orientation = orientation; } std::string const& text_symbolizer::get_face_name() const { - return face_name_; + return placement_options_->properties.processor.defaults.face_name; } void text_symbolizer::set_face_name(std::string face_name) { - face_name_ = face_name; + placement_options_->properties.processor.defaults.face_name = face_name; } void text_symbolizer::set_fontset(font_set const& fontset) { - fontset_ = fontset; + placement_options_->properties.processor.defaults.fontset = fontset; } font_set const& text_symbolizer::get_fontset() const { - return fontset_; + return placement_options_->properties.processor.defaults.fontset; } unsigned text_symbolizer::get_text_ratio() const { - return text_ratio_; + return placement_options_->properties.text_ratio; } void text_symbolizer::set_text_ratio(unsigned ratio) { - text_ratio_ = ratio; + placement_options_->properties.text_ratio = ratio; } unsigned text_symbolizer::get_wrap_width() const { - return wrap_width_; + return placement_options_->properties.wrap_width; } void text_symbolizer::set_wrap_width(unsigned width) { - wrap_width_ = width; + placement_options_->properties.wrap_width = width; } bool text_symbolizer::get_wrap_before() const { - return wrap_before_; + return placement_options_->properties.processor.defaults.wrap_before; } void text_symbolizer::set_wrap_before(bool wrap_before) { - wrap_before_ = wrap_before; + placement_options_->properties.processor.defaults.wrap_before = wrap_before; } unsigned char text_symbolizer::get_wrap_char() const { - return wrap_char_; + return placement_options_->properties.processor.defaults.wrap_char; } std::string text_symbolizer::get_wrap_char_string() const { - return std::string(1, wrap_char_); + return std::string(1, placement_options_->properties.processor.defaults.wrap_char); } void text_symbolizer::set_wrap_char(unsigned char character) { - wrap_char_ = character; + placement_options_->properties.processor.defaults.wrap_char = character; } void text_symbolizer::set_wrap_char_from_string(std::string const& character) { - wrap_char_ = (character)[0]; + placement_options_->properties.processor.defaults.wrap_char = (character)[0]; } text_transform_e text_symbolizer::get_text_transform() const { - return text_transform_; + return placement_options_->properties.processor.defaults.text_transform; } void text_symbolizer::set_text_transform(text_transform_e convert) { - text_transform_ = convert; + placement_options_->properties.processor.defaults.text_transform = convert; } unsigned text_symbolizer::get_line_spacing() const { - return line_spacing_; + return placement_options_->properties.processor.defaults.line_spacing; } void text_symbolizer::set_line_spacing(unsigned spacing) { - line_spacing_ = spacing; + placement_options_->properties.processor.defaults.line_spacing = spacing; } unsigned text_symbolizer::get_character_spacing() const { - return character_spacing_; + return placement_options_->properties.processor.defaults.character_spacing; } void text_symbolizer::set_character_spacing(unsigned spacing) { - character_spacing_ = spacing; + placement_options_->properties.processor.defaults.character_spacing = spacing; } unsigned text_symbolizer::get_label_spacing() const { - return label_spacing_; + return placement_options_->properties.label_spacing; } void text_symbolizer::set_label_spacing(unsigned spacing) { - label_spacing_ = spacing; + placement_options_->properties.label_spacing = spacing; } unsigned text_symbolizer::get_label_position_tolerance() const { - return label_position_tolerance_; + return placement_options_->properties.label_position_tolerance; } void text_symbolizer::set_label_position_tolerance(unsigned tolerance) { - label_position_tolerance_ = tolerance; + placement_options_->properties.label_position_tolerance = tolerance; } bool text_symbolizer::get_force_odd_labels() const { - return force_odd_labels_; + return placement_options_->properties.force_odd_labels; } void text_symbolizer::set_force_odd_labels(bool force) { - force_odd_labels_ = force; + placement_options_->properties.force_odd_labels = force; } double text_symbolizer::get_max_char_angle_delta() const { - return max_char_angle_delta_; + return placement_options_->properties.max_char_angle_delta; } void text_symbolizer::set_max_char_angle_delta(double angle) { - max_char_angle_delta_ = angle; + placement_options_->properties.max_char_angle_delta = angle; } void text_symbolizer::set_text_size(float size) { - placement_options_->set_default_text_size(size); + placement_options_->properties.processor.defaults.text_size = size; } float text_symbolizer::get_text_size() const { - return placement_options_->get_default_text_size(); + return placement_options_->properties.processor.defaults.text_size; } void text_symbolizer::set_fill(color const& fill) { - fill_ = fill; + placement_options_->properties.processor.defaults.fill = fill; } color const& text_symbolizer::get_fill() const { - return fill_; + return placement_options_->properties.processor.defaults.fill; } void text_symbolizer::set_halo_fill(color const& fill) { - halo_fill_ = fill; + placement_options_->properties.processor.defaults.halo_fill = fill; } color const& text_symbolizer::get_halo_fill() const { - return halo_fill_; + return placement_options_->properties.processor.defaults.halo_fill; } void text_symbolizer::set_halo_radius(double radius) { - halo_radius_ = radius; + placement_options_->properties.processor.defaults.halo_radius = radius; } double text_symbolizer::get_halo_radius() const { - return halo_radius_; + return placement_options_->properties.processor.defaults.halo_radius; } void text_symbolizer::set_label_placement(label_placement_e label_p) { - label_p_ = label_p; + placement_options_->properties.label_placement = label_p; } label_placement_e text_symbolizer::get_label_placement() const { - return label_p_; + return placement_options_->properties.label_placement; } -void text_symbolizer::set_displacement(double x, double y) +void text_symbolizer::set_displacement(double x, double y) { - placement_options_->set_default_displacement(boost::make_tuple(x,y)); + placement_options_->properties.displacement = boost::make_tuple(x,y); } void text_symbolizer::set_displacement(position const& p) @@ -437,97 +352,97 @@ void text_symbolizer::set_displacement(position const& p) position const& text_symbolizer::get_displacement() const { - return placement_options_->get_default_displacement(); + return placement_options_->properties.displacement; } bool text_symbolizer::get_avoid_edges() const { - return avoid_edges_; + return placement_options_->properties.avoid_edges; } void text_symbolizer::set_avoid_edges(bool avoid) { - avoid_edges_ = avoid; + placement_options_->properties.avoid_edges = avoid; } double text_symbolizer::get_minimum_distance() const { - return minimum_distance_; + return placement_options_->properties.minimum_distance; } void text_symbolizer::set_minimum_distance(double distance) { - minimum_distance_ = distance; + placement_options_->properties.minimum_distance = distance; } double text_symbolizer::get_minimum_padding() const { - return minimum_padding_; + return placement_options_->properties.minimum_padding; } void text_symbolizer::set_minimum_padding(double distance) { - minimum_padding_ = distance; + placement_options_->properties.minimum_padding = distance; } double text_symbolizer::get_minimum_path_length() const { - return minimum_path_length_; + return placement_options_->properties.minimum_path_length; } void text_symbolizer::set_minimum_path_length(double size) { - minimum_path_length_ = size; + placement_options_->properties.minimum_path_length = size; } void text_symbolizer::set_allow_overlap(bool overlap) { - overlap_ = overlap; + placement_options_->properties.allow_overlap = overlap; } bool text_symbolizer::get_allow_overlap() const { - return overlap_; + return placement_options_->properties.allow_overlap; } void text_symbolizer::set_text_opacity(double text_opacity) { - text_opacity_ = text_opacity; + placement_options_->properties.processor.defaults.text_opacity = text_opacity; } double text_symbolizer::get_text_opacity() const { - return text_opacity_; + return placement_options_->properties.processor.defaults.text_opacity; } void text_symbolizer::set_vertical_alignment(vertical_alignment_e valign) { - placement_options_->set_default_valign(valign); + placement_options_->properties.valign = valign; } vertical_alignment_e text_symbolizer::get_vertical_alignment() const { - return placement_options_->get_default_valign(); + return placement_options_->properties.valign; } void text_symbolizer::set_horizontal_alignment(horizontal_alignment_e halign) { - placement_options_->set_default_halign(halign); + placement_options_->properties.halign = halign; } horizontal_alignment_e text_symbolizer::get_horizontal_alignment() const { - return placement_options_->get_default_halign(); + return placement_options_->properties.halign; } void text_symbolizer::set_justify_alignment(justify_alignment_e jalign) { - placement_options_->set_default_jalign(jalign); + placement_options_->properties.jalign = jalign; } justify_alignment_e text_symbolizer::get_justify_alignment() const { - return placement_options_->get_default_jalign(); + return placement_options_->properties.jalign; } text_placements_ptr text_symbolizer::get_placement_options() const From 67eb73eab9677eca6a9818a290dc58309b187458 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 17:53:45 +0100 Subject: [PATCH 38/81] Return correct size information from font engine. --- include/mapnik/font_engine_freetype.hpp | 2 ++ src/font_engine_freetype.cpp | 19 ++++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/include/mapnik/font_engine_freetype.hpp b/include/mapnik/font_engine_freetype.hpp index 5bcd345c5..69307db81 100644 --- a/include/mapnik/font_engine_freetype.hpp +++ b/include/mapnik/font_engine_freetype.hpp @@ -344,6 +344,7 @@ struct text_renderer : private boost::noncopyable typedef T pixmap_type; text_renderer (pixmap_type & pixmap, face_set_ptr faces, stroker & s); + text_renderer (pixmap_type & pixmap, face_manager &font_manager_, stroker & s); box2d prepare_glyphs(text_path *path); void render(double x0, double y0); void render_id(int feature_id,double x0, double y0, double min_radius=1.0); @@ -421,6 +422,7 @@ private: pixmap_type & pixmap_; face_set_ptr faces_; +// face_manager &font_manager_; stroker & stroker_; color fill_; color halo_fill_; diff --git a/src/font_engine_freetype.cpp b/src/font_engine_freetype.cpp index 159e4a942..bd56292cc 100644 --- a/src/font_engine_freetype.cpp +++ b/src/font_engine_freetype.cpp @@ -241,15 +241,15 @@ char_info font_face_set::character_dimensions(const unsigned c) return dim; } -void font_face_set::get_string_info(string_info & info, UnicodeString const& ustr_unused, char_properties *format_unused) +void font_face_set::get_string_info(string_info & info, UnicodeString const& ustr, char_properties *format) { + double avg_height = character_dimensions('X').height(); unsigned width = 0; unsigned height = 0; UErrorCode err = U_ZERO_ERROR; UnicodeString reordered; UnicodeString shaped; - UnicodeString const& ustr = info.get_string(); int32_t length = ustr.length(); UBiDi *bidi = ubidi_openSized(length, 0, &err); @@ -272,9 +272,11 @@ void font_face_set::get_string_info(string_info & info, UnicodeString const& ust for (iter.setToStart(); iter.hasNext();) { UChar ch = iter.nextPostInc(); char_info char_dim = character_dimensions(ch); - info.add_info(ch, char_dim.width, char_dim.height()); width += char_dim.width; height = (char_dim.height() > height) ? char_dim.height() : height; + char_dim.format = format; + char_dim.avg_height = avg_height; + info.add_info(char_dim); } } @@ -303,6 +305,17 @@ text_renderer::text_renderer (pixmap_type & pixmap, face_set_ptr faces, strok } +#if 0 +template +text_renderer::text_renderer (pixmap_type & pixmap, face_manager &font_manager_, stroker & s) + : pixmap_(pixmap), + font_manager_(font_manager_), + stroker_(s) +{ + +} +#endif + template box2d text_renderer::prepare_glyphs(text_path *path) { From e66712d7225a91fab5643455dff6fc29c2301902 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 18:13:08 +0100 Subject: [PATCH 39/81] Update font engine. --- src/font_engine_freetype.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/font_engine_freetype.cpp b/src/font_engine_freetype.cpp index bd56292cc..192ab866e 100644 --- a/src/font_engine_freetype.cpp +++ b/src/font_engine_freetype.cpp @@ -388,7 +388,7 @@ box2d text_renderer::prepare_glyphs(text_path *path) } // take ownership of the glyph - glyphs_.push_back(new glyph_t(image, 0)); + glyphs_.push_back(new glyph_t(image, properties)); } return box2d(bbox.xMin, bbox.yMin, bbox.xMax, bbox.yMax); @@ -406,13 +406,12 @@ void text_renderer::render(double x0, double y0) // now render transformed glyphs typename glyphs_t::iterator pos; - - //make sure we've got reasonable values. - if (halo_radius_ > 0.0 && halo_radius_ < 1024.0) - { - stroker_.init(halo_radius_); for ( pos = glyphs_.begin(); pos != glyphs_.end();++pos) { + double halo_radius = pos->properties->halo_radius; + //make sure we've got reasonable values. + if (halo_radius <= 0.0 || halo_radius > 1024.0) continue; + stroker_.init(halo_radius); FT_Glyph g; error = FT_Glyph_Copy(pos->image, &g); if (!error) @@ -424,14 +423,13 @@ void text_renderer::render(double x0, double y0) { FT_BitmapGlyph bit = (FT_BitmapGlyph)g; - render_bitmap(&bit->bitmap, halo_fill_.rgba(), + render_bitmap(&bit->bitmap, pos->properties->halo_fill.rgba(), bit->left, - height - bit->top, opacity_); + height - bit->top, pos->properties->text_opacity); } } FT_Done_Glyph(g); } - } //render actual text for ( pos = glyphs_.begin(); pos != glyphs_.end();++pos) { @@ -443,9 +441,9 @@ void text_renderer::render(double x0, double y0) { FT_BitmapGlyph bit = (FT_BitmapGlyph)pos->image; - render_bitmap(&bit->bitmap, fill_.rgba(), + render_bitmap(&bit->bitmap, pos->properties->fill.rgba(), bit->left, - height - bit->top, opacity_); + height - bit->top, pos->properties->text_opacity); } } } @@ -463,10 +461,9 @@ void text_renderer::render_id(int feature_id,double x0, double y0, double min // now render transformed glyphs typename glyphs_t::iterator pos; - - stroker_.init(std::max(halo_radius_,min_radius)); for ( pos = glyphs_.begin(); pos != glyphs_.end();++pos) { + stroker_.init(std::max(pos->properties->halo_radius, min_radius)); FT_Glyph g; error = FT_Glyph_Copy(pos->image, &g); if (!error) From e0b5e15507c7818781a817e44a6d68cb75d08bce Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 18:20:34 +0100 Subject: [PATCH 40/81] Remove old functions from text_placements. --- include/mapnik/text_placements.hpp | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/include/mapnik/text_placements.hpp b/include/mapnik/text_placements.hpp index 0c462046a..2af7f9ac6 100644 --- a/include/mapnik/text_placements.hpp +++ b/include/mapnik/text_placements.hpp @@ -43,6 +43,8 @@ namespace mapnik { +class text_placements; + typedef text_path placement_element; typedef boost::tuple position; @@ -116,7 +118,6 @@ struct text_symbolizer_properties text_processor processor; //Contains expressions and text formats }; -class text_placements; class text_placement_info : boost::noncopyable { @@ -165,21 +166,6 @@ public: text_placements(); virtual text_placement_info_ptr get_placement_info() const =0; - virtual void set_default_text_size(float size) { text_size_ = size; } - float get_default_text_size() const { return text_size_; } - - virtual void set_default_displacement(position const& displacement) { displacement_ = displacement;} - position const& get_default_displacement() { return displacement_; } - - virtual void set_default_halign(horizontal_alignment_e const& align) { halign_ = align;} - horizontal_alignment_e const& get_default_halign() { return halign_; } - - virtual void set_default_jalign(justify_alignment_e const& align) { jalign_ = align;} - justify_alignment_e const& get_default_jalign() { return jalign_; } - - virtual void set_default_valign(vertical_alignment_e const& align) { valign_ = align;} - vertical_alignment_e const& get_default_valign() { return valign_; } - /** Get a list of all expressions used in any placement. * This function is used to collect attributes. */ @@ -187,13 +173,6 @@ public: virtual ~text_placements() {} text_symbolizer_properties properties; -protected: - float text_size_; - position displacement_; - horizontal_alignment_e halign_; - justify_alignment_e jalign_; - vertical_alignment_e valign_; - friend class text_placement_info; }; typedef boost::shared_ptr text_placements_ptr; From fdd58903f9456bfc7b0108c7295bd792adbbae30 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 18:24:28 +0100 Subject: [PATCH 41/81] Disable rendering of TextSymbolizer and ShieldSymbolizer. This avoids breaking compiles with the next changes. --- src/agg/process_shield_symbolizer.cpp | 2 ++ src/agg/process_text_symbolizer.cpp | 4 ++-- src/cairo_renderer.cpp | 2 ++ src/grid/process_shield_symbolizer.cpp | 3 ++- src/grid/process_text_symbolizer.cpp | 2 ++ 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/agg/process_shield_symbolizer.cpp b/src/agg/process_shield_symbolizer.cpp index 55265ff2b..fc74ea2ad 100644 --- a/src/agg/process_shield_symbolizer.cpp +++ b/src/agg/process_shield_symbolizer.cpp @@ -45,6 +45,7 @@ void agg_renderer::process(shield_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { +#if 0 typedef coord_transform2 path_type; @@ -264,6 +265,7 @@ void agg_renderer::process(shield_symbolizer const& sym, } } } +#endif } diff --git a/src/agg/process_text_symbolizer.cpp b/src/agg/process_text_symbolizer.cpp index 677c40af6..a807a115d 100644 --- a/src/agg/process_text_symbolizer.cpp +++ b/src/agg/process_text_symbolizer.cpp @@ -33,8 +33,7 @@ void agg_renderer::process(text_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { - - +#if 0 // Use a boost::ptr_vector here instread of std::vector? std::vector geometries_to_process; unsigned num_geom = feature.num_geometries(); @@ -176,6 +175,7 @@ void agg_renderer::process(text_symbolizer const& sym, } } } +#endif } template void agg_renderer::process(text_symbolizer const&, diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index cc1b68e41..862ea4034 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -1465,6 +1465,7 @@ void cairo_renderer_base::process(text_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { +#if 0 typedef coord_transform2 path_type; bool placement_found = false; @@ -1579,6 +1580,7 @@ void cairo_renderer_base::process(text_symbolizer const& sym, } } } +#endif } template class cairo_renderer; diff --git a/src/grid/process_shield_symbolizer.cpp b/src/grid/process_shield_symbolizer.cpp index fdc2f7b15..c286f7409 100644 --- a/src/grid/process_shield_symbolizer.cpp +++ b/src/grid/process_shield_symbolizer.cpp @@ -44,6 +44,7 @@ void grid_renderer::process(shield_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { +#if 0 typedef coord_transform2 path_type; bool placement_found = false; @@ -237,7 +238,7 @@ void grid_renderer::process(shield_symbolizer const& sym, } if (placement_found) pixmap_.add_feature(feature); - +#endif } 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 1cf6abd53..5541526de 100644 --- a/src/grid/process_text_symbolizer.cpp +++ b/src/grid/process_text_symbolizer.cpp @@ -33,6 +33,7 @@ void grid_renderer::process(text_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { +#if 0 typedef coord_transform2 path_type; bool placement_found = false; @@ -147,6 +148,7 @@ void grid_renderer::process(text_symbolizer const& sym, } if (placement_found) pixmap_.add_feature(feature); +#endif } template void grid_renderer::process(text_symbolizer const&, From 78c4464ad77c24e381753939ba621a8bcc1a1f1a Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 18:28:30 +0100 Subject: [PATCH 42/81] Remove next_position_only(). --- include/mapnik/text_placements.hpp | 5 +---- include/mapnik/text_placements_simple.hpp | 2 +- src/text_placements.cpp | 7 ------- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/include/mapnik/text_placements.hpp b/include/mapnik/text_placements.hpp index 2af7f9ac6..4c3f8c150 100644 --- a/include/mapnik/text_placements.hpp +++ b/include/mapnik/text_placements.hpp @@ -129,7 +129,6 @@ public: * If this functions returns false the placement data should be considered invalid! */ virtual bool next()=0; - virtual bool next_position_only() { return false; } virtual ~text_placement_info() {} void init(double scale_factor_, unsigned w = 0, unsigned h = 0, bool has_dimensions_ = false); @@ -190,12 +189,10 @@ class MAPNIK_DECL text_placement_info_dummy : public text_placement_info { public: text_placement_info_dummy(text_placements_dummy const* parent) : text_placement_info(parent), - state(0), position_state(0), parent_(parent) {} + state(0), parent_(parent) {} bool next(); - bool next_position_only(); private: unsigned state; - unsigned position_state; text_placements_dummy const* parent_; }; diff --git a/include/mapnik/text_placements_simple.hpp b/include/mapnik/text_placements_simple.hpp index 7bcbbf8fd..2c9efed7c 100644 --- a/include/mapnik/text_placements_simple.hpp +++ b/include/mapnik/text_placements_simple.hpp @@ -67,8 +67,8 @@ public: text_placement_info_simple(text_placements_simple const* parent) : text_placement_info(parent), state(0), position_state(0), parent_(parent) {} bool next(); +protected: bool next_position_only(); -private: unsigned state; unsigned position_state; text_placements_simple const* parent_; diff --git a/src/text_placements.cpp b/src/text_placements.cpp index 40617eecc..3b29e429e 100644 --- a/src/text_placements.cpp +++ b/src/text_placements.cpp @@ -347,13 +347,6 @@ bool text_placement_info_dummy::next() return true; } -bool text_placement_info_dummy::next_position_only() -{ - if (position_state) return false; - position_state++; - return true; -} - text_placement_info_ptr text_placements_dummy::get_placement_info() const { return text_placement_info_ptr(new text_placement_info_dummy(this)); From 3dab67732ef90208fc7ffd800c2672497278573e Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 18:33:53 +0100 Subject: [PATCH 43/81] Remove old variables. --- include/mapnik/text_placements.hpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/include/mapnik/text_placements.hpp b/include/mapnik/text_placements.hpp index 4c3f8c150..13d67b8f9 100644 --- a/include/mapnik/text_placements.hpp +++ b/include/mapnik/text_placements.hpp @@ -40,6 +40,7 @@ #include #include #include +#include namespace mapnik { @@ -118,7 +119,6 @@ struct text_symbolizer_properties text_processor processor; //Contains expressions and text formats }; - class text_placement_info : boost::noncopyable { public: @@ -148,13 +148,6 @@ public: box2d extents; std::queue< box2d > envelopes; boost::ptr_vector placements; - - /* NOTE: Values are public and non-virtual to avoid any performance problems. */ - position displacement; - float text_size; - horizontal_alignment_e halign; - justify_alignment_e jalign; - vertical_alignment_e valign; }; typedef boost::shared_ptr text_placement_info_ptr; @@ -164,7 +157,6 @@ class text_placements public: text_placements(); virtual text_placement_info_ptr get_placement_info() const =0; - /** Get a list of all expressions used in any placement. * This function is used to collect attributes. */ @@ -197,6 +189,7 @@ private: }; + } //namespace #endif // MAPNIK_TEXT_PLACEMENTS_HPP From a95524ae3560655aecef96e8ce234ebfb4602a72 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 18:36:17 +0100 Subject: [PATCH 44/81] Remove old variables. --- include/mapnik/text_symbolizer.hpp | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/include/mapnik/text_symbolizer.hpp b/include/mapnik/text_symbolizer.hpp index d55cd3974..f1c7ce6f0 100644 --- a/include/mapnik/text_symbolizer.hpp +++ b/include/mapnik/text_symbolizer.hpp @@ -131,31 +131,6 @@ struct MAPNIK_DECL text_symbolizer : public symbolizer_base void set_placement_options(text_placements_ptr placement_options); private: - expression_ptr name_; - expression_ptr orientation_; - std::string face_name_; - font_set fontset_; - unsigned text_ratio_; - unsigned wrap_width_; - unsigned char wrap_char_; - text_transform_e text_transform_; - unsigned line_spacing_; - unsigned character_spacing_; - unsigned label_spacing_; - unsigned label_position_tolerance_; - bool force_odd_labels_; - double max_char_angle_delta_; - color fill_; - color halo_fill_; - double halo_radius_; - label_placement_e label_p_; - bool avoid_edges_; - double minimum_distance_; - double minimum_padding_; - double minimum_path_length_; - bool overlap_; - double text_opacity_; - bool wrap_before_; text_placements_ptr placement_options_; }; } From 9d2a6088b17b955e7aca26489984c15b25337901 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 18:41:04 +0100 Subject: [PATCH 45/81] New placement finder. --- include/mapnik/placement_finder.hpp | 95 ++--- src/placement_finder.cpp | 596 ++++++++++++++-------------- 2 files changed, 330 insertions(+), 361 deletions(-) diff --git a/include/mapnik/placement_finder.hpp b/include/mapnik/placement_finder.hpp index bf8869ec4..37a0219f6 100644 --- a/include/mapnik/placement_finder.hpp +++ b/include/mapnik/placement_finder.hpp @@ -23,86 +23,34 @@ #ifndef MAPNIK_PLACEMENT_FINDER_HPP #define MAPNIK_PLACEMENT_FINDER_HPP -// mapnik -#include -#include -#include -#include #include -#include #include -// stl -#include - namespace mapnik { -typedef text_path placement_element; - -struct placement : boost::noncopyable -{ - placement(string_info & info_, shield_symbolizer const& sym, double scale_factor, unsigned w, unsigned h, bool has_dimensions_= false); - - placement(string_info & info_, text_symbolizer const& sym, double scale_factor); - - ~placement(); - - string_info & info; // should only be used for finding placement. doesn't necessarily match placements.vertex() values - - double scale_factor_; - label_placement_e label_placement; - - std::queue< box2d > envelopes; - - //output - boost::ptr_vector placements; - - int wrap_width; - bool wrap_before; // wraps text at wrap_char immediately before current word - unsigned char wrap_char; - int text_ratio; - - int label_spacing; // distance between repeated labels on a single geometry - unsigned label_position_tolerance; //distance the label can be moved on the line to fit, if 0 the default is used - bool force_odd_labels; //Always try render an odd amount of labels - - double max_char_angle_delta; - double minimum_distance; - double minimum_padding; - double minimum_path_length; - bool avoid_edges; - bool has_dimensions; - bool allow_overlap; - std::pair dimensions; - bool collect_extents; - box2d extents; - - // additional boxes attached to the text labels which must also be - // placed in order for the text placement to succeed. e.g: shields. - std::vector > additional_boxes; -}; - - template class placement_finder : boost::noncopyable { public: - placement_finder(DetectorT & detector); - placement_finder(DetectorT & detector, box2d const& extent); + placement_finder(text_placement_info &p, string_info &info, DetectorT & detector); + placement_finder(text_placement_info &p, string_info &info, DetectorT & detector, box2d const& extent); //Try place a single label at the given point - void find_point_placement(placement & p, text_placement_info_ptr po, double pos_x, double pos_y, double angle=0.0, unsigned line_spacing=0, unsigned character_spacing=0); + 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 template - void find_point_placements(placement & p, text_placement_info_ptr po, T & path); + void find_point_placements(T & path); //Iterate over the given path, placing line-following labels with respect to label_spacing template - void find_line_placements(placement & p, text_placement_info_ptr po, T & path); + void find_line_placements(T & path); - void update_detector(placement & p); + //Find placement, automatically select point or line placement + void find_placement(double angle, geometry_type const& geom, CoordTransform const& t, proj_transform const& prj_trans); + + void update_detector(); void clear(); @@ -117,15 +65,14 @@ private: // 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::auto_ptr get_placement_offset(placement & p, - const std::vector & path_positions, + std::auto_ptr get_placement_offset(const std::vector & path_positions, const std::vector & path_distances, int & orientation, unsigned index, double distance); ///Tests wether the given placement_element 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(placement & p, const std::auto_ptr & current_placement, const int & orientation); + bool test_placement(const std::auto_ptr & current_placement, const int & orientation); ///Does a line-circle intersect calculation // NOTE: Follow the strict pre conditions @@ -137,14 +84,26 @@ private: const double &x1, const double &y1, const double &x2, const double &y2, double &ix, double &iy); + void find_line_breaks(); + void init_string_size(); + void init_alignment(); + void adjust_position(placement_element *current_placement, double label_x, double label_y); + ///General Internals - - - DetectorT & detector_; box2d const& dimensions_; + string_info &info_; + text_symbolizer_properties &p; + text_placement_info π + double string_width_; + double string_height_; + double first_line_space_; + vertical_alignment_e valign_; + horizontal_alignment_e halign_; + std::vector line_breaks_; + std::vector > line_sizes_; }; - } + #endif // MAPNIK_PLACEMENT_FINDER_HPP diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index 136d3a5ae..2d2888cf4 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include // agg @@ -49,60 +50,6 @@ namespace mapnik { -placement::placement(string_info & info_, - shield_symbolizer const& sym, - double scale_factor, - unsigned w, unsigned h, - bool has_dimensions_) - : info(info_), - scale_factor_(scale_factor), - label_placement(sym.get_label_placement()), - wrap_width(sym.get_wrap_width()), - wrap_before(sym.get_wrap_before()), - wrap_char(sym.get_wrap_char()), - text_ratio(sym.get_text_ratio()), - label_spacing(scale_factor_ * sym.get_label_spacing()), - label_position_tolerance(sym.get_label_position_tolerance()), - force_odd_labels(sym.get_force_odd_labels()), - max_char_angle_delta(sym.get_max_char_angle_delta()), - minimum_distance(scale_factor_ * sym.get_minimum_distance()), - minimum_padding(scale_factor_ * sym.get_minimum_padding()), - minimum_path_length(0), - avoid_edges(sym.get_avoid_edges()), - has_dimensions(has_dimensions_), - allow_overlap(false), - dimensions(std::make_pair(w,h)), - collect_extents(false), - extents() -{} - -placement::placement(string_info & info_, - text_symbolizer const& sym, - double scale_factor) - : info(info_), - scale_factor_(scale_factor), - label_placement(sym.get_label_placement()), - wrap_width(sym.get_wrap_width()), - wrap_before(sym.get_wrap_before()), - wrap_char(sym.get_wrap_char()), - text_ratio(sym.get_text_ratio()), - label_spacing(scale_factor_ * sym.get_label_spacing()), - label_position_tolerance(sym.get_label_position_tolerance()), - force_odd_labels(sym.get_force_odd_labels()), - max_char_angle_delta(sym.get_max_char_angle_delta()), - minimum_distance(scale_factor_ * sym.get_minimum_distance()), - minimum_padding(scale_factor_ * sym.get_minimum_padding()), - minimum_path_length(scale_factor_ * sym.get_minimum_path_length()), - avoid_edges(sym.get_avoid_edges()), - has_dimensions(false), - allow_overlap(sym.get_allow_overlap()), - dimensions(), - collect_extents(false), - extents() -{} - - -placement::~placement() {} template std::pair get_position_at_distance(double target_distance, T & shape_path) @@ -151,22 +98,24 @@ double get_total_distance(T & shape_path) } template -placement_finder::placement_finder(DetectorT & detector) +placement_finder::placement_finder(text_placement_info &placement_info, string_info &info, DetectorT & detector) : detector_(detector), - dimensions_(detector_.extent()) + dimensions_(detector_.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_() { } template -placement_finder::placement_finder(DetectorT & detector, box2d const& extent) +placement_finder::placement_finder(text_placement_info &placement_info, string_info &info, DetectorT & detector, box2d const& extent) : detector_(detector), - dimensions_(extent) + 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_() { } template template -void placement_finder::find_point_placements(placement & p, text_placement_info_ptr po, T & shape_path) +void placement_finder::find_point_placements(T & shape_path) { unsigned cmd; double new_x = 0.0; @@ -182,15 +131,15 @@ void placement_finder::find_point_placements(placement & p, text_plac { double x, y; shape_path.vertex(&x,&y); - find_point_placement(p, po, x, y); + find_point_placement(x, y); return; } int num_labels = 1; if (p.label_spacing > 0) - num_labels = static_cast (floor(total_distance / p.label_spacing)); + num_labels = static_cast (floor(total_distance / pi.get_actual_label_spacing())); - if (p.force_odd_labels && num_labels%2 == 0) + if (p.force_odd_labels && num_labels % 2 == 0) num_labels--; if (num_labels <= 0) num_labels = 1; @@ -217,7 +166,7 @@ void placement_finder::find_point_placements(placement & p, text_plac { //Try place at the specified place double new_weight = (segment_length - (distance - target_distance))/segment_length; - find_point_placement(p, po, old_x + (new_x-old_x)*new_weight, old_y + (new_y-old_y)*new_weight); + 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. @@ -231,206 +180,227 @@ void placement_finder::find_point_placements(placement & p, text_plac } template -void placement_finder::find_point_placement(placement & p, - text_placement_info_ptr po, - double label_x, - double label_y, - double angle, - unsigned line_spacing, - unsigned character_spacing) +void placement_finder::init_string_size() { - double x, y; - std::auto_ptr current_placement(new placement_element); - - std::pair string_dimensions = p.info.get_dimensions(); - double string_width = string_dimensions.first + (character_spacing *(p.info.num_characters()-1)); - double string_height = string_dimensions.second; + // Get total string size + string_width_ = 0; + string_height_ = 0; + first_line_space_ = 0; + if (!info_.num_characters()) return; //At least one character is required + for (unsigned 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 +} - // use height of tallest character in the string for the 'line' spacing to obtain consistent line spacing - double max_character_height = string_height; // height of the tallest character in the string + + +template +void placement_finder::find_line_breaks() +{ + bool first_line = true; + line_breaks_.clear(); + line_sizes_.clear(); // check if we need to wrap the string - double wrap_at = string_width + 1.0; - if (p.wrap_width && string_width > p.wrap_width) + 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) ; + 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 - std::vector line_breaks; - std::vector line_widths; - - if ((p.info.num_characters() > 0) && ((wrap_at < string_width) || p.info.has_line_breaks())) + if ((wrap_at < string_width_) || info_.has_line_breaks()) { - int last_wrap_char = 0; - int last_wrap_char_width = 0; - string_width = 0.0; - string_height = 0.0; + 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 word_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, word_width does include char width + spacing, but not the spacing after the last char - for (unsigned int ii = 0; ii < p.info.num_characters(); ii++) + for (unsigned int ii = 0; ii < info_.num_characters(); ii++) { - char_info ci; - ci = p.info.at(ii); - - double cwidth = ci.width + character_spacing; - + char_info const& ci = info_.at(ii); unsigned c = ci.c; - word_width += cwidth; - if ((c == p.wrap_char) || (c == '\n')) + if ((c == ci.format->wrap_char) || (c == '\n')) { - last_wrap_char = ii; - last_wrap_char_width = cwidth; - line_width += word_width; + 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; 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); + 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 - character_spacing) > wrap_at && !p.wrap_before) || - ((line_width + word_width - character_spacing) > wrap_at && p.wrap_before)) )) + (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)) )) { - // Remove width of breaking space character since it is not rendered and the character_spacing for the last character on the line - line_width -= (last_wrap_char_width + character_spacing); - string_width = string_width > line_width ? string_width : line_width; - string_height += max_character_height; - line_breaks.push_back(last_wrap_char); - line_widths.push_back(line_width); - ii = last_wrap_char; + string_width_ = std::max(string_width_, line_width); //Total width is the longest line + string_height_ += line_height; + line_breaks_.push_back(last_wrap_char_pos); + line_sizes_.push_back(std::make_pair(line_width, line_height)); line_width = 0.0; - word_width = 0.0; + line_height = 0.0; + last_wrap_char_width = 0; //Wrap char supressed + first_line = false; } } - line_width += (word_width - character_spacing); // remove character_spacing from last character on the line - string_width = string_width > line_width ? string_width : line_width; - string_height += max_character_height; - line_breaks.push_back(p.info.num_characters()); - line_widths.push_back(line_width); + line_width += last_wrap_char_width + word_width; + line_height = std::max(line_height, word_height); + string_width_ = std::max(string_width_, line_width); + string_height_ += line_height; + line_sizes_.push_back(std::make_pair(line_width, line_height)); + } else { + //No linebreaks + line_sizes_.push_back(std::make_pair(string_width_, string_height_)); } - if (line_breaks.size() == 0) - { - line_breaks.push_back(p.info.num_characters()); - line_widths.push_back(string_width); + line_breaks_.push_back(info_.num_characters()); +} + + + +template +void placement_finder::init_alignment() +{ + valign_ = p.valign; + if (valign_ == V_AUTO) { + if (p.displacement.get<1>() > 0.0) + valign_ = V_BOTTOM; + else if (p.displacement.get<1>() < 0.0) + valign_ = V_TOP; + else + valign_ = V_MIDDLE; } - int total_lines = line_breaks.size(); - p.info.set_dimensions( string_width, (string_height + (line_spacing * (total_lines-1))) ); + halign_ = p.halign; + if (halign_ == H_AUTO) { + if (p.displacement.get<0>() > 0.0) + halign_ = H_RIGHT; + else if (p.displacement.get<0>() < 0.0) + halign_ = H_LEFT; + else + halign_ = H_MIDDLE; + } +} + +template +void placement_finder::adjust_position(placement_element *current_placement, double label_x, double label_y) +{ // if needed, adjust for desired vertical alignment current_placement->starting_y = label_y; // no adjustment, default is MIDDLE - - vertical_alignment_e real_valign = po->valign; - if (real_valign == V_AUTO) { - if (po->displacement.get<1>() > 0.0) - real_valign = V_BOTTOM; - else if (po->displacement.get<1>() < 0.0) - real_valign = V_TOP; - else - real_valign = V_MIDDLE; + if (valign_ == V_TOP) + current_placement->starting_y -= 0.5 * string_height_; // move center up by 1/2 the total height + else if (valign_ == V_BOTTOM) { + current_placement->starting_y += 0.5 * string_height_; // move center down by the 1/2 the total height + current_placement->starting_y -= first_line_space_; + } else if (valign_ == V_MIDDLE) { + current_placement->starting_y -= first_line_space_/2.0; } - horizontal_alignment_e real_halign = po->halign; - if (real_halign == H_AUTO) { - if (po->displacement.get<0>() > 0.0) - real_halign = H_RIGHT; - else if (po->displacement.get<0>() < 0.0) - real_halign = H_LEFT; - else - real_halign = H_MIDDLE; - } - - if (real_valign == V_TOP) - current_placement->starting_y -= 0.5 * (string_height + (line_spacing * (total_lines-1))); // move center up by 1/2 the total height - - else if (real_valign == V_BOTTOM) - current_placement->starting_y += 0.5 * (string_height + (line_spacing * (total_lines-1))); // move center down by the 1/2 the total height - - // correct placement for error, but BOTTOM does not need to be adjusted - // (text rendering is at text_size, but line placement is by line_height (max_character_height), - // and the rendering adds the extra space below the characters) - if (real_valign == V_TOP ) - current_placement->starting_y -= (po->text_size - max_character_height); // move up by the error - - else if (real_valign == V_MIDDLE) - current_placement->starting_y -= ((po->text_size - max_character_height) / 2.0); // move up by 1/2 the error - // set horizontal position to middle of text current_placement->starting_x = label_x; // no adjustment, default is MIDDLE - - if (real_halign == H_LEFT) - current_placement->starting_x -= 0.5 * string_width; // move center left by 1/2 the string width - - else if (real_halign == H_RIGHT) - current_placement->starting_x += 0.5 * string_width; // move center right by 1/2 the string width + if (halign_ == H_LEFT) + current_placement->starting_x -= 0.5 * string_width_; // move center left by 1/2 the string width + else if (halign_ == H_RIGHT) + current_placement->starting_x += 0.5 * string_width_; // move center right by 1/2 the string width // adjust text envelope position by user's x-y displacement (dx, dy) - current_placement->starting_x += p.scale_factor_ * boost::tuples::get<0>(po->displacement); - current_placement->starting_y += p.scale_factor_ * boost::tuples::get<1>(po->displacement); + current_placement->starting_x += pi.get_scale_factor() * boost::tuples::get<0>(p.displacement); + current_placement->starting_y += pi.get_scale_factor() * boost::tuples::get<1>(p.displacement); + +} + +template +void placement_finder::find_point_placement(double label_x, double label_y, double angle) +{ + init_string_size(); + find_line_breaks(); + init_alignment(); + + double rad = M_PI * angle/180.0; + double cosa = std::cos(rad); + double sina = std::sin(rad); + + double x, y; + std::auto_ptr current_placement(new placement_element); + + adjust_position(current_placement.get(), label_x, label_y); // presets for first line unsigned int line_number = 0; - unsigned int index_to_wrap_at = line_breaks[0]; - double line_width = line_widths[0]; + unsigned int index_to_wrap_at = line_breaks_[0]; + double line_width = line_sizes_[0].first; + double line_height = line_sizes_[0].second; + //TODO: Understand and document this // set for upper left corner of text envelope for the first line, bottom left of first character - x = -(line_width / 2.0); - if (p.info.get_rtl()==false) - { - y = (0.5 * (string_height + (line_spacing * (total_lines-1)))) - max_character_height; - } + y = (string_height_ / 2.0) - line_height; + + // adjust for desired justification + //TODO: Understand and document this + if (p.jalign == J_LEFT) + x = -(string_width_ / 2.0); + else if (p.jalign == J_RIGHT) + x = (string_width_ / 2.0) - line_width; else - { - y = -(0.5 * (string_height + (line_spacing * (total_lines-1)))) + max_character_height; - } - - // if needed, adjust for desired justification (J_MIDDLE is the default) - if( po->jalign == J_LEFT ) - x = -(string_width / 2.0); - - else if (po->jalign == J_RIGHT) - x = (string_width / 2.0) - line_width; + x = -(line_width / 2.0); // save each character rendering position and build envelope as go thru loop std::queue< box2d > c_envelopes; - for (unsigned i = 0; i < p.info.num_characters(); i++) + for (unsigned i = 0; i < info_.num_characters(); i++) { - char_info ci; - ci = p.info.at(i); + char_info const& ci = info_.at(i); - double cwidth = ci.width + character_spacing; + double cwidth = ci.width + ci.format->character_spacing; unsigned c = ci.c; if (i == index_to_wrap_at) { - index_to_wrap_at = line_breaks[++line_number]; - line_width = line_widths[line_number]; + index_to_wrap_at = line_breaks_[++line_number]; + line_width = line_sizes_[line_number].first; + line_height= line_sizes_[line_number].second; - if (p.info.get_rtl()==false) - { - y -= (max_character_height + line_spacing); // move position down to line start - } - else - { - y += (max_character_height + line_spacing); // move position up to line start - } + y -= line_height; // move position down to line start // reset to begining of line position - x = ((po->jalign == J_LEFT)? -(string_width / 2.0): ((po->jalign == J_RIGHT)? ((string_width /2.0) - line_width): -(line_width / 2.0))); + if (p.jalign == J_LEFT) + x = -(string_width_ / 2.0); + else if (p.jalign == J_RIGHT) + x = (string_width_ / 2.0) - line_width; + else + x = -(line_width / 2.0); continue; } else { // place the character relative to the center of the string envelope - double rad = M_PI * angle/180.0; - double cosa = fast_cos(rad); - double sina = fast_sin(rad); - double dx = x * cosa - y*sina; double dy = x * sina + y*cosa; @@ -439,37 +409,41 @@ void placement_finder::find_point_placement(placement & p, // compute the Bounding Box for each character and test for: // overlap, minimum distance or edge avoidance - exit if condition occurs box2d e; - if (p.has_dimensions) + /*x axis: left to right, y axis: top to bottom (negative values higher)*/ + if (pi.has_dimensions) { - e.init(current_placement->starting_x - (p.dimensions.first/2.0), // Top Left - current_placement->starting_y - (p.dimensions.second/2.0), + e.init(current_placement->starting_x - (pi.dimensions.first/2.0), // Top Left + current_placement->starting_y - (pi.dimensions.second/2.0), - current_placement->starting_x + (p.dimensions.first/2.0), // Bottom Right - current_placement->starting_y + (p.dimensions.second/2.0)); + current_placement->starting_x + (pi.dimensions.first/2.0), // Bottom Right + current_placement->starting_y + (pi.dimensions.second/2.0)); } else { e.init(current_placement->starting_x + dx, // Bottom Left - current_placement->starting_y - dy, + current_placement->starting_y - dy - ci.ymin, /*ymin usually <0 */ current_placement->starting_x + dx + ci.width, // Top Right - current_placement->starting_y - dy - max_character_height); + current_placement->starting_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,p.minimum_distance))) + if (!detector_.extent().intersects(e) || (!p.allow_overlap && !detector_.has_point_placement(e, pi.get_actual_minimum_distance()))) { return; + } // if avoid_edges test dimensions contains e - if (p.avoid_edges && !dimensions_.contains(e)) + if (p.avoid_edges && !dimensions_.contains(e)) { return; + } - if (p.minimum_padding > 0) + if (p.minimum_padding > 0) { - box2d epad(e.minx()-p.minimum_padding, - e.miny()-p.minimum_padding, - e.maxx()+p.minimum_padding, - e.maxy()+p.minimum_padding); + double min_pad = pi.get_actual_minimum_padding(); + box2d epad(e.minx()-min_pad, + e.miny()-min_pad, + e.maxx()+min_pad, + e.maxy()+min_pad); if (!dimensions_.contains(epad)) { return; @@ -482,6 +456,8 @@ void placement_finder::find_point_placement(placement & p, x += cwidth; // move position to next character } +#if 0 + //TODO // check the placement of any additional envelopes if (!p.allow_overlap && !p.additional_boxes.empty()) { @@ -498,22 +474,24 @@ void placement_finder::find_point_placement(placement & p, c_envelopes.push(pt); } } +#endif // since there was no early exit, add the character envelopes to the placements' envelopes while( !c_envelopes.empty() ) { - p.envelopes.push( c_envelopes.front() ); + pi.envelopes.push( c_envelopes.front() ); c_envelopes.pop(); } - p.placements.push_back(current_placement.release()); + pi.placements.push_back(current_placement.release()); } template template -void placement_finder::find_line_placements(placement & p, text_placement_info_ptr po, PathT & shape_path) +void placement_finder::find_line_placements(PathT & shape_path) { + init_string_size(); unsigned cmd; double new_x = 0.0; double new_y = 0.0; @@ -556,29 +534,26 @@ void placement_finder::find_line_placements(placement & p, text_place return; double distance = 0.0; - std::pair string_dimensions = p.info.get_dimensions(); - double string_width = string_dimensions.first; - - double displacement = boost::tuples::get<1>(po->displacement); // displace by dy + double displacement = boost::tuples::get<1>(p.displacement); // displace by dy //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 + if (total_distance < string_width_) //Can't place any strings return; //If there is no spacing then just do one label, otherwise calculate how many there should be int num_labels = 1; if (p.label_spacing > 0) - num_labels = static_cast (floor(total_distance / (p.label_spacing + string_width))); + num_labels = static_cast (floor(total_distance / (pi.get_actual_label_spacing() + string_width_))); - if (p.force_odd_labels && num_labels%2 == 0) + if (p.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 + 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; @@ -620,7 +595,7 @@ void placement_finder::find_line_placements(placement & p, text_place { //Record details for the start of the string placement int orientation = 0; - std::auto_ptr current_placement = get_placement_offset(p, path_positions, path_distances, orientation, index, segment_length - (distance - target_distance) + (diff*dir)); + std::auto_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() == NULL) @@ -639,22 +614,20 @@ void placement_finder::find_line_placements(placement & p, text_place } anglesum /= current_placement->nodes_.size(); //Now it is angle average - double disp_x = p.scale_factor_ * displacement*fast_cos(anglesum+M_PI/2); - double disp_y = p.scale_factor_ * displacement*fast_sin(anglesum+M_PI/2); //Offset all the characters by this angle for (unsigned i = 0; i < current_placement->nodes_.size(); i++) { - current_placement->nodes_[i].x += disp_x; - current_placement->nodes_[i].y += disp_y; + current_placement->nodes_[i].x += pi.get_scale_factor() * displacement*cos(anglesum+M_PI/2); + current_placement->nodes_[i].y += pi.get_scale_factor() * displacement*sin(anglesum+M_PI/2); } } - bool status = test_placement(p, current_placement, orientation); + bool status = test_placement(current_placement, orientation); if (status) //We have successfully placed one { - p.placements.push_back(current_placement.release()); - update_detector(p); + pi.placements.push_back(current_placement.release()); + update_detector(); //Totally break out of the loops diff = tolerance; @@ -663,8 +636,8 @@ void placement_finder::find_line_placements(placement & p, text_place else { //If we've failed to place, remove all the envelopes we've added up - while (!p.envelopes.empty()) - p.envelopes.pop(); + while (!pi.envelopes.empty()) + pi.envelopes.pop(); } //Don't need to loop twice when diff = 0 @@ -684,7 +657,7 @@ void placement_finder::find_line_placements(placement & p, text_place } template -std::auto_ptr placement_finder::get_placement_offset(placement & p, const std::vector &path_positions, const std::vector &path_distances, int &orientation, unsigned index, double distance) +std::auto_ptr placement_finder::get_placement_offset(const std::vector &path_positions, const std::vector &path_distances, int &orientation, unsigned index, double distance) { //Check that the given distance is on the given index and find the correct index and distance if not while (distance < 0 && index > 1) @@ -710,7 +683,6 @@ std::auto_ptr placement_finder::get_placement_offs std::auto_ptr current_placement(new placement_element); - double string_height = p.info.get_dimensions().second; double old_x = path_positions[index-1].x; double old_y = path_positions[index-1].y; @@ -728,7 +700,7 @@ std::auto_ptr placement_finder::get_placement_offs current_placement->starting_x = old_x + dx*distance/segment_length; current_placement->starting_y = old_y + dy*distance/segment_length; - double angle = fast_atan2(-dy, dx); + double angle = atan2(-dy, dx); bool orientation_forced = (orientation != 0); //Wether the orientation was set by the caller if (!orientation_forced) @@ -736,17 +708,14 @@ std::auto_ptr placement_finder::get_placement_offs unsigned upside_down_char_count = 0; //Count of characters that are placed upside down. - for (unsigned i = 0; i < p.info.num_characters(); ++i) + for (unsigned i = 0; i < info_.num_characters(); ++i) { - char_info ci; - unsigned c; + // grab the next character according to the orientation + char_info const &ci = orientation > 0 ? info_.at(i) : info_.at(info_.num_characters() - i - 1); + unsigned c = ci.c; double last_character_angle = angle; - // grab the next character according to the orientation - ci = orientation > 0 ? p.info.at(i) : p.info.at(p.info.num_characters() - i - 1); - c = ci.c; - //Coordinates this character will start at if (segment_length == 0) { // Not allowed to place across on 0 length segments or discontinuities @@ -826,14 +795,15 @@ std::auto_ptr placement_finder::get_placement_offs double render_y = start_y; //Center the text on the line - render_x += (((double)string_height/2.0) - 1.0)*sina; - render_y += (((double)string_height/2.0) - 1.0)*cosa; + double char_height = ci.avg_height; + render_x -= char_height/2.0*cos(render_angle+M_PI/2); + render_y += char_height/2.0*sin(render_angle+M_PI/2); if (orientation < 0) { // rotate in place - render_x += ci.width*cosa - (string_height-2)*sina; - render_y -= ci.width*sina + (string_height-2)*cosa; + render_x += ci.width*cos(render_angle) - (char_height-2)*sin(render_angle); + render_y -= ci.width*sin(render_angle) + (char_height-2)*cos(render_angle); render_angle += M_PI; } current_placement->add_node(c,render_x - current_placement->starting_x, @@ -851,13 +821,13 @@ std::auto_ptr placement_finder::get_placement_offs } //If we placed too many characters upside down - if (upside_down_char_count >= p.info.num_characters()/2.0) + 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(p, path_positions, path_distances, orientation, initial_index, initial_distance); + current_placement = get_placement_offset(path_positions, path_distances, orientation, initial_index, initial_distance); } else { @@ -871,58 +841,50 @@ std::auto_ptr placement_finder::get_placement_offs } template -bool placement_finder::test_placement(placement & p, const std::auto_ptr & current_placement, const int & orientation) +bool placement_finder::test_placement(const std::auto_ptr & current_placement, const int & orientation) { - std::pair string_dimensions = p.info.get_dimensions(); - - double string_height = string_dimensions.second; - - //Create and test envelopes bool status = true; - for (unsigned i = 0; i < p.info.num_characters(); ++i) + for (unsigned i = 0; i < info_.num_characters(); ++i) { // grab the next character according to the orientation - char_info ci = orientation > 0 ? p.info.at(i) : p.info.at(p.info.num_characters() - i - 1); + char_info const& ci = orientation > 0 ? info_.at(i) : info_.at(info_.num_characters() - i - 1); int c; double x, y, angle; - char_properties *format; - current_placement->vertex(&c, &x, &y, &angle, &format); + char_properties *properties; + current_placement->vertex(&c, &x, &y, &angle, &properties); x = current_placement->starting_x + x; y = current_placement->starting_y - y; if (orientation < 0) { - double sina = fast_sin(angle); - double cosa = fast_cos(angle); // rotate in place - x += ci.width*cosa - (string_height-2)*sina; - y -= ci.width*sina + (string_height-2)*cosa; + /* TODO: What's the meaning of -2? */ + x += ci.width*cos(angle) - (string_height_-2)*sin(angle); + y -= ci.width*sin(angle) + (string_height_-2)*cos(angle); angle += M_PI; } box2d e; - if (p.has_dimensions) + if (pi.has_dimensions) { - e.init(x, y, x + p.dimensions.first, y + p.dimensions.second); + e.init(x, y, x + pi.dimensions.first, y + pi.dimensions.second); } else { - double sina = fast_sin(angle); - double cosa = fast_cos(angle); // put four corners of the letter into envelope - e.init(x, y, x + ci.width*cosa, - y - ci.width*sina); - e.expand_to_include(x - ci.height()*sina, - y - ci.height()*cosa); - e.expand_to_include(x + (ci.width*cosa - ci.height()*sina), - y - (ci.width*sina + ci.height()*cosa)); + e.init(x, y, x + ci.width*cos(angle), + y - ci.width*sin(angle)); + e.expand_to_include(x - ci.height()*sin(angle), + y - ci.height()*cos(angle)); + e.expand_to_include(x + (ci.width*cos(angle) - ci.height()*sin(angle)), + y - (ci.width*sin(angle) + ci.height()*cos(angle))); } if (!detector_.extent().intersects(e) || - !detector_.has_placement(e, p.info.get_string(), p.minimum_distance)) + !detector_.has_placement(e, info_.get_string(), pi.get_actual_minimum_distance())) { //std::clog << "No Intersects:" << !dimensions_.intersects(e) << ": " << e << " @ " << dimensions_ << std::endl; - //std::clog << "No Placements:" << !detector_.has_placement(e, p.info.get_string(), p.minimum_distance) << std::endl; + //std::clog << "No Placements:" << !detector_.has_placement(e, info.get_string(), p.minimum_distance) << std::endl; status = false; break; } @@ -933,19 +895,20 @@ bool placement_finder::test_placement(placement & p, const std::auto_ status = false; break; } - if (p.minimum_padding > 0) + if (p.minimum_padding > 0) { - box2d epad(e.minx()-p.minimum_padding, - e.miny()-p.minimum_padding, - e.maxx()+p.minimum_padding, - e.maxy()+p.minimum_padding); + double min_pad = pi.get_actual_minimum_padding(); + box2d epad(e.minx()-min_pad, + e.miny()-min_pad, + e.maxx()+min_pad, + e.maxy()+min_pad); if (!dimensions_.contains(epad)) { status = false; break; } } - p.envelopes.push(e); + pi.envelopes.push(e); } current_placement->rewind(); @@ -1001,27 +964,27 @@ void placement_finder::find_line_circle_intersection( } template -void placement_finder::update_detector(placement & p) +void placement_finder::update_detector() { bool first = true; // add the bboxes to the detector and remove from the placement - while (!p.envelopes.empty()) + while (!pi.envelopes.empty()) { - box2d e = p.envelopes.front(); - detector_.insert(e, p.info.get_string()); - p.envelopes.pop(); + box2d e = pi.envelopes.front(); + detector_.insert(e, info_.get_string()); + pi.envelopes.pop(); - if (p.collect_extents) + if (pi.collect_extents) { if(first) { first = false; - p.extents = e; + pi.extents = e; } else { - p.extents.expand_to_include(e); + pi.extents.expand_to_include(e); } } } @@ -1033,11 +996,58 @@ void placement_finder::clear() detector_.clear(); } +template +void placement_finder::find_placement(double angle, geometry_type const& geom, CoordTransform const& t, proj_transform const& prj_trans) +{ + double label_x=0.0; + double label_y=0.0; + double z=0.0; + if (p.label_placement == POINT_PLACEMENT || + p.label_placement == VERTEX_PLACEMENT || + p.label_placement == INTERIOR_PLACEMENT) + { + unsigned iterations = 1; + if (p.label_placement == VERTEX_PLACEMENT) + { + iterations = geom.num_points(); + geom.rewind(0); + } + for(unsigned jj = 0; jj < iterations; jj++) { + switch (p.label_placement) + { + case POINT_PLACEMENT: + geom.label_position(&label_x, &label_y); + break; + case INTERIOR_PLACEMENT: + geom.label_interior_position(&label_x, &label_y); + break; + case VERTEX_PLACEMENT: + geom.vertex(&label_x, &label_y); + break; + case LINE_PLACEMENT: + case label_placement_enum_MAX: + /*not handled here*/ + break; + } + prj_trans.backward(label_x, label_y, z); + t.forward(&label_x, &label_y); + + find_point_placement(label_x, label_y, angle); + } + update_detector(); + } else if (p.label_placement == LINE_PLACEMENT && geom.num_points() > 1) + { + typedef coord_transform2 path_type; + path_type path(t, geom, prj_trans); + find_line_placements(path); + } +} + typedef coord_transform2 PathType; typedef label_collision_detector4 DetectorType; template class placement_finder; -template void placement_finder::find_point_placements (placement&, text_placement_info_ptr po, PathType & ); -template void placement_finder::find_line_placements (placement&, text_placement_info_ptr po, PathType & ); +template void placement_finder::find_point_placements(PathType &); +template void placement_finder::find_line_placements(PathType &); } // namespace From 581d35987e13db85093460b87a8401af1caefcf4 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 18:47:23 +0100 Subject: [PATCH 46/81] Disable ShieldSymbolizer in cairo_renderer.cpp (see fdd58903f9). --- src/cairo_renderer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 862ea4034..88bd63d3b 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -1082,11 +1082,11 @@ void cairo_renderer_base::process(shield_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { +#if 0 typedef coord_transform2 path_type; text_placement_info_ptr placement_options = sym.get_placement_options()->get_placement_info(); placement_options->next(); - placement_options->next_position_only(); UnicodeString text; if( sym.get_no_text() ) @@ -1282,6 +1282,7 @@ void cairo_renderer_base::process(shield_symbolizer const& sym, } } } +#endif } void cairo_renderer_base::process(line_pattern_symbolizer const& sym, From 02eca0825fbc60ee147f5f62fd41e063048199eb Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 19:17:59 +0100 Subject: [PATCH 47/81] Fix set_displacement(). --- src/text_symbolizer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text_symbolizer.cpp b/src/text_symbolizer.cpp index 17b7407d5..2966e73d1 100644 --- a/src/text_symbolizer.cpp +++ b/src/text_symbolizer.cpp @@ -347,7 +347,7 @@ void text_symbolizer::set_displacement(double x, double y) void text_symbolizer::set_displacement(position const& p) { - placement_options_->set_default_displacement(p); + placement_options_->properties.displacement = p; } position const& text_symbolizer::get_displacement() const From b945dff774bd8bf9b10f9cf21e303f26d88f8007 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 19:53:29 +0100 Subject: [PATCH 48/81] Fix metawriters. --- include/mapnik/metawriter.hpp | 6 +++--- include/mapnik/metawriter_inmem.hpp | 4 ++-- include/mapnik/metawriter_json.hpp | 4 ++-- src/metawriter.cpp | 24 ++++++++++++------------ src/metawriter_inmem.cpp | 6 +++--- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/include/mapnik/metawriter.hpp b/include/mapnik/metawriter.hpp index fa324cecf..7bd2fc394 100644 --- a/include/mapnik/metawriter.hpp +++ b/include/mapnik/metawriter.hpp @@ -41,7 +41,7 @@ namespace mapnik { -struct placement; +class text_placement_info; /** Implementation of std::map that also returns const& for operator[]. */ class metawriter_property_map @@ -98,8 +98,8 @@ public: virtual void add_box(box2d const& box, Feature const& feature, CoordTransform const& t, metawriter_properties const& properties)=0; - virtual void add_text(placement const& placement, - face_set_ptr face, + virtual void add_text(text_placement_info const& placement, + face_manager_freetype &font_manager, Feature const& feature, CoordTransform const& t, metawriter_properties const& properties)=0; diff --git a/include/mapnik/metawriter_inmem.hpp b/include/mapnik/metawriter_inmem.hpp index 5fb00d624..870bdcb2d 100644 --- a/include/mapnik/metawriter_inmem.hpp +++ b/include/mapnik/metawriter_inmem.hpp @@ -65,8 +65,8 @@ public: virtual void add_box(box2d const& box, Feature const& feature, CoordTransform const& t, metawriter_properties const& properties); - virtual void add_text(placement const& p, - face_set_ptr face, + virtual void add_text(text_placement_info const& p, + face_manager_freetype &font_manager, Feature const& feature, CoordTransform const& t, metawriter_properties const& properties); diff --git a/include/mapnik/metawriter_json.hpp b/include/mapnik/metawriter_json.hpp index eb7ae8ca9..6c30c2355 100644 --- a/include/mapnik/metawriter_json.hpp +++ b/include/mapnik/metawriter_json.hpp @@ -45,8 +45,8 @@ public: virtual void add_box(box2d const& box, Feature const& feature, CoordTransform const& t, metawriter_properties const& properties); - virtual void add_text(placement const& p, - face_set_ptr face, + virtual void add_text(text_placement_info const& p, + face_manager_freetype &font_manager, Feature const& feature, CoordTransform const& t, metawriter_properties const& properties); diff --git a/src/metawriter.cpp b/src/metawriter.cpp index 8642d969d..25b7b6eaa 100644 --- a/src/metawriter.cpp +++ b/src/metawriter.cpp @@ -23,7 +23,7 @@ // Mapnik #include #include -#include +#include // Boost #include @@ -175,8 +175,8 @@ void metawriter_json_stream::add_box(box2d const &box, Feature const& fe } -void metawriter_json_stream::add_text(placement const& p, - face_set_ptr face, +void metawriter_json_stream::add_text(text_placement_info const& p, + face_manager_freetype &font_manager, Feature const& feature, CoordTransform const& t, metawriter_properties const& properties) @@ -193,13 +193,13 @@ void metawriter_json_stream::add_text(placement const& p, Hightest y = baseline of top line */ -// if (p.placements.size()) std::cout << p.info.get_string() << "\n"; for (unsigned n = 0; n < p.placements.size(); n++) { placement_element & current_placement = const_cast(p.placements[n]); - bool inside = false; + bool inside = false; /* Part of text is inside rendering region */ bool straight = true; - int c; double x, y, angle; + int c; + double x, y, angle; char_properties *format; current_placement.rewind(); for (int i = 0; i < current_placement.num_nodes(); ++i) { @@ -219,13 +219,12 @@ void metawriter_json_stream::add_text(placement const& p, double minx = INT_MAX, miny = INT_MAX, maxx = INT_MIN, maxy = INT_MIN; for (int i = 0; i < current_placement.num_nodes(); ++i) { current_placement.vertex(&c, &x, &y, &angle, &format); + face_set_ptr face = font_manager.get_face_set(format->face_name, format->fontset); char_info ci = face->character_dimensions(c); - if (x < minx) minx = x; - if (x+ci.width > maxx) maxx = x+ci.width; - if (y+ci.height()+ci.ymin > maxy) maxy = y+ci.height()+ci.ymin; - if (y+ci.ymin < miny) miny = y+ci.ymin; - // std::cout << (char) c << " height:" << ci.height() << " ymin:" << ci.ymin << " y:" << y << " miny:"<< miny << " maxy:"<< maxy <<"\n"; - + minx = std::min(minx, x); + maxx = std::max(maxx, x+ci.width); + maxy = std::max(maxy, y+ci.ymax); + miny = std::min(miny, y+ci.ymin); } add_box(box2d(current_placement.starting_x+minx, current_placement.starting_y-miny, @@ -243,6 +242,7 @@ void metawriter_json_stream::add_text(placement const& p, } current_placement.vertex(&c, &x, &y, &angle, &format); if (c == ' ') continue; + face_set_ptr face = font_manager.get_face_set(format->face_name, format->fontset); char_info ci = face->character_dimensions(c); double x0, y0, x1, y1, x2, y2, x3, y3; diff --git a/src/metawriter_inmem.cpp b/src/metawriter_inmem.cpp index 27025cde0..be7617c13 100644 --- a/src/metawriter_inmem.cpp +++ b/src/metawriter_inmem.cpp @@ -72,9 +72,9 @@ metawriter_inmem::add_box(box2d const& box, Feature const& feature, instances_.push_back(inst); } -void -metawriter_inmem::add_text(placement const& p, - face_set_ptr /*face*/, +void +metawriter_inmem::add_text(text_placement_info const& p, + face_manager_freetype & /*face*/, Feature const& feature, CoordTransform const& /*t*/, metawriter_properties const& properties) { From fb0c9d6d9cfa115e09e5ea937bef9a181d9f294b Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 20:04:13 +0100 Subject: [PATCH 49/81] Fix text_placements.cpp. --- src/text_placements.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/text_placements.cpp b/src/text_placements.cpp index 3b29e429e..b6865f2cd 100644 --- a/src/text_placements.cpp +++ b/src/text_placements.cpp @@ -329,9 +329,6 @@ std::set text_placements::get_all_expressions() /************************************************************************/ text_placement_info::text_placement_info(text_placements const* parent): - displacement(parent->displacement_), - text_size(parent->text_size_), halign(parent->halign_), jalign(parent->jalign_), - valign(parent->valign_), properties(parent->properties), scale_factor(1), has_dimensions(false), From 1527fed59ccc8acf7e99f567af749af29621b832 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 20:08:45 +0100 Subject: [PATCH 50/81] Remove unused includes. --- include/mapnik/text_symbolizer.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/mapnik/text_symbolizer.hpp b/include/mapnik/text_symbolizer.hpp index f1c7ce6f0..cba2fdfe0 100644 --- a/include/mapnik/text_symbolizer.hpp +++ b/include/mapnik/text_symbolizer.hpp @@ -26,8 +26,6 @@ // mapnik #include #include -#include -#include #include #include From 31fd5647f258a0b2a137b0379f7decbb51eaf6c4 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 20:51:09 +0100 Subject: [PATCH 51/81] Use font manager and char properties in text engine. --- include/mapnik/font_engine_freetype.hpp | 39 +------------------------ include/mapnik/text_path.hpp | 20 ------------- src/font_engine_freetype.cpp | 30 +++++-------------- 3 files changed, 8 insertions(+), 81 deletions(-) diff --git a/include/mapnik/font_engine_freetype.hpp b/include/mapnik/font_engine_freetype.hpp index 69307db81..5ded5076d 100644 --- a/include/mapnik/font_engine_freetype.hpp +++ b/include/mapnik/font_engine_freetype.hpp @@ -343,43 +343,11 @@ struct text_renderer : private boost::noncopyable typedef boost::ptr_vector glyphs_t; typedef T pixmap_type; - text_renderer (pixmap_type & pixmap, face_set_ptr faces, stroker & s); text_renderer (pixmap_type & pixmap, face_manager &font_manager_, stroker & s); box2d prepare_glyphs(text_path *path); void render(double x0, double y0); void render_id(int feature_id,double x0, double y0, double min_radius=1.0); - - void set_pixel_size(unsigned size) - { - faces_->set_pixel_sizes(size); - } - - void set_character_size(float size) - { - faces_->set_character_sizes(size); - } - - void set_fill(mapnik::color const& fill) - { - fill_=fill; - } - - void set_halo_fill(mapnik::color const& halo) - { - halo_fill_=halo; - } - - void set_halo_radius( double radius=1.0) - { - halo_radius_=radius; - } - - void set_opacity( double opacity=1.0) - { - opacity_=opacity; - } - private: void render_bitmap(FT_Bitmap *bitmap, unsigned rgba, int x, int y, double opacity) { @@ -421,14 +389,9 @@ private: } pixmap_type & pixmap_; - face_set_ptr faces_; -// face_manager &font_manager_; + face_manager &font_manager_; stroker & stroker_; - color fill_; - color halo_fill_; - double halo_radius_; glyphs_t glyphs_; - double opacity_; }; typedef face_manager face_manager_freetype; } diff --git a/include/mapnik/text_path.hpp b/include/mapnik/text_path.hpp index 31bf69374..a9e232ae2 100644 --- a/include/mapnik/text_path.hpp +++ b/include/mapnik/text_path.hpp @@ -45,15 +45,11 @@ protected: typedef std::vector characters_t; characters_t characters_; UnicodeString text_; - double width_; - double height_; bool is_rtl; public: string_info(UnicodeString const& text) : characters_(), text_(text), - width_(0), - height_(0), is_rtl(false) { @@ -76,11 +72,6 @@ public: { text_ += text; } - - void add_info(int c, double width, double height) - { - characters_.push_back(char_info(c, width, height, 0, height)); //WARNING: Do not use. Only to keep old code compilable. - } unsigned num_characters() const { @@ -106,17 +97,6 @@ public: { return at(i); } - - void set_dimensions(double width, double height) - { - width_ = width; - height_ = height; - } - - std::pair get_dimensions() const - { - return std::pair(width_, height_); - } UnicodeString const& get_string() const { diff --git a/src/font_engine_freetype.cpp b/src/font_engine_freetype.cpp index 192ab866e..8ee7ca6ea 100644 --- a/src/font_engine_freetype.cpp +++ b/src/font_engine_freetype.cpp @@ -241,11 +241,10 @@ char_info font_face_set::character_dimensions(const unsigned c) return dim; } + void font_face_set::get_string_info(string_info & info, UnicodeString const& ustr, char_properties *format) { double avg_height = character_dimensions('X').height(); - unsigned width = 0; - unsigned height = 0; UErrorCode err = U_ZERO_ERROR; UnicodeString reordered; UnicodeString shaped; @@ -272,8 +271,6 @@ void font_face_set::get_string_info(string_info & info, UnicodeString const& ust for (iter.setToStart(); iter.hasNext();) { UChar ch = iter.nextPostInc(); char_info char_dim = character_dimensions(ch); - width += char_dim.width; - height = (char_dim.height() > height) ? char_dim.height() : height; char_dim.format = format; char_dim.avg_height = avg_height; info.add_info(char_dim); @@ -289,23 +286,8 @@ void font_face_set::get_string_info(string_info & info, UnicodeString const& ust #endif ubidi_close(bidi); - info.set_dimensions(width, height); } -template -text_renderer::text_renderer (pixmap_type & pixmap, face_set_ptr faces, stroker & s) - : pixmap_(pixmap), - faces_(faces), - stroker_(s), - fill_(0,0,0), - halo_fill_(255,255,255), - halo_radius_(0.0), - opacity_(1.0) -{ - -} - -#if 0 template text_renderer::text_renderer (pixmap_type & pixmap, face_manager &font_manager_, stroker & s) : pixmap_(pixmap), @@ -314,7 +296,6 @@ text_renderer::text_renderer (pixmap_type & pixmap, face_manager box2d text_renderer::prepare_glyphs(text_path *path) @@ -350,7 +331,10 @@ box2d text_renderer::prepare_glyphs(text_path *path) pen.x = int(x * 64); pen.y = int(y * 64); - glyph_ptr glyph = faces_->get_glyph(unsigned(c)); + face_set_ptr faces = font_manager_.get_face_set(properties->face_name, properties->fontset); + faces->set_pixel_sizes(properties->text_size); //TODO: Has to work with floats! + + glyph_ptr glyph = faces->get_glyph(unsigned(c)); FT_Face face = glyph->get_face()->get_face(); matrix.xx = (FT_Fixed)( cos( angle ) * 0x10000L ); @@ -490,10 +474,10 @@ boost::mutex freetype_engine::mutex_; #endif std::map > freetype_engine::name2file_; template void text_renderer::render(double, double); -template text_renderer::text_renderer(image_32&, face_set_ptr, stroker&); +template text_renderer::text_renderer(image_32&, face_manager&, stroker&); template box2dtext_renderer::prepare_glyphs(text_path*); template void text_renderer::render_id(int, double, double, double); -template text_renderer::text_renderer(grid&, face_set_ptr, stroker&); +template text_renderer::text_renderer(grid&, face_manager&, stroker&); template box2dtext_renderer::prepare_glyphs(text_path*); } From 1549fd92c36e012c00ffe2c372b2f09446b36edb Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 22 Jan 2012 21:19:35 +0100 Subject: [PATCH 52/81] Float font sizes. --- src/font_engine_freetype.cpp | 2 +- src/text_processing.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/font_engine_freetype.cpp b/src/font_engine_freetype.cpp index 8ee7ca6ea..5e58fa89c 100644 --- a/src/font_engine_freetype.cpp +++ b/src/font_engine_freetype.cpp @@ -332,7 +332,7 @@ box2d text_renderer::prepare_glyphs(text_path *path) pen.y = int(y * 64); face_set_ptr faces = font_manager_.get_face_set(properties->face_name, properties->fontset); - faces->set_pixel_sizes(properties->text_size); //TODO: Has to work with floats! + faces->set_character_sizes(properties->text_size); glyph_ptr glyph = faces->get_glyph(unsigned(c)); FT_Face face = glyph->get_face()->get_face(); diff --git a/src/text_processing.cpp b/src/text_processing.cpp index 30408316f..7d616ff18 100644 --- a/src/text_processing.cpp +++ b/src/text_processing.cpp @@ -435,7 +435,7 @@ string_info &processed_text::get_string_info() { throw config_error("Unable to find specified font face '" + p.face_name + "'"); } - faces->set_pixel_sizes(p.text_size * scale_factor_); + faces->set_character_sizes(p.text_size * scale_factor_); faces->get_string_info(info_, itr->str, &(itr->p)); info_.add_text(itr->str); } From 2eb3662d8757cfeced7db52819d97b83baa77932 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Mon, 23 Jan 2012 00:20:15 +0100 Subject: [PATCH 53/81] Add all files to build.py. --- src/build.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/build.py b/src/build.py index 8fe9d7021..d8839bf7d 100644 --- a/src/build.py +++ b/src/build.py @@ -149,6 +149,7 @@ source = Split( metawriter.cpp raster_colorizer.cpp text_placements.cpp + text_processing.cpp wkt/wkt_factory.cpp metawriter_inmem.cpp metawriter_factory.cpp @@ -227,6 +228,7 @@ source += Split( agg/process_polygon_symbolizer.cpp agg/process_polygon_pattern_symbolizer.cpp agg/process_raster_symbolizer.cpp + agg/process_shield_symbolizer.cpp agg/process_markers_symbolizer.cpp """ ) From 3b887972b82051ec7a52455df5c0f318c708c51a Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Mon, 23 Jan 2012 19:02:35 +0100 Subject: [PATCH 54/81] Start work on new symbolizer helpers. --- include/mapnik/symbolizer_helpers.hpp | 138 ++++++++++++++++++++++++++ src/agg/process_text_symbolizer.cpp | 48 ++++----- 2 files changed, 159 insertions(+), 27 deletions(-) create mode 100644 include/mapnik/symbolizer_helpers.hpp diff --git a/include/mapnik/symbolizer_helpers.hpp b/include/mapnik/symbolizer_helpers.hpp new file mode 100644 index 000000000..bea50295d --- /dev/null +++ b/include/mapnik/symbolizer_helpers.hpp @@ -0,0 +1,138 @@ +/***************************************************************************** + * + * 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 SYMBOLIZER_HELPERS_HPP +#define SYMBOLIZER_HELPERS_HPP + +#include +#include +#include +#include +#include + +#include + + +namespace mapnik { + +template +class text_symbolizer_helper +{ +public: + text_symbolizer_helper(unsigned width, + unsigned height, + double scale_factor, + CoordTransform const &t, + FaceManagerT &font_manager, + DetectorT &detector) : + width_(width), + height_(height), + scale_factor_(scale_factor), + t_(t), + font_manager_(font_manager), + detector_(detector), + text_() + { + + } + + text_placement_info_ptr get_placement(text_symbolizer const& sym, + Feature const& feature, + proj_transform const& prj_trans); +private: + bool initialize_geometries(text_symbolizer const& sym, + Feature const& feature, + proj_transform const& prj_trans); + + unsigned width_; + unsigned height_; + double scale_factor_; + CoordTransform const &t_; + FaceManagerT &font_manager_; + DetectorT &detector_; + boost::shared_ptr text_; /*TODO: Use shared pointers for text placement so we don't need to keep a reference here! */ + // Use a boost::ptr_vector here instread of std::vector? + std::vector geometries_to_process_; +}; + + +template +text_placement_info_ptr text_symbolizer_helper::get_placement( + text_symbolizer const& sym, + Feature const& feature, + proj_transform const& prj_trans) +{ + if (!initialize_geometries(sym, feature, prj_trans)) return text_placement_info_ptr(); + + text_ = boost::shared_ptr(new processed_text(font_manager_, scale_factor_)); + metawriter_with_properties writer = sym.get_metawriter(); + + box2d dims(0, 0, width_, height_); + +// typedef coord_transform2 path_type; + text_placement_info_ptr placement = sym.get_placement_options()->get_placement_info(); + placement->init(scale_factor_, width_, height_); + if (writer.first) + placement->collect_extents = true; + + while (placement->next()) + { + text_processor &processor = placement->properties.processor; + } +} + +template +bool text_symbolizer_helper::initialize_geometries( + text_symbolizer const& sym, + Feature const& feature, + proj_transform const& prj_trans) +{ + unsigned num_geom = feature.num_geometries(); + for (unsigned i=0; i 0) + { + // TODO - find less costly method than fetching full envelope + box2d gbox = t_.forward(geom.envelope(),prj_trans); + if (gbox.width() < sym.get_minimum_path_length()) + { + continue; + } + } + // TODO - calculate length here as well + geometries_to_process_.push_back(const_cast(&geom)); + } + + if (!geometries_to_process_.size() > 0) + { + // early return to avoid significant overhead of rendering setup + return false; + } + return true; +} + +} +#endif // SYMBOLIZER_HELPERS_HPP diff --git a/src/agg/process_text_symbolizer.cpp b/src/agg/process_text_symbolizer.cpp index a807a115d..d3d18354c 100644 --- a/src/agg/process_text_symbolizer.cpp +++ b/src/agg/process_text_symbolizer.cpp @@ -24,7 +24,7 @@ // mapnik #include #include -#include +#include namespace mapnik { @@ -33,33 +33,27 @@ void agg_renderer::process(text_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { -#if 0 - // Use a boost::ptr_vector here instread of std::vector? - std::vector geometries_to_process; - unsigned num_geom = feature.num_geometries(); - for (unsigned i=0; i 0) - { - // TODO - find less costly method than fetching full envelope - box2d gbox = t_.forward(geom.envelope(),prj_trans); - if (gbox.width() < sym.get_minimum_path_length()) - { - continue; - } - } - // TODO - calculate length here as well - geometries_to_process.push_back(const_cast(&geom)); - } - - if (!geometries_to_process.size() > 0) - return; // early return to avoid significant overhead of rendering setup + /* This could also be a member of the renderer class, but I would have + to check if any of the variables changes and notify the helper. + It could be done at a later point, but for now keep the code simple. + */ + text_symbolizer_helper, boost::shared_ptr > helper(width_, height_, scale_factor_, t_, font_manager_, detector_); + + text_placement_info_ptr placement = helper.get_placement(sym, feature, prj_trans); + + if (!placement) return; + + text_renderer ren(pixmap_, font_manager_, *(font_manager_.get_stroker())); + for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) + { + double x = placement->placements[ii].starting_x; + double y = placement->placements[ii].starting_y; + ren.prepare_glyphs(&(placement->placements[ii])); + ren.render(x, y); + } + +#if 0 - typedef coord_transform2 path_type; bool placement_found = false; text_placement_info_ptr placement_options = sym.get_placement_options()->get_placement_info(); From 469568862b1330a6dbdcbaaddaee1dcf84f19e1d Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Tue, 24 Jan 2012 18:23:33 +0100 Subject: [PATCH 55/81] Add documentation. --- docs/textrendering.gv | 20 +++++++++ include/mapnik/text_path.hpp | 26 ++++++++--- include/mapnik/text_placements.hpp | 69 +++++++++++++++++++++++++++--- include/mapnik/text_processing.hpp | 15 ++++--- 4 files changed, 112 insertions(+), 18 deletions(-) create mode 100644 docs/textrendering.gv diff --git a/docs/textrendering.gv b/docs/textrendering.gv new file mode 100644 index 000000000..285ec4cb2 --- /dev/null +++ b/docs/textrendering.gv @@ -0,0 +1,20 @@ +#process with: dot textrendering.gv -Tpng > textrendering.png +digraph textrendering { +# Classes without important virtual members: Round +# Classes with important virtual members: Rect +# Pointers [style=dashed] +# Red: function is called + text_placements[shape=box] + text_placement_info[shape=box] + Renderer + + TextSymbolizer -> text_placements [label="placement_options_", style=dashed] + text_placements -> text_symbolizer_properties [label="properties"] + text_placements -> text_placement_info [label="get_placement_info()", style=dashed] + text_placement_info -> text_symbolizer_properties [label="properties"] + text_placement_info -> text_path [label="placements", style=dashed] + text_symbolizer_properties -> text_processor [label="processor"] + text_path -> Renderer [label="used by"] + Renderer -> text_placement_info [color=red, label="init()"] + +} \ No newline at end of file diff --git a/include/mapnik/text_path.hpp b/include/mapnik/text_path.hpp index a9e232ae2..b7d7daaf1 100644 --- a/include/mapnik/text_path.hpp +++ b/include/mapnik/text_path.hpp @@ -117,7 +117,9 @@ public: } }; -struct text_path : boost::noncopyable + +/** List of all characters and their positions and formats for a placement. */ +class text_path : boost::noncopyable { struct character_node { @@ -139,41 +141,51 @@ struct text_path : boost::noncopyable } }; + int itr_; +public: typedef std::vector character_nodes_t; + character_nodes_t nodes_; double starting_x; double starting_y; - character_nodes_t nodes_; - int itr_; - std::pair string_dimensions; +// std::pair string_dimensions; text_path() - : starting_x(0), - starting_y(0), - itr_(0) {} + : itr_(0), + starting_x(0), + starting_y(0) + + { + + } ~text_path() {} + /** Adds a new char to the list. */ void add_node(int c, double x, double y, double angle, char_properties *format) { nodes_.push_back(character_node(c, x, y, angle, format)); } + /** Return node. Always returns a new node. Has no way to report that there are no more nodes. */ void vertex(int *c, double *x, double *y, double *angle, char_properties **format) { nodes_[itr_++].vertex(c, x, y, angle, format); } + /** Start again at first node. */ void rewind() { itr_ = 0; } + /** Number of nodes. */ int num_nodes() const { return nodes_.size(); } + /** Delete all nodes. */ void clear() { nodes_.clear(); diff --git a/include/mapnik/text_placements.hpp b/include/mapnik/text_placements.hpp index 13d67b8f9..fdb6530dc 100644 --- a/include/mapnik/text_placements.hpp +++ b/include/mapnik/text_placements.hpp @@ -92,10 +92,13 @@ enum justify_alignment DEFINE_ENUM( justify_alignment_e, justify_alignment ); +/** Contains all text symbolizer properties which are not directly related to text formating. */ struct text_symbolizer_properties { text_symbolizer_properties(); + /** Load all values and also the ```processor``` object from XML ptree. */ void set_values_from_xml(boost::property_tree::ptree const &sym, std::map const & fontsets); + /** Save all values to XML ptree (but does not create a new parent node!). */ void to_xml(boost::property_tree::ptree &node, bool explicit_defaults, text_symbolizer_properties const &dfl=text_symbolizer_properties()) const; //Per symbolizer options @@ -105,71 +108,124 @@ struct text_symbolizer_properties horizontal_alignment_e halign; justify_alignment_e jalign; vertical_alignment_e valign; - unsigned label_spacing; // distance between repeated labels on a single geometry - unsigned label_position_tolerance; //distance the label can be moved on the line to fit, if 0 the default is used + /** distance between repeated labels on a single geometry */ + unsigned label_spacing; + /** distance the label can be moved on the line to fit, if 0 the default is used */ + unsigned label_position_tolerance; bool avoid_edges; double minimum_distance; double minimum_padding; double minimum_path_length; double max_char_angle_delta; - bool force_odd_labels; //Always try render an odd amount of labels + /** Always try render an odd amount of labels */ + bool force_odd_labels; bool allow_overlap; unsigned text_ratio; unsigned wrap_width; - text_processor processor; //Contains expressions and text formats + /** Contains everything related to text formating */ + text_processor processor; }; + +/** Generate a possible placement and store results of placement_finder. + * This placement has first to be tested by placement_finder to verify it + * can actually be used. + */ class text_placement_info : boost::noncopyable { public: + /** Constructor. Takes the parent text_placements object as a parameter + * to read defaults from it. */ text_placement_info(text_placements const* parent); /** Get next placement. * This function is also called before the first placement is tried. * Each class has to return at least one position! - * If this functions returns false the placement data should be considered invalid! + * If this functions returns false the placement data should be + * considered invalid! */ virtual bool next()=0; virtual ~text_placement_info() {} + /** Initialize values used by placement finder. Only has to be done once + * per object. + */ void init(double scale_factor_, unsigned w = 0, unsigned h = 0, bool has_dimensions_ = false); + /** Properties actually used by placement finder and renderer. Values in + * here are modified each time next() is called. */ text_symbolizer_properties properties; + + /** Scale factor used by the renderer. */ double scale_factor; + /* TODO: Don't know what this is used for. */ bool has_dimensions; + /* TODO: Don't know what this is used for. */ std::pair dimensions; + /** Set scale factor. */ void set_scale_factor(double factor) { scale_factor = factor; } + /** Get scale factor. */ double get_scale_factor() { return scale_factor; } + /** Get label spacing taking the scale factor into account. */ double get_actual_label_spacing() { return scale_factor * properties.label_spacing; } + /** Get minimum distance taking the scale factor into account. */ double get_actual_minimum_distance() { return scale_factor * properties.minimum_distance; } + /** Get minimum padding taking the scale factor into account. */ double get_actual_minimum_padding() { return scale_factor * properties.minimum_padding; } + /** Collect a bounding box of all texts placed. */ bool collect_extents; - //Output + //Output by placement finder + /** Bounding box of all texts placed. */ box2d extents; + /* TODO */ std::queue< box2d > envelopes; + /* TODO */ boost::ptr_vector placements; }; typedef boost::shared_ptr text_placement_info_ptr; +/** This object handles the management of all TextSymbolizer properties. It can + * be used as a base class for own objects which implement new processing + * semantics. Basically this class just makes sure a pointer of the right + * class is returned by the get_placement_info call. + */ class text_placements { public: text_placements(); + /** Get a text_placement_info object to use in rendering. + * The returned object creates a list of settings which is + * used to try to find a placement and stores all + * information that is generated by + * the placement finder. + * + * This function usually is implemented as + * text_placement_info_ptr text_placements_XXX::get_placement_info() const + * { + * return text_placement_info_ptr(new text_placement_info_XXX(this)); + * } + */ virtual text_placement_info_ptr get_placement_info() const =0; /** Get a list of all expressions used in any placement. * This function is used to collect attributes. */ virtual std::set get_all_expressions(); + /** Destructor. */ virtual ~text_placements() {} + + /** List of all properties used as the default for the subclasses. */ text_symbolizer_properties properties; }; +/** Pointer to object of class text_placements */ typedef boost::shared_ptr text_placements_ptr; + class text_placements_info_dummy; +/** Dummy placement algorithm. Always takes the default value. */ class MAPNIK_DECL text_placements_dummy: public text_placements { public: @@ -177,6 +233,7 @@ public: friend class text_placement_info_dummy; }; +/** Placement info object for dummy placement algorithm. Always takes the default value. */ class MAPNIK_DECL text_placement_info_dummy : public text_placement_info { public: diff --git a/include/mapnik/text_processing.hpp b/include/mapnik/text_processing.hpp index ab111dc1b..512282b00 100644 --- a/include/mapnik/text_processing.hpp +++ b/include/mapnik/text_processing.hpp @@ -68,9 +68,6 @@ struct char_properties double halo_radius; }; -class abstract_token; -class processed_text; - class processed_expression { public: @@ -78,8 +75,7 @@ public: p(properties), str(text) {} char_properties p; UnicodeString str; -private: - friend class processed_text; + }; class processed_text @@ -101,15 +97,24 @@ private: string_info info_; }; +class abstract_token; +/** Stores formating information and uses this to produce formated text for a given feature. */ class text_processor { public: text_processor(); + /** Construct object from XML. */ void from_xml(boost::property_tree::ptree const& pt, std::map const &fontsets); + /** Write object to XML ptree. */ void to_xml(boost::property_tree::ptree &node, bool explicit_defaults, text_processor const& dfl) const; + + /** 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 const& feature); void set_old_style_expression(expression_ptr expr); + /** Add a new formating token. */ void push_back(abstract_token *token); std::set get_all_expressions() const; char_properties defaults; From bdc20f766bd8ebb9c55c7b05c2ef9b6c416cb00e Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Wed, 25 Jan 2012 15:44:19 +0100 Subject: [PATCH 56/81] Complete symbolizer_helpers.hpp for TextSymbolizer. --- include/mapnik/symbolizer_helpers.hpp | 28 ++++++ src/agg/process_text_symbolizer.cpp | 121 +------------------------- 2 files changed, 29 insertions(+), 120 deletions(-) diff --git a/include/mapnik/symbolizer_helpers.hpp b/include/mapnik/symbolizer_helpers.hpp index bea50295d..225971d36 100644 --- a/include/mapnik/symbolizer_helpers.hpp +++ b/include/mapnik/symbolizer_helpers.hpp @@ -93,10 +93,38 @@ text_placement_info_ptr text_symbolizer_helper::get_pla if (writer.first) placement->collect_extents = true; + unsigned num_geom = feature.num_geometries(); + if (!num_geom) return text_placement_info_ptr(); //Nothing to do + while (placement->next()) { text_processor &processor = placement->properties.processor; + text_symbolizer_properties const& p = placement->properties; + /* TODO: Simplify this. */ + text_->clear(); + processor.process(*text_, feature); + string_info &info = text_->get_string_info(); + /* END TODO */ + double angle = 0.0; + if (p.orientation) + { + angle = boost::apply_visitor(evaluate(feature),*(p.orientation)).to_double(); + } + placement_finder finder(*placement, info, detector_, dims); + + unsigned num_geom = feature.num_geometries(); + for (unsigned i=0; iplacements.size()) + continue; + if (writer.first) writer.first->add_text(*placement, font_manager_, feature, t_, writer.second); + return placement; + } } + return text_placement_info_ptr(); } template diff --git a/src/agg/process_text_symbolizer.cpp b/src/agg/process_text_symbolizer.cpp index d3d18354c..fd857a576 100644 --- a/src/agg/process_text_symbolizer.cpp +++ b/src/agg/process_text_symbolizer.cpp @@ -37,7 +37,7 @@ void agg_renderer::process(text_symbolizer const& sym, to check if any of the variables changes and notify the helper. It could be done at a later point, but for now keep the code simple. */ - text_symbolizer_helper, boost::shared_ptr > helper(width_, height_, scale_factor_, t_, font_manager_, detector_); + text_symbolizer_helper, label_collision_detector4> helper(width_, height_, scale_factor_, t_, font_manager_, *detector_); text_placement_info_ptr placement = helper.get_placement(sym, feature, prj_trans); @@ -51,125 +51,6 @@ void agg_renderer::process(text_symbolizer const& sym, ren.prepare_glyphs(&(placement->placements[ii])); ren.render(x, y); } - -#if 0 - - - bool placement_found = false; - text_placement_info_ptr placement_options = sym.get_placement_options()->get_placement_info(); - while (!placement_found && placement_options->next()) - { - expression_ptr name_expr = sym.get_name(); - if (!name_expr) return; - value_type result = boost::apply_visitor(evaluate(feature),*name_expr); - UnicodeString text = result.to_unicode(); - - if ( sym.get_text_transform() == UPPERCASE) - { - text = text.toUpper(); - } - else if ( sym.get_text_transform() == LOWERCASE) - { - text = text.toLower(); - } - else if ( sym.get_text_transform() == CAPITALIZE) - { - text = text.toTitle(NULL); - } - - if ( text.length() <= 0 ) continue; - color const& fill = sym.get_fill(); - - face_set_ptr faces; - - if (sym.get_fontset().size() > 0) - { - faces = font_manager_.get_face_set(sym.get_fontset()); - } - else - { - faces = font_manager_.get_face_set(sym.get_face_name()); - } - - stroker_ptr strk = font_manager_.get_stroker(); - if (!(faces->size() > 0 && strk)) - { - throw config_error("Unable to find specified font face '" + sym.get_face_name() + "'"); - } - text_renderer ren(pixmap_, faces, *strk); - - ren.set_character_size(placement_options->text_size * scale_factor_); - ren.set_fill(fill); - ren.set_halo_fill(sym.get_halo_fill()); - ren.set_halo_radius(sym.get_halo_radius() * scale_factor_); - ren.set_opacity(sym.get_text_opacity()); - - box2d dims(0,0,width_,height_); - placement_finder finder(*detector_,dims); - - string_info info(text); - - faces->get_string_info(info, text, 0); - metawriter_with_properties writer = sym.get_metawriter(); - - BOOST_FOREACH( geometry_type * geom, geometries_to_process ) - { - while (!placement_found && placement_options->next_position_only()) - { - placement text_placement(info, sym, scale_factor_); - text_placement.avoid_edges = sym.get_avoid_edges(); - if (writer.first) - text_placement.collect_extents =true; // needed for inmem metawriter - - if (sym.get_label_placement() == POINT_PLACEMENT || - sym.get_label_placement() == INTERIOR_PLACEMENT) - { - double label_x=0.0; - double label_y=0.0; - double z=0.0; - if (sym.get_label_placement() == POINT_PLACEMENT) - geom->label_position(&label_x, &label_y); - else - geom->label_interior_position(&label_x, &label_y); - prj_trans.backward(label_x,label_y, z); - t_.forward(&label_x,&label_y); - - double angle = 0.0; - expression_ptr angle_expr = sym.get_orientation(); - if (angle_expr) - { - // apply rotation - value_type result = boost::apply_visitor(evaluate(feature),*angle_expr); - angle = result.to_double(); - } - - finder.find_point_placement(text_placement, placement_options, label_x,label_y, - angle, sym.get_line_spacing(), - sym.get_character_spacing()); - finder.update_detector(text_placement); - } - else if ( geom->num_points() > 1 && sym.get_label_placement() == LINE_PLACEMENT) - { - path_type path(t_,*geom,prj_trans); - finder.find_line_placements(text_placement, placement_options, path); - } - - if (!text_placement.placements.size()) continue; - placement_found = true; - - for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ii) - { - double x = text_placement.placements[ii].starting_x; - double y = text_placement.placements[ii].starting_y; - ren.prepare_glyphs(&text_placement.placements[ii]); - ren.render(x,y); - } - - if (writer.first) writer.first->add_text(text_placement, faces, feature, t_, writer.second); - } - } - } -#endif } template void agg_renderer::process(text_symbolizer const&, From 1106dcb445e7efb2078032543f5b2292bd636c31 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Wed, 25 Jan 2012 16:17:38 +0100 Subject: [PATCH 57/81] Support for cairo + grid renderer. --- include/mapnik/symbolizer_helpers.hpp | 1 - src/cairo_renderer.cpp | 175 +++----------------------- src/grid/process_text_symbolizer.cpp | 131 +++---------------- 3 files changed, 38 insertions(+), 269 deletions(-) diff --git a/include/mapnik/symbolizer_helpers.hpp b/include/mapnik/symbolizer_helpers.hpp index 225971d36..e471bb026 100644 --- a/include/mapnik/symbolizer_helpers.hpp +++ b/include/mapnik/symbolizer_helpers.hpp @@ -87,7 +87,6 @@ text_placement_info_ptr text_symbolizer_helper::get_pla box2d dims(0, 0, width_, height_); -// typedef coord_transform2 path_type; text_placement_info_ptr placement = sym.get_placement_options()->get_placement_info(); placement->init(scale_factor_, width_, height_); if (writer.first) diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 88bd63d3b..78b25a031 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -572,11 +573,7 @@ public: void add_text(text_path & path, cairo_face_manager & manager, - face_set_ptr const& faces, - unsigned text_size, - color const& fill, - unsigned halo_radius, - color const& halo_fill) + face_manager &font_manager) { double sx = path.starting_x; double sy = path.starting_y; @@ -591,6 +588,10 @@ public: path.vertex(&c, &x, &y, &angle, &format); + face_set_ptr faces = font_manager.get_face_set(format->face_name, format->fontset); + float text_size = format->text_size; + faces->set_character_sizes(text_size); + glyph_ptr glyph = faces->get_glyph(c); if (glyph) @@ -609,43 +610,11 @@ public: set_font_face(manager, glyph->get_face()); glyph_path(glyph->get_index(), sx + x, sy - y); - } - } - - set_line_width(halo_radius); - set_line_join(ROUND_JOIN); - set_color(halo_fill); - stroke(); - - set_color(fill); - - path.rewind(); - - for (int iii = 0; iii < path.num_nodes(); iii++) - { - int c; - double x, y, angle; - char_properties *format; - - path.vertex(&c, &x, &y, &angle, &format); - - glyph_ptr glyph = faces->get_glyph(c); - - if (glyph) - { - Cairo::Matrix matrix; - - matrix.xx = text_size * cos(angle); - matrix.xy = text_size * sin(angle); - matrix.yx = text_size * -sin(angle); - matrix.yy = text_size * cos(angle); - matrix.x0 = 0; - matrix.y0 = 0; - - set_font_matrix(matrix); - - set_font_face(manager, glyph->get_face()); - + set_line_width(format->halo_radius); + set_line_join(ROUND_JOIN); + set_color(format->halo_fill); + stroke(); + set_color(format->fill); show_glyph(glyph->get_index(), sx + x, sy - y); } } @@ -1466,122 +1435,18 @@ void cairo_renderer_base::process(text_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { -#if 0 - typedef coord_transform2 path_type; + text_symbolizer_helper, label_collision_detector4> helper(detector_.extent().width(), detector_.extent().height(), 1.0 /*scale_factor*/, t_, font_manager_, detector_); - bool placement_found = false; - text_placement_info_ptr placement_options = sym.get_placement_options()->get_placement_info(); - while (!placement_found && placement_options->next()) + text_placement_info_ptr placement = helper.get_placement(sym, feature, prj_trans); + + if (!placement) return; + + cairo_context context(context_); + + for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) { - expression_ptr name_expr = sym.get_name(); - if (!name_expr) return; - value_type result = boost::apply_visitor(evaluate(feature),*name_expr); - UnicodeString text = result.to_unicode(); - - if ( sym.get_text_transform() == UPPERCASE) - { - text = text.toUpper(); - } - else if ( sym.get_text_transform() == LOWERCASE) - { - text = text.toLower(); - } - else if ( sym.get_text_transform() == CAPITALIZE) - { - text = text.toTitle(NULL); - } - - if (text.length() <= 0) continue; - - face_set_ptr faces; - - if (sym.get_fontset().size() > 0) - { - faces = font_manager_.get_face_set(sym.get_fontset()); - } - else - { - faces = font_manager_.get_face_set(sym.get_face_name()); - } - - if (faces->size() == 0) - { - throw config_error("Unable to find specified font face '" + sym.get_face_name() + "'"); - } - cairo_context context(context_); - string_info info(text); - - faces->set_character_sizes(placement_options->text_size); - faces->get_string_info(info, text, 0); - - placement_finder finder(detector_); - - metawriter_with_properties writer = sym.get_metawriter(); - - unsigned num_geom = feature.num_geometries(); - for (unsigned i=0; inext_position_only()) - { - placement text_placement(info, sym, 1.0); - text_placement.avoid_edges = sym.get_avoid_edges(); - if (writer.first) - text_placement.collect_extents = true; // needed for inmem metawriter - - if (sym.get_label_placement() == POINT_PLACEMENT || - sym.get_label_placement() == INTERIOR_PLACEMENT) - { - double label_x, label_y, z=0.0; - if (sym.get_label_placement() == POINT_PLACEMENT) - geom.label_position(&label_x, &label_y); - else - geom.label_interior_position(&label_x, &label_y); - prj_trans.backward(label_x,label_y, z); - t_.forward(&label_x,&label_y); - - double angle = 0.0; - expression_ptr angle_expr = sym.get_orientation(); - if (angle_expr) - { - // apply rotation - value_type result = boost::apply_visitor(evaluate(feature),*angle_expr); - angle = result.to_double(); - } - - finder.find_point_placement(text_placement, placement_options, - label_x, label_y, - angle, sym.get_line_spacing(), - sym.get_character_spacing()); - finder.update_detector(text_placement); - } - else if ( geom.num_points() > 1 && sym.get_label_placement() == LINE_PLACEMENT) - { - path_type path(t_, geom, prj_trans); - finder.find_line_placements(text_placement, placement_options, path); - } - - if (!text_placement.placements.size()) continue; - placement_found = true; - - for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ii) - { - context.add_text(text_placement.placements[ii], - face_manager_, - faces, - placement_options->text_size, - sym.get_fill(), - sym.get_halo_radius(), - sym.get_halo_fill() - ); - } - - if (writer.first) writer.first->add_text(text_placement, faces, feature, t_, writer.second); - } - } + context.add_text(placement->placements[ii], face_manager_, font_manager_); } -#endif } template class cairo_renderer; diff --git a/src/grid/process_text_symbolizer.cpp b/src/grid/process_text_symbolizer.cpp index 5541526de..527196d36 100644 --- a/src/grid/process_text_symbolizer.cpp +++ b/src/grid/process_text_symbolizer.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace mapnik { @@ -33,122 +34,26 @@ void grid_renderer::process(text_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { -#if 0 - typedef coord_transform2 path_type; + text_symbolizer_helper, + label_collision_detector4> helper( + width_, height_, + scale_factor_ * (1.0/pixmap_.get_resolution()), + t_, font_manager_, detector_); - bool placement_found = false; - text_placement_info_ptr placement_options = sym.get_placement_options()->get_placement_info(); - while (!placement_found && placement_options->next()) + text_placement_info_ptr placement = helper.get_placement(sym, feature, prj_trans); + + if (!placement) return; + + text_renderer ren(pixmap_, font_manager_, *(font_manager_.get_stroker())); + for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) { - expression_ptr name_expr = sym.get_name(); - if (!name_expr) return; - value_type result = boost::apply_visitor(evaluate(feature),*name_expr); - UnicodeString text = result.to_unicode(); - - if ( sym.get_text_transform() == UPPERCASE) - { - text = text.toUpper(); - } - else if ( sym.get_text_transform() == LOWERCASE) - { - text = text.toLower(); - } - else if ( sym.get_text_transform() == CAPITALIZE) - { - text = text.toTitle(NULL); - } - - if ( text.length() <= 0 ) continue; - color const& fill = sym.get_fill(); - - face_set_ptr faces; - - if (sym.get_fontset().size() > 0) - { - faces = font_manager_.get_face_set(sym.get_fontset()); - } - else - { - faces = font_manager_.get_face_set(sym.get_face_name()); - } - - stroker_ptr strk = font_manager_.get_stroker(); - if (!(faces->size() > 0 && strk)) - { - throw config_error("Unable to find specified font face '" + sym.get_face_name() + "'"); - } - text_renderer ren(pixmap_, faces, *strk); - ren.set_character_size(placement_options->text_size * (scale_factor_ * (1.0/pixmap_.get_resolution()))); - ren.set_fill(fill); - ren.set_halo_fill(sym.get_halo_fill()); - ren.set_halo_radius(sym.get_halo_radius() * scale_factor_); - ren.set_opacity(sym.get_text_opacity()); - - // /pixmap_.get_resolution() ? - box2d dims(0,0,width_,height_); - placement_finder finder(detector_,dims); - - string_info info(text); - - faces->get_string_info(info, text, 0); - unsigned num_geom = feature.num_geometries(); - for (unsigned i=0; inext_position_only()) - { - placement text_placement(info, sym, scale_factor_); - text_placement.avoid_edges = sym.get_avoid_edges(); - if (sym.get_label_placement() == POINT_PLACEMENT || - sym.get_label_placement() == INTERIOR_PLACEMENT) - { - double label_x, label_y, z=0.0; - if (sym.get_label_placement() == POINT_PLACEMENT) - geom.label_position(&label_x, &label_y); - else - geom.label_interior_position(&label_x, &label_y); - prj_trans.backward(label_x,label_y, z); - t_.forward(&label_x,&label_y); - - double angle = 0.0; - expression_ptr angle_expr = sym.get_orientation(); - if (angle_expr) - { - // apply rotation - value_type result = boost::apply_visitor(evaluate(feature),*angle_expr); - angle = result.to_double(); - } - - finder.find_point_placement(text_placement, placement_options, - label_x, label_y, - angle, sym.get_line_spacing(), - sym.get_character_spacing()); - - finder.update_detector(text_placement); - } - else if ( geom.num_points() > 1 && sym.get_label_placement() == LINE_PLACEMENT) - { - path_type path(t_,geom,prj_trans); - finder.find_line_placements(text_placement, placement_options, path); - } - - if (!text_placement.placements.size()) continue; - placement_found = true; - - for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ii) - { - double x = text_placement.placements[ii].starting_x; - double y = text_placement.placements[ii].starting_y; - ren.prepare_glyphs(&text_placement.placements[ii]); - ren.render_id(feature.id(),x,y,2); - } - } - } + double x = placement->placements[ii].starting_x; + double y = placement->placements[ii].starting_y; + ren.prepare_glyphs(&(placement->placements[ii])); + ren.render_id(feature.id(),x,y,2); } - if (placement_found) - pixmap_.add_feature(feature); -#endif + pixmap_.add_feature(feature); + } template void grid_renderer::process(text_symbolizer const&, From 26c13d4df4aec1528b2690bd2ae9a5608fbceac3 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Wed, 25 Jan 2012 00:00:09 +0100 Subject: [PATCH 58/81] Add tests for text formating. --- tests/data/placement/formating.xml | 21 +++++++++++++++++++++ tests/data/placement/test.py | 5 +++-- 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 tests/data/placement/formating.xml diff --git a/tests/data/placement/formating.xml b/tests/data/placement/formating.xml new file mode 100644 index 000000000..ed42f0865 --- /dev/null +++ b/tests/data/placement/formating.xml @@ -0,0 +1,21 @@ + + + + + + My Style + + shape + points.shp + + + + + + diff --git a/tests/data/placement/test.py b/tests/data/placement/test.py index 1fabccbce..7fb48166a 100755 --- a/tests/data/placement/test.py +++ b/tests/data/placement/test.py @@ -11,7 +11,7 @@ dirname = os.path.dirname(sys.argv[0]) widths = [ 800, 600, 400, 300, 250, 200, 150, 100] filenames = ["list", "simple"] filenames_one_width = ["simple-E", "simple-NE", "simple-NW", "simple-N", - "simple-SE", "simple-SW", "simple-S", "simple-W"] + "simple-SE", "simple-SW", "simple-S", "simple-W", "formating"] def render(filename, width): print "Rendering style \"%s\" with width %d" % (filename, width) @@ -29,4 +29,5 @@ for filename in filenames: mapnik.save_map(m, "%s-out.xml" % filename) for filename in filenames_one_width: - render(filename, 500) \ No newline at end of file + render(filename, 500) + From 37ba77550c77f82ef9acda4da538b2ce3e50e140 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Wed, 25 Jan 2012 01:14:01 +0100 Subject: [PATCH 59/81] More documentation. --- docs/textrendering.gv | 8 ++++++-- include/mapnik/text_processing.hpp | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/textrendering.gv b/docs/textrendering.gv index 285ec4cb2..90a897a6a 100644 --- a/docs/textrendering.gv +++ b/docs/textrendering.gv @@ -13,8 +13,12 @@ digraph textrendering { text_placements -> text_placement_info [label="get_placement_info()", style=dashed] text_placement_info -> text_symbolizer_properties [label="properties"] text_placement_info -> text_path [label="placements", style=dashed] + text_placement_info -> text_placement_info [label="next()"] text_symbolizer_properties -> text_processor [label="processor"] - text_path -> Renderer [label="used by"] + text_processor -> processed_text [label="process()", style=dashed] + processed_text -> string_info [label="get_string_info()"] + text_path -> Renderer [color=red, label="used by"] Renderer -> text_placement_info [color=red, label="init()"] + Renderer -> processed_text [color=red, label="initializes"] -} \ No newline at end of file +} diff --git a/include/mapnik/text_processing.hpp b/include/mapnik/text_processing.hpp index 512282b00..450acb0bb 100644 --- a/include/mapnik/text_processing.hpp +++ b/include/mapnik/text_processing.hpp @@ -52,7 +52,9 @@ DEFINE_ENUM( text_transform_e, text_transform ); struct char_properties { char_properties(); + /** Construct object from XML. */ void set_values_from_xml(boost::property_tree::ptree const &sym, std::map const & fontsets); + /** Write object to XML ptree. */ void to_xml(boost::property_tree::ptree &node, bool explicit_defaults, char_properties const &dfl=char_properties()) const; std::string face_name; font_set fontset; @@ -75,7 +77,6 @@ public: p(properties), str(text) {} char_properties p; UnicodeString str; - }; class processed_text @@ -113,10 +114,13 @@ public: * The output object has to be created by the caller and passed in for thread safety. */ void process(processed_text &output, Feature const& feature); + /** Automatically create processing instructions for a single expression. */ void set_old_style_expression(expression_ptr expr); /** Add a new formating token. */ void push_back(abstract_token *token); + /** Get a list of all expressions used in any placement. This function is used to collect attributes. */ std::set get_all_expressions() const; + /** Default values for char_properties. */ char_properties defaults; protected: void from_xml_recursive(boost::property_tree::ptree const& pt, std::map const &fontsets); From cd5c1c6ab4e127213ddad27852b102c02fa2f315 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Wed, 25 Jan 2012 23:26:39 +0100 Subject: [PATCH 60/81] Add reference image for formating test. --- tests/data/placement/formating-500-reference.png | Bin 0 -> 8139 bytes tests/data/placement/formating.xml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 tests/data/placement/formating-500-reference.png diff --git a/tests/data/placement/formating-500-reference.png b/tests/data/placement/formating-500-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..0ab02b598955b5f4ab0ff0b001234dc24332d7bd GIT binary patch literal 8139 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 N6$Q - + [name]+' ''('+[name]+')' From 51d87187010c448ae21cf35876caac02b74a7fba Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Wed, 25 Jan 2012 23:31:07 +0100 Subject: [PATCH 61/81] Move tests to new location. --- tests/{data/placement => visual_tests}/clean.sh | 0 .../formating-500-reference.png | Bin .../{data/placement => visual_tests}/formating.xml | 0 .../list-100-reference.png | Bin .../list-150-reference.png | Bin .../list-200-reference.png | Bin .../list-250-reference.png | Bin .../list-300-reference.png | Bin .../list-400-reference.png | Bin .../list-600-reference.png | Bin .../list-800-reference.png | Bin tests/{data/placement => visual_tests}/list.xml | 0 tests/{data/placement => visual_tests}/points.dbf | Bin tests/{data/placement => visual_tests}/points.osm | 0 tests/{data/placement => visual_tests}/points.shp | Bin .../simple-100-reference.png | Bin .../simple-150-reference.png | Bin .../simple-200-reference.png | Bin .../simple-250-reference.png | Bin .../simple-300-reference.png | Bin .../simple-400-reference.png | Bin .../simple-600-reference.png | Bin .../simple-800-reference.png | Bin .../simple-E-500-reference.png | Bin tests/{data/placement => visual_tests}/simple-E.xml | 0 .../simple-N-500-reference.png | Bin tests/{data/placement => visual_tests}/simple-N.xml | 0 .../simple-NE-500-reference.png | Bin .../{data/placement => visual_tests}/simple-NE.xml | 0 .../simple-NW-500-reference.png | Bin .../{data/placement => visual_tests}/simple-NW.xml | 0 .../simple-S-500-reference.png | Bin tests/{data/placement => visual_tests}/simple-S.xml | 0 .../simple-SE-500-reference.png | Bin .../{data/placement => visual_tests}/simple-SE.xml | 0 .../simple-SW-500-reference.png | Bin .../{data/placement => visual_tests}/simple-SW.xml | 0 .../simple-W-500-reference.png | Bin tests/{data/placement => visual_tests}/simple-W.xml | 0 tests/{data/placement => visual_tests}/simple.xml | 0 tests/{data/placement => visual_tests}/test.py | 0 41 files changed, 0 insertions(+), 0 deletions(-) rename tests/{data/placement => visual_tests}/clean.sh (100%) rename tests/{data/placement => visual_tests}/formating-500-reference.png (100%) rename tests/{data/placement => visual_tests}/formating.xml (100%) rename tests/{data/placement => visual_tests}/list-100-reference.png (100%) rename tests/{data/placement => visual_tests}/list-150-reference.png (100%) rename tests/{data/placement => visual_tests}/list-200-reference.png (100%) rename tests/{data/placement => visual_tests}/list-250-reference.png (100%) rename tests/{data/placement => visual_tests}/list-300-reference.png (100%) rename tests/{data/placement => visual_tests}/list-400-reference.png (100%) rename tests/{data/placement => visual_tests}/list-600-reference.png (100%) rename tests/{data/placement => visual_tests}/list-800-reference.png (100%) rename tests/{data/placement => visual_tests}/list.xml (100%) rename tests/{data/placement => visual_tests}/points.dbf (100%) rename tests/{data/placement => visual_tests}/points.osm (100%) rename tests/{data/placement => visual_tests}/points.shp (100%) rename tests/{data/placement => visual_tests}/simple-100-reference.png (100%) rename tests/{data/placement => visual_tests}/simple-150-reference.png (100%) rename tests/{data/placement => visual_tests}/simple-200-reference.png (100%) rename tests/{data/placement => visual_tests}/simple-250-reference.png (100%) rename tests/{data/placement => visual_tests}/simple-300-reference.png (100%) rename tests/{data/placement => visual_tests}/simple-400-reference.png (100%) rename tests/{data/placement => visual_tests}/simple-600-reference.png (100%) rename tests/{data/placement => visual_tests}/simple-800-reference.png (100%) rename tests/{data/placement => visual_tests}/simple-E-500-reference.png (100%) rename tests/{data/placement => visual_tests}/simple-E.xml (100%) rename tests/{data/placement => visual_tests}/simple-N-500-reference.png (100%) rename tests/{data/placement => visual_tests}/simple-N.xml (100%) rename tests/{data/placement => visual_tests}/simple-NE-500-reference.png (100%) rename tests/{data/placement => visual_tests}/simple-NE.xml (100%) rename tests/{data/placement => visual_tests}/simple-NW-500-reference.png (100%) rename tests/{data/placement => visual_tests}/simple-NW.xml (100%) rename tests/{data/placement => visual_tests}/simple-S-500-reference.png (100%) rename tests/{data/placement => visual_tests}/simple-S.xml (100%) rename tests/{data/placement => visual_tests}/simple-SE-500-reference.png (100%) rename tests/{data/placement => visual_tests}/simple-SE.xml (100%) rename tests/{data/placement => visual_tests}/simple-SW-500-reference.png (100%) rename tests/{data/placement => visual_tests}/simple-SW.xml (100%) rename tests/{data/placement => visual_tests}/simple-W-500-reference.png (100%) rename tests/{data/placement => visual_tests}/simple-W.xml (100%) rename tests/{data/placement => visual_tests}/simple.xml (100%) rename tests/{data/placement => visual_tests}/test.py (100%) diff --git a/tests/data/placement/clean.sh b/tests/visual_tests/clean.sh similarity index 100% rename from tests/data/placement/clean.sh rename to tests/visual_tests/clean.sh diff --git a/tests/data/placement/formating-500-reference.png b/tests/visual_tests/formating-500-reference.png similarity index 100% rename from tests/data/placement/formating-500-reference.png rename to tests/visual_tests/formating-500-reference.png diff --git a/tests/data/placement/formating.xml b/tests/visual_tests/formating.xml similarity index 100% rename from tests/data/placement/formating.xml rename to tests/visual_tests/formating.xml diff --git a/tests/data/placement/list-100-reference.png b/tests/visual_tests/list-100-reference.png similarity index 100% rename from tests/data/placement/list-100-reference.png rename to tests/visual_tests/list-100-reference.png diff --git a/tests/data/placement/list-150-reference.png b/tests/visual_tests/list-150-reference.png similarity index 100% rename from tests/data/placement/list-150-reference.png rename to tests/visual_tests/list-150-reference.png diff --git a/tests/data/placement/list-200-reference.png b/tests/visual_tests/list-200-reference.png similarity index 100% rename from tests/data/placement/list-200-reference.png rename to tests/visual_tests/list-200-reference.png diff --git a/tests/data/placement/list-250-reference.png b/tests/visual_tests/list-250-reference.png similarity index 100% rename from tests/data/placement/list-250-reference.png rename to tests/visual_tests/list-250-reference.png diff --git a/tests/data/placement/list-300-reference.png b/tests/visual_tests/list-300-reference.png similarity index 100% rename from tests/data/placement/list-300-reference.png rename to tests/visual_tests/list-300-reference.png diff --git a/tests/data/placement/list-400-reference.png b/tests/visual_tests/list-400-reference.png similarity index 100% rename from tests/data/placement/list-400-reference.png rename to tests/visual_tests/list-400-reference.png diff --git a/tests/data/placement/list-600-reference.png b/tests/visual_tests/list-600-reference.png similarity index 100% rename from tests/data/placement/list-600-reference.png rename to tests/visual_tests/list-600-reference.png diff --git a/tests/data/placement/list-800-reference.png b/tests/visual_tests/list-800-reference.png similarity index 100% rename from tests/data/placement/list-800-reference.png rename to tests/visual_tests/list-800-reference.png diff --git a/tests/data/placement/list.xml b/tests/visual_tests/list.xml similarity index 100% rename from tests/data/placement/list.xml rename to tests/visual_tests/list.xml diff --git a/tests/data/placement/points.dbf b/tests/visual_tests/points.dbf similarity index 100% rename from tests/data/placement/points.dbf rename to tests/visual_tests/points.dbf diff --git a/tests/data/placement/points.osm b/tests/visual_tests/points.osm similarity index 100% rename from tests/data/placement/points.osm rename to tests/visual_tests/points.osm diff --git a/tests/data/placement/points.shp b/tests/visual_tests/points.shp similarity index 100% rename from tests/data/placement/points.shp rename to tests/visual_tests/points.shp diff --git a/tests/data/placement/simple-100-reference.png b/tests/visual_tests/simple-100-reference.png similarity index 100% rename from tests/data/placement/simple-100-reference.png rename to tests/visual_tests/simple-100-reference.png diff --git a/tests/data/placement/simple-150-reference.png b/tests/visual_tests/simple-150-reference.png similarity index 100% rename from tests/data/placement/simple-150-reference.png rename to tests/visual_tests/simple-150-reference.png diff --git a/tests/data/placement/simple-200-reference.png b/tests/visual_tests/simple-200-reference.png similarity index 100% rename from tests/data/placement/simple-200-reference.png rename to tests/visual_tests/simple-200-reference.png diff --git a/tests/data/placement/simple-250-reference.png b/tests/visual_tests/simple-250-reference.png similarity index 100% rename from tests/data/placement/simple-250-reference.png rename to tests/visual_tests/simple-250-reference.png diff --git a/tests/data/placement/simple-300-reference.png b/tests/visual_tests/simple-300-reference.png similarity index 100% rename from tests/data/placement/simple-300-reference.png rename to tests/visual_tests/simple-300-reference.png diff --git a/tests/data/placement/simple-400-reference.png b/tests/visual_tests/simple-400-reference.png similarity index 100% rename from tests/data/placement/simple-400-reference.png rename to tests/visual_tests/simple-400-reference.png diff --git a/tests/data/placement/simple-600-reference.png b/tests/visual_tests/simple-600-reference.png similarity index 100% rename from tests/data/placement/simple-600-reference.png rename to tests/visual_tests/simple-600-reference.png diff --git a/tests/data/placement/simple-800-reference.png b/tests/visual_tests/simple-800-reference.png similarity index 100% rename from tests/data/placement/simple-800-reference.png rename to tests/visual_tests/simple-800-reference.png diff --git a/tests/data/placement/simple-E-500-reference.png b/tests/visual_tests/simple-E-500-reference.png similarity index 100% rename from tests/data/placement/simple-E-500-reference.png rename to tests/visual_tests/simple-E-500-reference.png diff --git a/tests/data/placement/simple-E.xml b/tests/visual_tests/simple-E.xml similarity index 100% rename from tests/data/placement/simple-E.xml rename to tests/visual_tests/simple-E.xml diff --git a/tests/data/placement/simple-N-500-reference.png b/tests/visual_tests/simple-N-500-reference.png similarity index 100% rename from tests/data/placement/simple-N-500-reference.png rename to tests/visual_tests/simple-N-500-reference.png diff --git a/tests/data/placement/simple-N.xml b/tests/visual_tests/simple-N.xml similarity index 100% rename from tests/data/placement/simple-N.xml rename to tests/visual_tests/simple-N.xml diff --git a/tests/data/placement/simple-NE-500-reference.png b/tests/visual_tests/simple-NE-500-reference.png similarity index 100% rename from tests/data/placement/simple-NE-500-reference.png rename to tests/visual_tests/simple-NE-500-reference.png diff --git a/tests/data/placement/simple-NE.xml b/tests/visual_tests/simple-NE.xml similarity index 100% rename from tests/data/placement/simple-NE.xml rename to tests/visual_tests/simple-NE.xml diff --git a/tests/data/placement/simple-NW-500-reference.png b/tests/visual_tests/simple-NW-500-reference.png similarity index 100% rename from tests/data/placement/simple-NW-500-reference.png rename to tests/visual_tests/simple-NW-500-reference.png diff --git a/tests/data/placement/simple-NW.xml b/tests/visual_tests/simple-NW.xml similarity index 100% rename from tests/data/placement/simple-NW.xml rename to tests/visual_tests/simple-NW.xml diff --git a/tests/data/placement/simple-S-500-reference.png b/tests/visual_tests/simple-S-500-reference.png similarity index 100% rename from tests/data/placement/simple-S-500-reference.png rename to tests/visual_tests/simple-S-500-reference.png diff --git a/tests/data/placement/simple-S.xml b/tests/visual_tests/simple-S.xml similarity index 100% rename from tests/data/placement/simple-S.xml rename to tests/visual_tests/simple-S.xml diff --git a/tests/data/placement/simple-SE-500-reference.png b/tests/visual_tests/simple-SE-500-reference.png similarity index 100% rename from tests/data/placement/simple-SE-500-reference.png rename to tests/visual_tests/simple-SE-500-reference.png diff --git a/tests/data/placement/simple-SE.xml b/tests/visual_tests/simple-SE.xml similarity index 100% rename from tests/data/placement/simple-SE.xml rename to tests/visual_tests/simple-SE.xml diff --git a/tests/data/placement/simple-SW-500-reference.png b/tests/visual_tests/simple-SW-500-reference.png similarity index 100% rename from tests/data/placement/simple-SW-500-reference.png rename to tests/visual_tests/simple-SW-500-reference.png diff --git a/tests/data/placement/simple-SW.xml b/tests/visual_tests/simple-SW.xml similarity index 100% rename from tests/data/placement/simple-SW.xml rename to tests/visual_tests/simple-SW.xml diff --git a/tests/data/placement/simple-W-500-reference.png b/tests/visual_tests/simple-W-500-reference.png similarity index 100% rename from tests/data/placement/simple-W-500-reference.png rename to tests/visual_tests/simple-W-500-reference.png diff --git a/tests/data/placement/simple-W.xml b/tests/visual_tests/simple-W.xml similarity index 100% rename from tests/data/placement/simple-W.xml rename to tests/visual_tests/simple-W.xml diff --git a/tests/data/placement/simple.xml b/tests/visual_tests/simple.xml similarity index 100% rename from tests/data/placement/simple.xml rename to tests/visual_tests/simple.xml diff --git a/tests/data/placement/test.py b/tests/visual_tests/test.py similarity index 100% rename from tests/data/placement/test.py rename to tests/visual_tests/test.py From 1bd3e3678c0e39262e3a895f35155e96e1bf1882 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Thu, 26 Jan 2012 18:12:16 +0100 Subject: [PATCH 62/81] Really use filtered geometries. --- include/mapnik/symbolizer_helpers.hpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/include/mapnik/symbolizer_helpers.hpp b/include/mapnik/symbolizer_helpers.hpp index e471bb026..22168474a 100644 --- a/include/mapnik/symbolizer_helpers.hpp +++ b/include/mapnik/symbolizer_helpers.hpp @@ -111,12 +111,10 @@ text_placement_info_ptr text_symbolizer_helper::get_pla } placement_finder finder(*placement, info, detector_, dims); - unsigned num_geom = feature.num_geometries(); - for (unsigned i=0; inum_points() == 0) continue; // don't bother with empty geometries + finder.find_placement(angle, *geom, t_, prj_trans); if (!placement->placements.size()) continue; if (writer.first) writer.first->add_text(*placement, font_manager_, feature, t_, writer.second); From dbc1280731ee694d26b15f647861918a22ebd4aa Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Thu, 26 Jan 2012 19:58:47 +0100 Subject: [PATCH 63/81] Process more than one geometry. Start work on ShieldSymbolizer. --- include/mapnik/symbolizer_helpers.hpp | 271 ++++++++++++++++++-------- src/agg/process_shield_symbolizer.cpp | 88 --------- src/agg/process_text_symbolizer.cpp | 31 +-- src/cairo_renderer.cpp | 16 +- src/grid/process_text_symbolizer.cpp | 26 +-- 5 files changed, 225 insertions(+), 207 deletions(-) diff --git a/include/mapnik/symbolizer_helpers.hpp b/include/mapnik/symbolizer_helpers.hpp index 22168474a..c389609a0 100644 --- a/include/mapnik/symbolizer_helpers.hpp +++ b/include/mapnik/symbolizer_helpers.hpp @@ -23,10 +23,13 @@ #define SYMBOLIZER_HELPERS_HPP #include +#include #include #include #include #include +#include +#include #include @@ -37,7 +40,150 @@ template class text_symbolizer_helper { public: - text_symbolizer_helper(unsigned width, + text_symbolizer_helper(text_symbolizer const& sym, + Feature const& feature, + proj_transform const& prj_trans, + unsigned width, + unsigned height, + double scale_factor, + CoordTransform const &t, + FaceManagerT &font_manager, + DetectorT &detector) : + sym_(sym), + feature_(feature), + prj_trans_(prj_trans), + width_(width), + height_(height), + scale_factor_(scale_factor), + t_(t), + font_manager_(font_manager), + detector_(detector), + text_(), + angle_(0.0) + { + initialize_geometries(); + if (!geometries_to_process_.size()) return; + text_ = boost::shared_ptr(new processed_text(font_manager_, scale_factor_)); + placement_ = sym_.get_placement_options()->get_placement_info(); + placement_->init(scale_factor_, width_, height_); + metawriter_with_properties writer = sym_.get_metawriter(); + if (writer.first) + placement_->collect_extents = true; + itr_ = geometries_to_process_.begin(); + next_placement(); + } + + bool next_placement() + { + if (!placement_->next()) return false; + /* TODO: Simplify this. */ + text_->clear(); + placement_->properties.processor.process(*text_, feature_); + info_ = &(text_->get_string_info()); + if (placement_->properties.orientation) + { + angle_ = boost::apply_visitor( + evaluate(feature_), + *(placement_->properties.orientation)).to_double(); + } else { + angle_ = 0.0; + } + /* END TODO */ + return true; + } + + /** Return next placement. + * If no more placements are found returns null pointer. + * TODO: Currently stops returning placements to early. + */ + text_placement_info_ptr get_placement(); +private: + void initialize_geometries(); + + text_symbolizer const& sym_; + Feature const& feature_; + proj_transform const& prj_trans_; + unsigned width_; + unsigned height_; + double scale_factor_; + CoordTransform const &t_; + FaceManagerT &font_manager_; + DetectorT &detector_; + boost::shared_ptr text_; /*TODO: Use shared pointers for text placement so we don't need to keep a reference here! */ + std::vector geometries_to_process_; + std::vector::iterator itr_; + text_placement_info_ptr placement_; + double angle_; + string_info *info_; +}; + + +template +text_placement_info_ptr text_symbolizer_helper::get_placement() +{ + if (!geometries_to_process_.size()) return text_placement_info_ptr(); + metawriter_with_properties writer = sym_.get_metawriter(); + box2d dims(0, 0, width_, height_); + + while (geometries_to_process_.size()) + { + if (itr_ == geometries_to_process_.end()) { + //Just processed the last geometry. Try next placement. + if (!next_placement()) return text_placement_info_ptr(); //No more placements + //Start again from begin of list + itr_ = geometries_to_process_.begin(); + } + //TODO: Avoid calling constructor repeatedly + placement_finder finder(*placement_, *info_, detector_, dims); + finder.find_placement(angle_, **itr_, t_, prj_trans_); + if (placement_->placements.size()) + { + //Found a placement + geometries_to_process_.erase(itr_); //Remove current geometry + if (writer.first) writer.first->add_text(*placement_, font_manager_, feature_, t_, writer.second); + itr_++; + return placement_; + } + //No placement for this geometry. Keep it in geometries_to_process_ for next try. + itr_++; + + } + return text_placement_info_ptr(); +} + +template +void text_symbolizer_helper::initialize_geometries() +{ + unsigned num_geom = feature_.num_geometries(); + for (unsigned i=0; i 0) + { + // TODO - find less costly method than fetching full envelope + box2d gbox = t_.forward(geom.envelope(), prj_trans_); + if (gbox.width() < sym_.get_minimum_path_length()) + { + continue; + } + } + // TODO - calculate length here as well + geometries_to_process_.push_back(const_cast(&geom)); + } +} + + +/*****************************************************************************/ + +template +class shield_symbolizer_helper +{ +public: + shield_symbolizer_helper(unsigned width, unsigned height, double scale_factor, CoordTransform const &t, @@ -54,110 +200,71 @@ public: } - text_placement_info_ptr get_placement(text_symbolizer const& sym, + text_placement_info_ptr get_placement(shield_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans); private: - bool initialize_geometries(text_symbolizer const& sym, - Feature const& feature, - proj_transform const& prj_trans); - + void init_marker(shield_symbolizer const& sym, Feature const& feature); unsigned width_; unsigned height_; double scale_factor_; CoordTransform const &t_; FaceManagerT &font_manager_; DetectorT &detector_; - boost::shared_ptr text_; /*TODO: Use shared pointers for text placement so we don't need to keep a reference here! */ - // Use a boost::ptr_vector here instread of std::vector? - std::vector geometries_to_process_; + boost::shared_ptr text_; + box2d label_ext_; + boost::optional marker_; + agg::trans_affine transform_; + int marker_w; + int marker_h; }; template -text_placement_info_ptr text_symbolizer_helper::get_placement( - text_symbolizer const& sym, +text_placement_info_ptr shield_symbolizer_helper::get_placement( + shield_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { - if (!initialize_geometries(sym, feature, prj_trans)) return text_placement_info_ptr(); - - text_ = boost::shared_ptr(new processed_text(font_manager_, scale_factor_)); - metawriter_with_properties writer = sym.get_metawriter(); - - box2d dims(0, 0, width_, height_); - - text_placement_info_ptr placement = sym.get_placement_options()->get_placement_info(); - placement->init(scale_factor_, width_, height_); - if (writer.first) - placement->collect_extents = true; - - unsigned num_geom = feature.num_geometries(); - if (!num_geom) return text_placement_info_ptr(); //Nothing to do - - while (placement->next()) - { - text_processor &processor = placement->properties.processor; - text_symbolizer_properties const& p = placement->properties; - /* TODO: Simplify this. */ - text_->clear(); - processor.process(*text_, feature); - string_info &info = text_->get_string_info(); - /* END TODO */ - double angle = 0.0; - if (p.orientation) - { - angle = boost::apply_visitor(evaluate(feature),*(p.orientation)).to_double(); - } - placement_finder finder(*placement, info, detector_, dims); - - BOOST_FOREACH(geometry_type * geom, geometries_to_process_) - { - if (geom->num_points() == 0) continue; // don't bother with empty geometries - finder.find_placement(angle, *geom, t_, prj_trans); - if (!placement->placements.size()) - continue; - if (writer.first) writer.first->add_text(*placement, font_manager_, feature, t_, writer.second); - return placement; - } - } - return text_placement_info_ptr(); + init_marker(sym); + if (!marker_) return text_placement_info_ptr(); } template -bool text_symbolizer_helper::initialize_geometries( - text_symbolizer const& sym, - Feature const& feature, - proj_transform const& prj_trans) +void shield_symbolizer_helper::init_marker(shield_symbolizer const& sym, Feature const& feature) { - unsigned num_geom = feature.num_geometries(); - for (unsigned i=0; i const& m = sym.get_transform(); + transform_.load_from(&m[0]); + marker_.reset(); + if (!filename.empty()) { - geometry_type const& geom = feature.get_geometry(i); - - // don't bother with empty geometries - if (geom.num_points() == 0) continue; - - if ((geom.type() == Polygon) && sym.get_minimum_path_length() > 0) - { - // TODO - find less costly method than fetching full envelope - box2d gbox = t_.forward(geom.envelope(),prj_trans); - if (gbox.width() < sym.get_minimum_path_length()) - { - continue; - } - } - // TODO - calculate length here as well - geometries_to_process_.push_back(const_cast(&geom)); + marker_ = marker_cache::instance()->find(filename, true); } - - if (!geometries_to_process_.size() > 0) - { - // early return to avoid significant overhead of rendering setup - return false; + if (!marker_) { + marker_w = 0; + marker_h = 0; + label_ext_.init(0, 0, 0, 0); + return; } - return true; + 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; + double px2 = px1; + double py2 = py0; + double px3 = px0; + double py3 = py1; + transform_.transform(&px0,&py0); + transform_.transform(&px1,&py1); + transform_.transform(&px2,&py2); + transform_.transform(&px3,&py3); + label_ext_.init(px0, py0, px1, py1); + label_ext_.expand_to_include(px2, py2); + label_ext_.expand_to_include(px3, py3); } -} +} //namespace #endif // SYMBOLIZER_HELPERS_HPP diff --git a/src/agg/process_shield_symbolizer.cpp b/src/agg/process_shield_symbolizer.cpp index fc74ea2ad..ccb7f6028 100644 --- a/src/agg/process_shield_symbolizer.cpp +++ b/src/agg/process_shield_symbolizer.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -46,95 +45,8 @@ void agg_renderer::process(shield_symbolizer const& sym, proj_transform const& prj_trans) { #if 0 - typedef coord_transform2 path_type; - - - text_placement_info_ptr placement_options = sym.get_placement_options()->get_placement_info(); - placement_options->next(); - placement_options->next_position_only(); - - UnicodeString text; - if( sym.get_no_text() ) - text = UnicodeString( " " ); // TODO: fix->use 'space' as the text to render - else - { - expression_ptr name_expr = sym.get_name(); - if (!name_expr) return; - value_type result = boost::apply_visitor(evaluate(feature),*name_expr); - text = result.to_unicode(); - } - - if ( sym.get_text_transform() == UPPERCASE) - { - text = text.toUpper(); - } - else if ( sym.get_text_transform() == LOWERCASE) - { - text = text.toLower(); - } - else if ( sym.get_text_transform() == CAPITALIZE) - { - text = text.toTitle(NULL); - } - - agg::trans_affine tr; - boost::array const& m = sym.get_transform(); - tr.load_from(&m[0]); - - std::string filename = path_processor_type::evaluate( *sym.get_filename(), feature); - boost::optional marker; - if ( !filename.empty() ) - { - marker = marker_cache::instance()->find(filename, true); - } - else - { - marker.reset(boost::make_shared()); - } - - - if (text.length() > 0 && marker) - { - int w = (*marker)->width(); - int h = (*marker)->height(); - - double px0 = - 0.5 * w; - double py0 = - 0.5 * h; - double px1 = 0.5 * w; - double py1 = 0.5 * h; - double px2 = px1; - double py2 = py0; - double px3 = px0; - double py3 = py1; - tr.transform(&px0,&py0); - tr.transform(&px1,&py1); - tr.transform(&px2,&py2); - tr.transform(&px3,&py3); - box2d label_ext (px0, py0, px1, py1); - label_ext.expand_to_include(px2, py2); - label_ext.expand_to_include(px3, py3); - - face_set_ptr faces; - - if (sym.get_fontset().size() > 0) - { - faces = font_manager_.get_face_set(sym.get_fontset()); - } - else - { - faces = font_manager_.get_face_set(sym.get_face_name()); - } - - stroker_ptr strk = font_manager_.get_stroker(); - if (strk && faces->size() > 0) - { text_renderer ren(pixmap_, faces, *strk); - ren.set_character_size(sym.get_text_size() * scale_factor_); - ren.set_fill(sym.get_fill()); - ren.set_halo_fill(sym.get_halo_fill()); - ren.set_halo_radius(sym.get_halo_radius() * scale_factor_); - ren.set_opacity(sym.get_text_opacity()); placement_finder finder(*detector_); diff --git a/src/agg/process_text_symbolizer.cpp b/src/agg/process_text_symbolizer.cpp index fd857a576..d693c15e7 100644 --- a/src/agg/process_text_symbolizer.cpp +++ b/src/agg/process_text_symbolizer.cpp @@ -33,23 +33,24 @@ void agg_renderer::process(text_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { - /* This could also be a member of the renderer class, but I would have - to check if any of the variables changes and notify the helper. - It could be done at a later point, but for now keep the code simple. - */ - text_symbolizer_helper, label_collision_detector4> helper(width_, height_, scale_factor_, t_, font_manager_, *detector_); - - text_placement_info_ptr placement = helper.get_placement(sym, feature, prj_trans); - - if (!placement) return; + text_symbolizer_helper, + label_collision_detector4> helper( + sym, feature, prj_trans, + width_, height_, + scale_factor_, + t_, font_manager_, *detector_); text_renderer ren(pixmap_, font_manager_, *(font_manager_.get_stroker())); - for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) - { - double x = placement->placements[ii].starting_x; - double y = placement->placements[ii].starting_y; - ren.prepare_glyphs(&(placement->placements[ii])); - ren.render(x, y); + + text_placement_info_ptr placement; + while ((placement = helper.get_placement())) { + for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) + { + double x = placement->placements[ii].starting_x; + double y = placement->placements[ii].starting_y; + ren.prepare_glyphs(&(placement->placements[ii])); + ren.render(x, y); + } } } diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 78b25a031..1d060d47d 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -1435,17 +1435,15 @@ void cairo_renderer_base::process(text_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { - text_symbolizer_helper, label_collision_detector4> helper(detector_.extent().width(), detector_.extent().height(), 1.0 /*scale_factor*/, t_, font_manager_, detector_); - - text_placement_info_ptr placement = helper.get_placement(sym, feature, prj_trans); - - if (!placement) return; + text_symbolizer_helper, label_collision_detector4> helper(sym, feature, prj_trans, detector_.extent().width(), detector_.extent().height(), 1.0 /*scale_factor*/, t_, font_manager_, detector_); cairo_context context(context_); - - for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) - { - context.add_text(placement->placements[ii], face_manager_, font_manager_); + text_placement_info_ptr placement; + while ((placement = helper.get_placement())) { + for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) + { + context.add_text(placement->placements[ii], face_manager_, font_manager_); + } } } diff --git a/src/grid/process_text_symbolizer.cpp b/src/grid/process_text_symbolizer.cpp index 527196d36..041106975 100644 --- a/src/grid/process_text_symbolizer.cpp +++ b/src/grid/process_text_symbolizer.cpp @@ -23,8 +23,6 @@ // mapnik #include -#include -#include #include namespace mapnik { @@ -36,23 +34,25 @@ void grid_renderer::process(text_symbolizer const& sym, { text_symbolizer_helper, label_collision_detector4> helper( + sym, feature, prj_trans, width_, height_, scale_factor_ * (1.0/pixmap_.get_resolution()), t_, font_manager_, detector_); - - text_placement_info_ptr placement = helper.get_placement(sym, feature, prj_trans); - - if (!placement) return; + bool placement_found = false; text_renderer ren(pixmap_, font_manager_, *(font_manager_.get_stroker())); - for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) - { - double x = placement->placements[ii].starting_x; - double y = placement->placements[ii].starting_y; - ren.prepare_glyphs(&(placement->placements[ii])); - ren.render_id(feature.id(),x,y,2); + + text_placement_info_ptr placement; + while ((placement = helper.get_placement())) { + for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) + { + double x = placement->placements[ii].starting_x; + double y = placement->placements[ii].starting_y; + ren.prepare_glyphs(&(placement->placements[ii])); + ren.render_id(feature.id(),x,y,2); + } } - pixmap_.add_feature(feature); + if (placement_found) pixmap_.add_feature(feature); } From 92ca0b74eda3f55ec8022e2a8ceb51d39078992e Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 27 Jan 2012 21:08:28 +0100 Subject: [PATCH 64/81] Update clean.sh. --- tests/visual_tests/clean.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/visual_tests/clean.sh b/tests/visual_tests/clean.sh index a6f192b13..eebdaf882 100755 --- a/tests/visual_tests/clean.sh +++ b/tests/visual_tests/clean.sh @@ -1,5 +1,6 @@ rm -f list-[0-9][0-9]0-agg.png rm -f simple-[0-9][0-9]0-agg.png rm -f simple-{E,N,NE,NW,N,SE,SW,S,W}-500-agg.png +rm -f formating-500-agg.png rm -f list-out.xml simple-out.xml From d6b8209a9defb0720ade723e432f15dddd2eb4aa Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 27 Jan 2012 23:13:07 +0100 Subject: [PATCH 65/81] Rewrote text processing to use a tree structure. Reduces the number of classes and simplifies extension. --- include/mapnik/text_processing.hpp | 105 +++++- src/text_processing.cpp | 499 +++++++++++++++-------------- 2 files changed, 345 insertions(+), 259 deletions(-) diff --git a/include/mapnik/text_processing.hpp b/include/mapnik/text_processing.hpp index 450acb0bb..9c9306c05 100644 --- a/include/mapnik/text_processing.hpp +++ b/include/mapnik/text_processing.hpp @@ -46,9 +46,9 @@ enum text_transform CAPITALIZE, text_transform_MAX }; - DEFINE_ENUM( text_transform_e, text_transform ); + struct char_properties { char_properties(); @@ -79,7 +79,8 @@ public: UnicodeString str; }; -class processed_text + +class processed_text : boost::noncopyable { public: processed_text(face_manager & font_manager, double scale_factor); @@ -88,8 +89,8 @@ public: unsigned empty() const { return expr_list_.empty(); } void clear(); typedef std::list expression_list; - expression_list::const_iterator begin(); - expression_list::const_iterator end(); + expression_list::const_iterator begin() const; + expression_list::const_iterator end() const; string_info &get_string_info(); private: expression_list expr_list_; @@ -98,7 +99,86 @@ private: string_info info_; }; -class abstract_token; + +namespace formating { +class node; +typedef boost::shared_ptr node_ptr; +class node +{ +public: + virtual ~node() {} + virtual void to_xml(boost::property_tree::ptree &xml) const; + static node_ptr from_xml(boost::property_tree::ptree const& xml); + virtual void apply(char_properties const& p, Feature const& feature, processed_text &output) const = 0; + virtual void add_expressions(std::set &expressions) const; +}; + +class 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 const& feature, processed_text &output) const; + virtual void add_expressions(std::set &expressions) const; + + void push_back(node_ptr n); + void set_children(std::vector const& children); + std::vector const& get_children() const; + void clear(); +private: + std::vector children_; +}; + +class text_node: public node { +public: + text_node(expression_ptr text): node(), text_(text) {} + void to_xml(boost::property_tree::ptree &xml) const; + static node_ptr from_xml(boost::property_tree::ptree const& xml); + virtual void apply(char_properties const& p, Feature const& feature, processed_text &output) const; + virtual void add_expressions(std::set &expressions) const; + + void set_text(expression_ptr text); + expression_ptr get_text() const; +private: + expression_ptr text_; +}; + +class format_node: public node { +public: + format_node(); + void to_xml(boost::property_tree::ptree &xml) const; + static node_ptr from_xml(boost::property_tree::ptree const& xml); + virtual void apply(char_properties const& p, Feature const& feature, processed_text &output) const; + + void set_child(node_ptr child); + node_ptr get_child() const; + + void set_face_name(boost::optional face_name); + void set_text_size(boost::optional text_size); + void set_character_spacing(boost::optional character_spacing); + void set_line_spacing(boost::optional line_spacing); + void set_text_opacity(boost::optional opacity); + void set_wrap_before(boost::optional wrap_before); + void set_wrap_char(boost::optional wrap_char); + void set_text_transform(boost::optional text_trans); + void set_fill(boost::optional fill); + void set_halo_fill(boost::optional halo_fill); + void set_halo_radius(boost::optional radius); +private: + boost::optional face_name_; + boost::optional text_size_; + boost::optional character_spacing_; + boost::optional line_spacing_; + boost::optional text_opacity_; + boost::optional wrap_before_; + boost::optional wrap_char_; + boost::optional text_transform_; + boost::optional fill_; + boost::optional halo_fill_; + boost::optional halo_radius_; + node_ptr child_; +}; + +} //namespace formating /** Stores formating information and uses this to produce formated text for a given feature. */ class text_processor @@ -113,22 +193,21 @@ public: /** 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 const& feature); + void process(processed_text &output, Feature const& feature) const; /** Automatically create processing instructions for a single expression. */ void set_old_style_expression(expression_ptr expr); - /** Add a new formating token. */ - void push_back(abstract_token *token); + /** Sets new format tree. */ + void set_format_tree(formating::node_ptr tree); + /** Get format tree. */ + formating::node_ptr get_format_tree() const; /** Get a list of all expressions used in any placement. This function is used to collect attributes. */ std::set get_all_expressions() const; /** Default values for char_properties. */ char_properties defaults; -protected: - void from_xml_recursive(boost::property_tree::ptree const& pt, std::map const &fontsets); private: - std::list list_; - bool clear_on_write; //Clear list once + formating::node_ptr tree_; }; -} /* namespace */ +} /* namespace mapnik*/ #endif diff --git a/src/text_processing.cpp b/src/text_processing.cpp index 7d616ff18..5bd389e1e 100644 --- a/src/text_processing.cpp +++ b/src/text_processing.cpp @@ -25,371 +25,378 @@ #include #include #include -#include #include +#include #include #include +#include #include +#include namespace mapnik { using boost::property_tree::ptree; using boost::optional; -class abstract_token -{ -public: - virtual ~abstract_token() {} - virtual ptree *to_xml(ptree *node) = 0; -}; +namespace formating { -class abstract_formating_token : public abstract_token +void node::to_xml(boost::property_tree::ptree &xml) const { -public: - virtual void apply(char_properties &p, Feature const& feature) = 0; -}; + //TODO: Should this throw a config_error? +#ifdef MAPNIK_DEBUG + std::cerr << "Error: Trying to write unsupported node type to XML.\n"; +#endif +} -class abstract_text_token : public abstract_token +node_ptr node::from_xml(boost::property_tree::ptree const& xml) { -public: - virtual UnicodeString to_string(Feature const& feature) = 0; -}; + list_node *list = new list_node(); + node_ptr list_ptr(list); + ptree::const_iterator itr = xml.begin(); + ptree::const_iterator end = xml.end(); + for (; itr != end; ++itr) { + node_ptr n; + if (itr->first == "") { + n = text_node::from_xml(itr->second); + } else if (itr->first == "Format") { + n = format_node::from_xml(itr->second); + } else if (itr->first != "" && itr->first != "" && itr->first != "Placement") { + throw config_error("Unknown item " + itr->first); + } + if (n) list->push_back(n); + } + if (list->get_children().size() == 1) { + return list->get_children()[0]; + } else if (list->get_children().size() > 1) { + return list_ptr; + } else { + return node_ptr(); + } +} -class end_format_token : public abstract_token +void node::add_expressions(std::set &expressions) const { -public: - end_format_token() {} - ptree *to_xml(ptree *node); -}; - -class expression_token: public abstract_text_token -{ -public: - expression_token(expression_ptr text); - UnicodeString to_string(Feature const& feature); - ptree *to_xml(ptree *node); - void set_expression(expression_ptr text); - expression_ptr get_expression(); -private: - expression_ptr text_; -}; - -class fixed_formating_token : public abstract_formating_token -{ -public: - fixed_formating_token(); - virtual void apply(char_properties &p, Feature const& feature); - ptree* to_xml(ptree *node); - void from_xml(ptree const& node); - void set_face_name(optional face_name); - void set_text_size(optional text_size); - void set_character_spacing(optional character_spacing); - void set_line_spacing(optional line_spacing); - void set_text_opacity(optional opacity); - void set_wrap_before(optional wrap_before); - void set_wrap_char(optional wrap_char); - void set_text_transform(optional text_trans); - void set_fill(optional fill); - void set_halo_fill(optional halo_fill); - void set_halo_radius(optional radius); -private: - boost::optional face_name_; -// font_set fontset; - boost::optional text_size_; - boost::optional character_spacing_; - boost::optional line_spacing_; - boost::optional text_opacity_; - boost::optional wrap_before_; - boost::optional wrap_char_; - boost::optional text_transform_; - boost::optional fill_; - boost::optional halo_fill_; - boost::optional halo_radius_; -}; + //Do nothing by default +} /************************************************************/ -expression_token::expression_token(expression_ptr text): - text_(text) +void list_node::to_xml(boost::property_tree::ptree &xml) const { + std::vector::const_iterator itr = children_.begin(); + std::vector::const_iterator end = children_.end(); + for (;itr != end; itr++) + { + (*itr)->to_xml(xml); + } } -void expression_token::set_expression(expression_ptr text) + +void list_node::apply(char_properties const& p, Feature const& feature, processed_text &output) const +{ + std::vector::const_iterator itr = children_.begin(); + std::vector::const_iterator end = children_.end(); + for (;itr != end; itr++) + { + (*itr)->apply(p, feature, output); + } +} + + +void list_node::add_expressions(std::set &expressions) const +{ + std::vector::const_iterator itr = children_.begin(); + std::vector::const_iterator end = children_.end(); + for (;itr != end; itr++) + { + (*itr)->add_expressions(expressions); + } +} + + +void list_node::push_back(node_ptr n) +{ + children_.push_back(n); +} + + +void list_node::clear() +{ + children_.clear(); +} + +void list_node::set_children(std::vector const& children) +{ + children_ = children; +} + +std::vector const& list_node::get_children() const +{ + return children_; +} + +/************************************************************/ + +void text_node::to_xml(ptree &xml) const +{ + ptree &new_node = xml.push_back(ptree::value_type( + "", ptree()))->second; + new_node.put_value(to_expression_string(*text_)); +} + + +node_ptr text_node::from_xml(boost::property_tree::ptree const& xml) +{ + std::string data = xml.data(); + boost::trim(data); + if (data.empty()) return node_ptr(); //No text + return node_ptr(new text_node(parse_expression(data, "utf8"))); +} + +void text_node::apply(char_properties const& p, Feature const& feature, processed_text &output) const +{ + UnicodeString text_str = boost::apply_visitor(evaluate(feature), *text_).to_unicode(); + if (p.text_transform == UPPERCASE) + { + text_str = text_str.toUpper(); + } + else if (p.text_transform == LOWERCASE) + { + text_str = text_str.toLower(); + } + else if (p.text_transform == CAPITALIZE) + { + text_str = text_str.toTitle(NULL); + } + if (text_str.length() > 0) { + output.push_back(processed_expression(p, text_str)); + } else { +#ifdef MAPNIK_DEBUG + std::cerr << "Warning: Empty expression.\n"; +#endif + } +} + + +void text_node::add_expressions(std::set &expressions) const +{ + if (text_) expressions.insert(text_); +} + + +void text_node::set_text(expression_ptr text) { text_ = text; } -expression_ptr expression_token::get_expression() + +expression_ptr text_node::get_text() const { return text_; } -UnicodeString expression_token::to_string(const Feature &feature) -{ - value_type result = boost::apply_visitor(evaluate(feature), *text_); - return result.to_unicode(); -} - -ptree *expression_token::to_xml(ptree *node) -{ - ptree &new_node = node->push_back(ptree::value_type( - "", ptree()))->second; - new_node.put_value(to_expression_string(*text_)); - return &new_node; -} - /************************************************************/ -fixed_formating_token::fixed_formating_token(): - fill_() +format_node::format_node(): + node(), + fill_(), + child_() { + } -void fixed_formating_token::apply(char_properties &p, const Feature &feature) +void format_node::to_xml(ptree &xml) const { - if (face_name_) p.face_name = *face_name_; - if (text_size_) p.text_size = *text_size_; - if (character_spacing_) p.character_spacing = *character_spacing_; - if (line_spacing_) p.line_spacing = *line_spacing_; - if (text_opacity_) p.text_opacity = *text_opacity_; - if (wrap_before_) p.wrap_before = *wrap_before_; - if (wrap_char_) p.wrap_char = *wrap_char_; - if (text_transform_) p.text_transform = *text_transform_; - if (fill_) p.fill = *fill_; - if (halo_fill_) p.halo_fill = *halo_fill_; - if (halo_radius_) p.halo_radius = *halo_radius_; + ptree &new_node = xml.push_back(ptree::value_type("Format", ptree()))->second; + if (face_name_) set_attr(new_node, "face-name", *face_name_); + if (text_size_) set_attr(new_node, "size", *text_size_); + if (character_spacing_) set_attr(new_node, "character-spacing", *character_spacing_); + if (line_spacing_) set_attr(new_node, "line-spacing", *line_spacing_); + if (text_opacity_) set_attr(new_node, "opacity", *text_opacity_); + if (wrap_before_) set_attr(new_node, "wrap-before", *wrap_before_); + if (wrap_char_) set_attr(new_node, "wrap-character", *wrap_char_); + if (text_transform_) set_attr(new_node, "text-transform", *text_transform_); + if (fill_) set_attr(new_node, "fill", *fill_); + if (halo_fill_) set_attr(new_node, "halo-fill", *halo_fill_); + if (halo_radius_) set_attr(new_node, "halo-radius", *halo_radius_); + if (child_) child_->to_xml(new_node); } -ptree *fixed_formating_token::to_xml(ptree *node) -{ - ptree &new_node = node->push_back(ptree::value_type("Format", ptree()))->second; - if (face_name_) set_attr(new_node, "face-name", face_name_); - if (text_size_) set_attr(new_node, "size", text_size_); - if (character_spacing_) set_attr(new_node, "character-spacing", character_spacing_); - if (line_spacing_) set_attr(new_node, "line-spacing", line_spacing_); - if (text_opacity_) set_attr(new_node, "opacity", text_opacity_); - if (wrap_before_) set_attr(new_node, "wrap-before", wrap_before_); - if (wrap_char_) set_attr(new_node, "wrap-character", wrap_char_); - if (text_transform_) set_attr(new_node, "text-transform", text_transform_); - if (fill_) set_attr(new_node, "fill", fill_); - if (halo_fill_) set_attr(new_node, "halo-fill", halo_fill_); - if (halo_radius_) set_attr(new_node, "halo-radius", halo_radius_); - return &new_node; -} - -void fixed_formating_token::from_xml(ptree const& node) +node_ptr format_node::from_xml(ptree const& xml) { - set_face_name(get_opt_attr(node, "face-name")); + format_node *n = new format_node(); + node_ptr np(n); + + node_ptr child = node::from_xml(xml); + n->set_child(child); + + n->set_face_name(get_opt_attr(xml, "face-name")); /*TODO: Fontset is problematic. We don't have the fontsets pointer here... */ - set_text_size(get_opt_attr(node, "size")); - set_character_spacing(get_opt_attr(node, "character-spacing")); - set_line_spacing(get_opt_attr(node, "line-spacing")); - set_text_opacity(get_opt_attr(node, "opactity")); - set_wrap_before(get_opt_attr(node, "wrap-before")); - set_wrap_char(get_opt_attr(node, "wrap-character")); - set_text_transform(get_opt_attr(node, "text-transform")); - set_fill(get_opt_attr(node, "fill")); - set_halo_fill(get_opt_attr(node, "halo-fill")); - set_halo_radius(get_opt_attr(node, "halo-radius")); + n->set_text_size(get_opt_attr(xml, "size")); + n->set_character_spacing(get_opt_attr(xml, "character-spacing")); + n->set_line_spacing(get_opt_attr(xml, "line-spacing")); + n->set_text_opacity(get_opt_attr(xml, "opactity")); + boost::optional wrap = get_opt_attr(xml, "wrap-before"); + boost::optional wrap_before; + if (wrap) wrap_before = *wrap; + n->set_wrap_before(wrap_before); + n->set_wrap_char(get_opt_attr(xml, "wrap-character")); + n->set_text_transform(get_opt_attr(xml, "text-transform")); + n->set_fill(get_opt_attr(xml, "fill")); + n->set_halo_fill(get_opt_attr(xml, "halo-fill")); + n->set_halo_radius(get_opt_attr(xml, "halo-radius")); + return np; } -void fixed_formating_token::set_face_name(optional face_name) + +void format_node::apply(char_properties const& p, const Feature &feature, processed_text &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_; + + if (child_) { + child_->apply(new_properties, feature, output); + } else { +#ifdef MAPNIK_DEBUG + std::cerr << "Warning: Useless format: No text to format\n"; +#endif + } +} + + +void format_node::set_child(node_ptr child) +{ + child_ = child; +} + + +node_ptr format_node::get_child() const +{ + return child_; +} + + +void format_node::set_face_name(optional face_name) { face_name_ = face_name; } -void fixed_formating_token::set_text_size(optional text_size) +void format_node::set_text_size(optional text_size) { text_size_ = text_size; } -void fixed_formating_token::set_character_spacing(optional character_spacing) +void format_node::set_character_spacing(optional character_spacing) { character_spacing_ = character_spacing; } -void fixed_formating_token::set_line_spacing(optional line_spacing) +void format_node::set_line_spacing(optional line_spacing) { line_spacing_ = line_spacing; } -void fixed_formating_token::set_text_opacity(optional text_opacity) +void format_node::set_text_opacity(optional text_opacity) { text_opacity_ = text_opacity; } -void fixed_formating_token::set_wrap_before(optional wrap_before) +void format_node::set_wrap_before(optional wrap_before) { wrap_before_ = wrap_before; } -void fixed_formating_token::set_wrap_char(optional wrap_char) +void format_node::set_wrap_char(optional wrap_char) { wrap_char_ = wrap_char; } -void fixed_formating_token::set_text_transform(optional text_transform) +void format_node::set_text_transform(optional text_transform) { text_transform_ = text_transform; } -void fixed_formating_token::set_fill(optional c) +void format_node::set_fill(optional c) { fill_ = c; } -void fixed_formating_token::set_halo_fill(optional c) +void format_node::set_halo_fill(optional c) { halo_fill_ = c; } -void fixed_formating_token::set_halo_radius(optional radius) +void format_node::set_halo_radius(optional radius) { halo_radius_ = radius; } - -/************************************************************/ - -ptree *end_format_token::to_xml(ptree *node) -{ - return 0; -} +} //namespace formating /************************************************************/ text_processor::text_processor(): - list_(), clear_on_write(false) + tree_() { } -void text_processor::push_back(abstract_token *token) +void text_processor::set_format_tree(formating::node_ptr tree) { - if (clear_on_write) list_.clear(); - clear_on_write = false; - list_.push_back(token); + tree_ = tree; +} + +formating::node_ptr text_processor::get_format_tree() const +{ + return tree_; } void text_processor::from_xml(const boost::property_tree::ptree &pt, std::map const &fontsets) { - clear_on_write = true; defaults.set_values_from_xml(pt, fontsets); - from_xml_recursive(pt, fontsets); + formating::node_ptr n = formating::node::from_xml(pt); + if (n) set_format_tree(n); } -void text_processor::from_xml_recursive(const boost::property_tree::ptree &pt, std::map const &fontsets) -{ - ptree::const_iterator itr = pt.begin(); - ptree::const_iterator end = pt.end(); - for (; itr != end; ++itr) { - if (itr->first == "") { - std::string data = itr->second.data(); - boost::trim(data); - if (data.empty()) continue; - expression_token *token = new expression_token(parse_expression(data, "utf8")); - push_back(token); - } else if (itr->first == "Format") { - fixed_formating_token *token = new fixed_formating_token(); - token->from_xml(itr->second); - push_back(token); - from_xml_recursive(itr->second, fontsets); /* Parse children, making a list out of a tree. */ - push_back(new end_format_token()); - } else if (itr->first != "" && itr->first != "" && itr->first != "Placement") { - std::cerr << "Unknown item" << itr->first; - } - } -} void text_processor::to_xml(boost::property_tree::ptree &node, bool explicit_defaults, text_processor const& dfl) const { defaults.to_xml(node, explicit_defaults, dfl.defaults); - std::list::const_iterator itr = list_.begin(); - std::list::const_iterator end = list_.end(); - std::stack nodes; - ptree *current_node = &node; - for (; itr != end; ++itr) { - abstract_token *token = *itr; - ptree *new_node = token->to_xml(current_node); - if (dynamic_cast(token)) { - nodes.push(current_node); - current_node = new_node; - } else if (dynamic_cast(token)) { - current_node = nodes.top(); - nodes.pop(); - } - } + if (tree_) tree_->to_xml(node); } -void text_processor::process(processed_text &output, Feature const& feature) +void text_processor::process(processed_text &output, Feature const& feature) const { - std::list::const_iterator itr = list_.begin(); - std::list::const_iterator end = list_.end(); - std::stack formats; - formats.push(defaults); - - for (; itr != end; ++itr) { - abstract_text_token *text = dynamic_cast(*itr); - abstract_formating_token *format = dynamic_cast(*itr);; - end_format_token *end = dynamic_cast(*itr);; - if (text) { - UnicodeString text_str = text->to_string(feature); - char_properties const& p = formats.top(); - /* TODO: Make a class out of text_transform which does the work! */ - if (p.text_transform == UPPERCASE) - { - text_str = text_str.toUpper(); - } - else if (p.text_transform == LOWERCASE) - { - text_str = text_str.toLower(); - } - else if (p.text_transform == CAPITALIZE) - { - text_str = text_str.toTitle(NULL); - } - if (text_str.length() > 0) { - output.push_back(processed_expression(p, text_str)); - } else { + output.clear(); + if (tree_) { + tree_->apply(defaults, feature, output); + } else { #ifdef MAPNIK_DEBUG - std::cerr << "Warning: Empty expression.\n"; + std::cerr << "Warning: text_processor can't produce text: No formating tree!\n"; #endif - } - } else if (format) { - char_properties next_properties = formats.top(); - format->apply(next_properties, feature); - formats.push(next_properties); - } else if (end) { - /* Always keep at least the defaults_ on stack. */ - if (formats.size() > 1) { - formats.pop(); - } else { - std::cerr << "Warning: Internal mapnik error. More elements popped than pushed in text_processor::process()\n"; - output.clear(); - return; - } - } - } - if (formats.size() != 1) { - std::cerr << "Warning: Internal mapnik error. Less elements popped than pushed in text_processor::process()\n"; } } std::set text_processor::get_all_expressions() const { std::set result; - std::list::const_iterator itr = list_.begin(); - std::list::const_iterator end = list_.end(); - for (; itr != end; ++itr) { - expression_token *text = dynamic_cast(*itr); - if (text) result.insert(text->get_expression()); - } + if (tree_) tree_->add_expressions(result); return result; } void text_processor::set_old_style_expression(expression_ptr expr) { - list_.push_back(new expression_token(expr)); + tree_ = formating::node_ptr(new formating::text_node(expr)); } /************************************************************/ @@ -399,12 +406,12 @@ void processed_text::push_back(processed_expression const& exp) expr_list_.push_back(exp); } -processed_text::expression_list::const_iterator processed_text::begin() +processed_text::expression_list::const_iterator processed_text::begin() const { return expr_list_.begin(); } -processed_text::expression_list::const_iterator processed_text::end() +processed_text::expression_list::const_iterator processed_text::end() const { return expr_list_.end(); } @@ -424,7 +431,7 @@ void processed_text::clear() string_info &processed_text::get_string_info() { - //info_.clear(); TODO: if this function is called twice invalid results are returned, so clear string_info first + 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) From 604b8db016f1be3e0239fc17b9682424c22298bb Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 27 Jan 2012 23:14:49 +0100 Subject: [PATCH 66/81] Update tests. --- tests/visual_tests/clean.sh | 2 +- .../formating-1-500-reference.png | Bin 0 -> 4391 bytes .../{formating.xml => formating-1.xml} | 3 ++- .../formating-2-500-reference.png | Bin 0 -> 4391 bytes tests/visual_tests/formating-2.xml | 22 ++++++++++++++++++ .../formating-3-500-reference.png | Bin 0 -> 2994 bytes tests/visual_tests/formating-3.xml | 22 ++++++++++++++++++ .../formating-4-500-reference.png | Bin 0 -> 533 bytes tests/visual_tests/formating-4.xml | 21 +++++++++++++++++ .../visual_tests/formating-500-reference.png | Bin 8139 -> 0 bytes tests/visual_tests/test.py | 11 ++++++--- 11 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 tests/visual_tests/formating-1-500-reference.png rename tests/visual_tests/{formating.xml => formating-1.xml} (80%) create mode 100644 tests/visual_tests/formating-2-500-reference.png create mode 100644 tests/visual_tests/formating-2.xml create mode 100644 tests/visual_tests/formating-3-500-reference.png create mode 100644 tests/visual_tests/formating-3.xml create mode 100644 tests/visual_tests/formating-4-500-reference.png create mode 100644 tests/visual_tests/formating-4.xml delete mode 100644 tests/visual_tests/formating-500-reference.png diff --git a/tests/visual_tests/clean.sh b/tests/visual_tests/clean.sh index eebdaf882..8733c453a 100755 --- a/tests/visual_tests/clean.sh +++ b/tests/visual_tests/clean.sh @@ -2,5 +2,5 @@ rm -f list-[0-9][0-9]0-agg.png rm -f simple-[0-9][0-9]0-agg.png rm -f simple-{E,N,NE,NW,N,SE,SW,S,W}-500-agg.png rm -f formating-500-agg.png -rm -f list-out.xml simple-out.xml +rm -f *-out.xml diff --git a/tests/visual_tests/formating-1-500-reference.png b/tests/visual_tests/formating-1-500-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..f863a4169693c7b44d4cfea608bfbe1dc8b1708d GIT binary patch literal 4391 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 ibgt3=$@cl_nfB - + + [name]+' ''('+[name]+')' diff --git a/tests/visual_tests/formating-2-500-reference.png b/tests/visual_tests/formating-2-500-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..f863a4169693c7b44d4cfea608bfbe1dc8b1708d GIT binary patch literal 4391 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 ibgt3=$@cl_nfB + + + + + My Style + + shape + points.shp + + + + + + diff --git a/tests/visual_tests/formating-3-500-reference.png b/tests/visual_tests/formating-3-500-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..788f2a33b6221f938dbc0608a557d811e6969175 GIT binary patch literal 2994 zcmds(`8V778pqSB>TuDiqSaCDba7jXskJeQy{f5bX@}a<#ZDL{)RHJYYEY%MhFWJ* zTSYCANi3Px&LGy1h_s_5goGv%5zGDFd+zjqxc8j*`JB%W&pFTYd3~PeeV){taC=30 z4S5gf6PGx5QrPoQ0TzWM_M%7-4T5+o`N-|2= z+ab<0JWP>n&=5mf3ETHEl7)O{({I-32`Gzd4z0d)w9#*<(9L9X$dpyUO4x(sA#GMOAq%{zZPc?RB>1o%4lQMD*bxc%Czb7dFH!S64m=gf&P!gSQcf=5G&69elfD`-?3{ z;8xZ2?alejhOi>bOxItpX58SzL3W{QzG$eU56J z365C2F-}GWyAVUYBDi(L!k1_4=`e$r5&^qNNfTTUT;D#W^o1GCLaOO`2FQHskn)=3 z$^fQVDWAg9D^1vqnL^-KUz~){@%60eUd`x&yG?%QFqkc2U~7hx#m>ZyV(_F9l0Gr} zbEo1Pr&8!5k%fWHP#{QiQ)49I8+<+TU$!8Tq-+`m8nyb(4Gf$$H zQ~2{|NlM;@y%^!B{SXiquuQ8DBS)(F;{LL!6Cek1knk7DqW2wWDLu~pWc-@dc$t;Vc+DLh!#Gih_(RX1`QeZ19KT?k z`=p**^G!LHT1Ej`?ai0{{yY|!RSHFu6|=nCV`oYeHYQyc({%6#ozD~v5f}+hveGoN zlE|KPMHsV(i<<;JU=U=zOJxxtOc)fcGMaY3ltAJN-3dWhhwSq+R1A;AUXc=Ht{uI6 z$)m+c`Ny>f9#T0zBO9(t>(5xUQmwLnv9g(J`+m`7BOpt)aF!xQy!Sd-eij-$=7&>bf0nDX8)9rLVhO)aA+=?HjzJf?T zXGir?@7aGEZ9#fOtpC;B1vK%W4fT9FVUPsCxI|6&636Gy3MK&>=A2{&b@^rcE6=li z%pWfqYaSk}Hw$1VY<@~v{lC}U zBRnwrg5+j!K9V$HfN!|D$$@NTsfAG7gOU7SIc+)nG*i$hMfMSqpMm;%px|YC3y6XT=8T&-jm}z zm!gFUupr>l&8aBD`5m7s4`OK4^*32j`DyxuVZtSvsLNPQUJG62z#6%u?X`PJ7SYrH z?}8yLX*|o-^Xz`~$3GH9nYS|geB2|k*4;PP>sxu?RvuueVI{zD$C=`f9?{)GV-JQi zT98pE3K@M-zvjumr5T4U&_&LJYMgF-T)AmCjs|wumw3z3FgcxO4sV{)5@qB<7${oI zn-;F+DGh|sIX6q_Hqi2)=OdhN*0y#*4gl8mw zutzym#;`e`%ckRxYt!gH^1^t&0F4NLc}V!}nXA)=$Iz*EQ1V1g)6F4t@F{RKF9e}~ z)=k*=6q>sP={IsJN4D+lN{3IoTw}n_d6cQQ^1@|fw_^4FhB=g6!&1?PN7A3cTcDdC zWyag%qNN?agLyNwd8NqF_t(XztMIX1j&da&g(P`obc4j1>&dwI8zlU>9qB;qg`Vn* zGE6bL+wOa6zV6)z=7LdsE!F0Is&Id=LY-01r`GPKAq&4IIx400rR#SDAZXesT9$-} z)PM7z@AdZ}HYW1MG9Mk&?l(bHNfqeLf!UAvmQZlw*bLgpt~~n(njxBINy|x|8m}x_ z94cZwz3t2J((RZ4vsyeED!pzwurgRfFHpt~OVq2KYDx2D!i&K(t%V!E{fk zRP2E>TUa%;3>{#i!k}7lhRylSLNs!daRjI`Qk3ubDh94_>wBk6_m9?|%JXO-OD YXRs(5l8dcX16E;>gB{!!Z+$Q6U;X;K8~^|S literal 0 HcmV?d00001 diff --git a/tests/visual_tests/formating-3.xml b/tests/visual_tests/formating-3.xml new file mode 100644 index 000000000..1e1bbd048 --- /dev/null +++ b/tests/visual_tests/formating-3.xml @@ -0,0 +1,22 @@ + + + + + + My Style + + shape + points.shp + + + + + + diff --git a/tests/visual_tests/formating-4-500-reference.png b/tests/visual_tests/formating-4-500-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..fbc8d6bb1f482c6e590b28bd41b808419ff632a7 GIT binary patch literal 533 zcmeAS@N?(olHy`uVBq!ia0y~yVEh7Pr*N<)`13=d27i_jdkfWOz{C#K*um6mdQ8FU#kT=bh@)Ez8-M+$|fu z?vCP%_zfV|n;S{T#T@06kBeDvyRq?TpKg=gF}N}#M|OsFiZ2)h6ox4Nz{aKBBza+W SJGkCiCxvX + + + + + My Style + + shape + points.shp + + + + + + diff --git a/tests/visual_tests/formating-500-reference.png b/tests/visual_tests/formating-500-reference.png deleted file mode 100644 index 0ab02b598955b5f4ab0ff0b001234dc24332d7bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8139 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 N6$Q 1: + filenames = [] + filenames_one_width = sys.argv[1:] + for filename in filenames: for width in widths: m = render(filename, width) mapnik.save_map(m, "%s-out.xml" % filename) for filename in filenames_one_width: - render(filename, 500) + m = render(filename, 500) + mapnik.save_map(m, "%s-out.xml" % filename) From ff07b4bce08c32f09a0c34a6d9bc4f0c035ff919 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sat, 28 Jan 2012 00:09:58 +0100 Subject: [PATCH 67/81] placement_element => text_path --- include/mapnik/placement_finder.hpp | 8 ++++---- include/mapnik/text_placements.hpp | 4 +--- src/metawriter.cpp | 2 +- src/placement_finder.cpp | 26 +++++++++++++------------- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/include/mapnik/placement_finder.hpp b/include/mapnik/placement_finder.hpp index 37a0219f6..94d42fcb1 100644 --- a/include/mapnik/placement_finder.hpp +++ b/include/mapnik/placement_finder.hpp @@ -65,14 +65,14 @@ private: // 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::auto_ptr get_placement_offset(const std::vector & path_positions, + std::auto_ptr get_placement_offset(const std::vector & path_positions, const std::vector & path_distances, int & orientation, unsigned index, double distance); - ///Tests wether the given placement_element be placed without a collision + ///Tests wether 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(const std::auto_ptr & current_placement, const int & orientation); + bool test_placement(const std::auto_ptr & current_placement, const int & orientation); ///Does a line-circle intersect calculation // NOTE: Follow the strict pre conditions @@ -87,7 +87,7 @@ private: void find_line_breaks(); void init_string_size(); void init_alignment(); - void adjust_position(placement_element *current_placement, double label_x, double label_y); + void adjust_position(text_path *current_placement, double label_x, double label_y); ///General Internals DetectorT & detector_; diff --git a/include/mapnik/text_placements.hpp b/include/mapnik/text_placements.hpp index fdb6530dc..c765b6a8c 100644 --- a/include/mapnik/text_placements.hpp +++ b/include/mapnik/text_placements.hpp @@ -46,8 +46,6 @@ namespace mapnik { class text_placements; -typedef text_path placement_element; - typedef boost::tuple position; enum label_placement_enum { @@ -180,7 +178,7 @@ public: /* TODO */ std::queue< box2d > envelopes; /* TODO */ - boost::ptr_vector placements; + boost::ptr_vector placements; }; typedef boost::shared_ptr text_placement_info_ptr; diff --git a/src/metawriter.cpp b/src/metawriter.cpp index 25b7b6eaa..a97fcb6a5 100644 --- a/src/metawriter.cpp +++ b/src/metawriter.cpp @@ -194,7 +194,7 @@ void metawriter_json_stream::add_text(text_placement_info const& p, */ for (unsigned n = 0; n < p.placements.size(); n++) { - placement_element & current_placement = const_cast(p.placements[n]); + text_path & current_placement = const_cast(p.placements[n]); bool inside = false; /* Part of text is inside rendering region */ bool straight = true; diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index 2d2888cf4..764120304 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -310,7 +310,7 @@ void placement_finder::init_alignment() template -void placement_finder::adjust_position(placement_element *current_placement, double label_x, double label_y) +void placement_finder::adjust_position(text_path *current_placement, double label_x, double label_y) { // if needed, adjust for desired vertical alignment current_placement->starting_y = label_y; // no adjustment, default is MIDDLE @@ -348,7 +348,7 @@ void placement_finder::find_point_placement(double label_x, double la double sina = std::sin(rad); double x, y; - std::auto_ptr current_placement(new placement_element); + std::auto_ptr current_placement(new text_path); adjust_position(current_placement.get(), label_x, label_y); @@ -595,7 +595,7 @@ void placement_finder::find_line_placements(PathT & shape_path) { //Record details for the start of the string placement int orientation = 0; - std::auto_ptr current_placement = get_placement_offset(path_positions, path_distances, orientation, index, segment_length - (distance - target_distance) + (diff*dir)); + std::auto_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() == NULL) @@ -657,7 +657,7 @@ void placement_finder::find_line_placements(PathT & shape_path) } template -std::auto_ptr placement_finder::get_placement_offset(const std::vector &path_positions, const std::vector &path_distances, int &orientation, unsigned index, double distance) +std::auto_ptr placement_finder::get_placement_offset(const std::vector &path_positions, const std::vector &path_distances, int &orientation, unsigned index, double distance) { //Check that the given distance is on the given index and find the correct index and distance if not while (distance < 0 && index > 1) @@ -666,7 +666,7 @@ std::auto_ptr placement_finder::get_placement_offs distance += path_distances[index]; } if (index <= 1 && distance < 0) //We've gone off the start, fail out - return std::auto_ptr(NULL); + return std::auto_ptr(NULL); //Same thing, checking if we go off the end while (index < path_distances.size() && distance > path_distances[index]) @@ -675,13 +675,13 @@ std::auto_ptr placement_finder::get_placement_offs index++; } if (index >= path_distances.size()) - return std::auto_ptr(NULL); + return std::auto_ptr(NULL); //Keep track of the initial index,distance incase we need to re-call get_placement_offset const unsigned initial_index = index; const double initial_distance = distance; - std::auto_ptr current_placement(new placement_element); + std::auto_ptr current_placement(new text_path); double old_x = path_positions[index-1].x; double old_y = path_positions[index-1].y; @@ -695,7 +695,7 @@ std::auto_ptr placement_finder::get_placement_offs double segment_length = path_distances[index]; if (segment_length == 0) { // Not allowed to place across on 0 length segments or discontinuities - return std::auto_ptr(NULL); + return std::auto_ptr(NULL); } current_placement->starting_x = old_x + dx*distance/segment_length; @@ -719,7 +719,7 @@ std::auto_ptr placement_finder::get_placement_offs //Coordinates this character will start at if (segment_length == 0) { // Not allowed to place across on 0 length segments or discontinuities - return std::auto_ptr(NULL); + return std::auto_ptr(NULL); } double start_x = old_x + dx*distance/segment_length; double start_y = old_y + dy*distance/segment_length; @@ -747,7 +747,7 @@ std::auto_ptr placement_finder::get_placement_offs if (index >= path_positions.size()) //Bail out if we run off the end of the shape { //std::clog << "FAIL: Out of space" << std::endl; - return std::auto_ptr(NULL); + return std::auto_ptr(NULL); } new_x = path_positions[index].x; new_y = path_positions[index].y; @@ -784,7 +784,7 @@ std::auto_ptr placement_finder::get_placement_offs fabs(angle_delta) > p.max_char_angle_delta) { //std::clog << "FAIL: Too Bendy!" << std::endl; - return std::auto_ptr(NULL); + return std::auto_ptr(NULL); } double render_angle = angle; @@ -833,7 +833,7 @@ std::auto_ptr placement_finder::get_placement_offs { //Otherwise we have failed to find a placement //std::clog << "FAIL: Double upside-down!" << std::endl; - return std::auto_ptr(NULL); + return std::auto_ptr(NULL); } } @@ -841,7 +841,7 @@ std::auto_ptr placement_finder::get_placement_offs } template -bool placement_finder::test_placement(const std::auto_ptr & current_placement, const int & orientation) +bool placement_finder::test_placement(const std::auto_ptr & current_placement, const int & orientation) { //Create and test envelopes bool status = true; From a606f3cd9ae511a7981ecea9b6a36c796e9cef02 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sat, 28 Jan 2012 17:12:52 +0100 Subject: [PATCH 68/81] Correctly initialize directory for run_tests.py --- tests/run_tests.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/run_tests.py b/tests/run_tests.py index a4be7190d..bbe469a68 100755 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -63,8 +63,9 @@ def main(): # 3 * '-v' gets us debugging information from nose argv.append('-v') argv.append('-v') - - argv.extend(['-w','./tests/python_tests']) + + dirname = os.path.dirname(sys.argv[0]) + argv.extend(['-w', dirname+'/python_tests']) if not nose.run(argv=argv, plugins=[TodoPlugin(), Doctest()]): sys.exit(1) From a3871e52b794f92ca1674a400ed204c504c87679 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sat, 28 Jan 2012 17:13:41 +0100 Subject: [PATCH 69/81] Rename set_values_from_xml to from_xml. --- include/mapnik/text_placements.hpp | 2 +- include/mapnik/text_processing.hpp | 2 +- src/load_map.cpp | 6 +++--- src/text_placements.cpp | 4 ++-- src/text_processing.cpp | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/mapnik/text_placements.hpp b/include/mapnik/text_placements.hpp index c765b6a8c..bb6ee9df4 100644 --- a/include/mapnik/text_placements.hpp +++ b/include/mapnik/text_placements.hpp @@ -95,7 +95,7 @@ struct text_symbolizer_properties { text_symbolizer_properties(); /** Load all values and also the ```processor``` object from XML ptree. */ - void set_values_from_xml(boost::property_tree::ptree const &sym, std::map const & fontsets); + void from_xml(boost::property_tree::ptree const &sym, std::map const & fontsets); /** Save all values to XML ptree (but does not create a new parent node!). */ void to_xml(boost::property_tree::ptree &node, bool explicit_defaults, text_symbolizer_properties const &dfl=text_symbolizer_properties()) const; diff --git a/include/mapnik/text_processing.hpp b/include/mapnik/text_processing.hpp index 9c9306c05..8bc4dcc1d 100644 --- a/include/mapnik/text_processing.hpp +++ b/include/mapnik/text_processing.hpp @@ -53,7 +53,7 @@ struct char_properties { char_properties(); /** Construct object from XML. */ - void set_values_from_xml(boost::property_tree::ptree const &sym, std::map const & fontsets); + void from_xml(boost::property_tree::ptree const &sym, std::map const & fontsets); /** Write object to XML ptree. */ void to_xml(boost::property_tree::ptree &node, bool explicit_defaults, char_properties const &dfl=char_properties()) const; std::string face_name; diff --git a/src/load_map.cpp b/src/load_map.cpp index c6e3f47e8..311a2c66f 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -1282,7 +1282,7 @@ void map_parser::parse_text_symbolizer( rule & rule, ptree const & sym ) } text_symbolizer text_symbol = text_symbolizer(placement_finder); - placement_finder->properties.set_values_from_xml(sym, fontsets_); + placement_finder->properties.from_xml(sym, fontsets_); if (strict_) ensure_font_face(placement_finder->properties.processor.defaults.face_name); if (list) { ptree::const_iterator symIter = sym.begin(); @@ -1296,7 +1296,7 @@ void map_parser::parse_text_symbolizer( rule & rule, ptree const & sym ) } ensure_attrs(symIter->second, "TextSymbolizer/Placement", s_common.str()); text_symbolizer_properties & p = list->add(); - p.set_values_from_xml(symIter->second, fontsets_); + p.from_xml(symIter->second, fontsets_); if (strict_) ensure_font_face(p.processor.defaults.face_name); } } @@ -1419,7 +1419,7 @@ void map_parser::parse_shield_symbolizer( rule & rule, ptree const & sym ) } } text_placements_ptr placement_finder = shield_symbol.get_placement_options(); - placement_finder->properties.set_values_from_xml(sym, fontsets_); + placement_finder->properties.from_xml(sym, fontsets_); rule.append(shield_symbol); } catch (const config_error & ex) diff --git a/src/text_placements.cpp b/src/text_placements.cpp index b6865f2cd..2ea18f63a 100644 --- a/src/text_placements.cpp +++ b/src/text_placements.cpp @@ -63,7 +63,7 @@ text_symbolizer_properties::text_symbolizer_properties() : } -void text_symbolizer_properties::set_values_from_xml(boost::property_tree::ptree const &sym, std::map const & fontsets) +void text_symbolizer_properties::from_xml(boost::property_tree::ptree const &sym, std::map const & fontsets) { optional placement_ = get_opt_attr(sym, "placement"); if (placement_) label_placement = *placement_; @@ -204,7 +204,7 @@ char_properties::char_properties() : } -void char_properties::set_values_from_xml(boost::property_tree::ptree const &sym, std::map const & fontsets) +void char_properties::from_xml(boost::property_tree::ptree const &sym, std::map const & fontsets) { optional text_size_ = get_opt_attr(sym, "size"); if (text_size_) text_size = *text_size_; diff --git a/src/text_processing.cpp b/src/text_processing.cpp index 5bd389e1e..8f65cbce9 100644 --- a/src/text_processing.cpp +++ b/src/text_processing.cpp @@ -363,7 +363,7 @@ formating::node_ptr text_processor::get_format_tree() const void text_processor::from_xml(const boost::property_tree::ptree &pt, std::map const &fontsets) { - defaults.set_values_from_xml(pt, fontsets); + defaults.from_xml(pt, fontsets); formating::node_ptr n = formating::node::from_xml(pt); if (n) set_format_tree(n); } From 2d50c840fe9d0b099f0a2fec05ec96413b1fba69 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sat, 28 Jan 2012 19:37:13 +0100 Subject: [PATCH 70/81] Update tests. --- tests/python_tests/object_test.py | 5 ++--- tests/python_tests/pickling_test.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/python_tests/object_test.py b/tests/python_tests/object_test.py index a4caf1c3a..7981cb16a 100644 --- a/tests/python_tests/object_test.py +++ b/tests/python_tests/object_test.py @@ -24,7 +24,6 @@ def test_line_symbolizer_init(): # ShieldSymbolizer initialization def test_shieldsymbolizer_init(): s = mapnik.ShieldSymbolizer(mapnik.Expression('[Field Name]'), 'DejaVu Sans Bold', 6, mapnik.Color('#000000'), mapnik.PathExpression('../data/images/dummy.png')) - eq_(s.anchor, (0.0,0.5,)) eq_(s.displacement, (0.0,0.0)) eq_(s.allow_overlap, False) eq_(s.avoid_edges, False) @@ -41,7 +40,7 @@ def test_shieldsymbolizer_init(): eq_(s.text_ratio, 0) eq_(s.text_size, 6) eq_(s.wrap_width, 0) - eq_(s.vertical_alignment, mapnik.vertical_alignment.MIDDLE) + eq_(s.vertical_alignment, mapnik.vertical_alignment.AUTO) eq_(s.label_spacing, 0) eq_(s.label_position_tolerance, 0) # 22.5 * M_PI/180.0 initialized by default @@ -54,7 +53,7 @@ def test_shieldsymbolizer_init(): # r1341 eq_(s.wrap_before, False) - eq_(s.horizontal_alignment, mapnik.horizontal_alignment.MIDDLE) + eq_(s.horizontal_alignment, mapnik.horizontal_alignment.AUTO) eq_(s.justify_alignment, mapnik.justify_alignment.MIDDLE) eq_(s.opacity, 1.0) diff --git a/tests/python_tests/pickling_test.py b/tests/python_tests/pickling_test.py index 56a1e1fb1..092c4e60a 100644 --- a/tests/python_tests/pickling_test.py +++ b/tests/python_tests/pickling_test.py @@ -74,6 +74,7 @@ def test_linesymbolizer_pickle(): # TextSymbolizer pickling def test_textsymbolizer_pickle(): + raise Todo("text_symbolizer pickling currently disabled") ts = mapnik.TextSymbolizer(mapnik.Expression('[Field_Name]'), 'Font Name', 8, mapnik.Color('black')) eq_(str(ts.name), str(mapnik.Expression('[Field_Name]'))) @@ -81,7 +82,6 @@ def test_textsymbolizer_pickle(): eq_(ts.text_size, 8) eq_(ts.fill, mapnik.Color('black')) - raise Todo("text_symbolizer pickling currently disabled") ts2 = pickle.loads(pickle.dumps(ts,pickle.HIGHEST_PROTOCOL)) eq_(ts.name, ts2.name) From 09459683e9608082423e4a74152d44fa5538eb04 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 29 Jan 2012 04:49:02 +0100 Subject: [PATCH 71/81] Update symbolizer helpers for ShieldSymbolizer. Correctly handle point placement for TextSymbolizer. (Tries each possible placement for each point). --- include/mapnik/placement_finder.hpp | 9 +- include/mapnik/symbolizer_helpers.hpp | 284 +++++++-------------- src/agg/process_shield_symbolizer.cpp | 153 ++--------- src/build.py | 1 + src/load_map.cpp | 83 ++++-- src/placement_finder.cpp | 49 +--- src/symbolizer_helpers.cpp | 351 ++++++++++++++++++++++++++ 7 files changed, 524 insertions(+), 406 deletions(-) create mode 100644 src/symbolizer_helpers.cpp diff --git a/include/mapnik/placement_finder.hpp b/include/mapnik/placement_finder.hpp index 94d42fcb1..f565a90f6 100644 --- a/include/mapnik/placement_finder.hpp +++ b/include/mapnik/placement_finder.hpp @@ -36,20 +36,17 @@ public: placement_finder(text_placement_info &p, string_info &info, DetectorT & detector); placement_finder(text_placement_info &p, string_info &info, DetectorT & detector, box2d const& extent); - //Try place a single label at the given point + /** 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 + /** Iterate over the given path, placing point labels with respect to label_spacing. */ template void find_point_placements(T & path); - //Iterate over the given path, placing line-following labels with respect to label_spacing + /** Iterate over the given path, placing line-following labels with respect to label_spacing. */ template void find_line_placements(T & path); - //Find placement, automatically select point or line placement - void find_placement(double angle, geometry_type const& geom, CoordTransform const& t, proj_transform const& prj_trans); - void update_detector(); void clear(); diff --git a/include/mapnik/symbolizer_helpers.hpp b/include/mapnik/symbolizer_helpers.hpp index c389609a0..1b50931d3 100644 --- a/include/mapnik/symbolizer_helpers.hpp +++ b/include/mapnik/symbolizer_helpers.hpp @@ -36,6 +36,10 @@ namespace mapnik { +typedef std::pair point_type; + +/** Helper object that does all the TextSymbolizer placment finding + * work except actually rendering the object. */ template class text_symbolizer_helper { @@ -48,223 +52,119 @@ public: double scale_factor, CoordTransform const &t, FaceManagerT &font_manager, - DetectorT &detector) : - sym_(sym), - feature_(feature), - prj_trans_(prj_trans), - width_(width), - height_(height), - scale_factor_(scale_factor), - t_(t), - font_manager_(font_manager), - detector_(detector), - text_(), - angle_(0.0) - { - initialize_geometries(); - if (!geometries_to_process_.size()) return; - text_ = boost::shared_ptr(new processed_text(font_manager_, scale_factor_)); - placement_ = sym_.get_placement_options()->get_placement_info(); - placement_->init(scale_factor_, width_, height_); - metawriter_with_properties writer = sym_.get_metawriter(); - if (writer.first) - placement_->collect_extents = true; - itr_ = geometries_to_process_.begin(); - next_placement(); - } - - bool next_placement() - { - if (!placement_->next()) return false; - /* TODO: Simplify this. */ - text_->clear(); - placement_->properties.processor.process(*text_, feature_); - info_ = &(text_->get_string_info()); - if (placement_->properties.orientation) - { - angle_ = boost::apply_visitor( - evaluate(feature_), - *(placement_->properties.orientation)).to_double(); - } else { - angle_ = 0.0; - } - /* END TODO */ - return true; - } + DetectorT &detector) + : sym_(sym), + feature_(feature), + prj_trans_(prj_trans), + t_(t), + font_manager_(font_manager), + detector_(detector), + writer_(sym.get_metawriter()), + dims_(0, 0, width, height), + text_(font_manager, scale_factor), + angle_(0.0), + placement_valid_(true) + { + initialize_geometries(); + if (!geometries_to_process_.size()) return; //TODO: Test this + placement_ = sym_.get_placement_options()->get_placement_info(); + placement_->init(scale_factor, width, height); + if (writer_.first) placement_->collect_extents = true; + next_placement(); + initialize_points(); + } /** Return next placement. * If no more placements are found returns null pointer. - * TODO: Currently stops returning placements to early. */ text_placement_info_ptr get_placement(); -private: + text_placement_info_ptr get_point_placement(); + text_placement_info_ptr get_line_placement(); +protected: + bool next_placement(); void initialize_geometries(); + void initialize_points(); + //Input text_symbolizer const& sym_; Feature const& feature_; proj_transform const& prj_trans_; - unsigned width_; - unsigned height_; - double scale_factor_; CoordTransform const &t_; FaceManagerT &font_manager_; DetectorT &detector_; - boost::shared_ptr text_; /*TODO: Use shared pointers for text placement so we don't need to keep a reference here! */ - std::vector geometries_to_process_; - std::vector::iterator itr_; - text_placement_info_ptr placement_; + metawriter_with_properties writer_; + box2d dims_; + + //Processing + processed_text text_; + /* Using list instead of vector, because we delete random elements and need iterators to stay valid. */ + std::list geometries_to_process_; + std::list::iterator geo_itr_; + std::list points_; + std::list::iterator point_itr_; double angle_; string_info *info_; + bool placement_valid_; + bool point_placement_; + + //Output + text_placement_info_ptr placement_; }; - template -text_placement_info_ptr text_symbolizer_helper::get_placement() -{ - if (!geometries_to_process_.size()) return text_placement_info_ptr(); - metawriter_with_properties writer = sym_.get_metawriter(); - box2d dims(0, 0, width_, height_); - - while (geometries_to_process_.size()) - { - if (itr_ == geometries_to_process_.end()) { - //Just processed the last geometry. Try next placement. - if (!next_placement()) return text_placement_info_ptr(); //No more placements - //Start again from begin of list - itr_ = geometries_to_process_.begin(); - } - //TODO: Avoid calling constructor repeatedly - placement_finder finder(*placement_, *info_, detector_, dims); - finder.find_placement(angle_, **itr_, t_, prj_trans_); - if (placement_->placements.size()) - { - //Found a placement - geometries_to_process_.erase(itr_); //Remove current geometry - if (writer.first) writer.first->add_text(*placement_, font_manager_, feature_, t_, writer.second); - itr_++; - return placement_; - } - //No placement for this geometry. Keep it in geometries_to_process_ for next try. - itr_++; - - } - return text_placement_info_ptr(); -} - -template -void text_symbolizer_helper::initialize_geometries() -{ - unsigned num_geom = feature_.num_geometries(); - for (unsigned i=0; i 0) - { - // TODO - find less costly method than fetching full envelope - box2d gbox = t_.forward(geom.envelope(), prj_trans_); - if (gbox.width() < sym_.get_minimum_path_length()) - { - continue; - } - } - // TODO - calculate length here as well - geometries_to_process_.push_back(const_cast(&geom)); - } -} - - -/*****************************************************************************/ - -template -class shield_symbolizer_helper +class shield_symbolizer_helper: public text_symbolizer_helper { public: - shield_symbolizer_helper(unsigned width, - unsigned height, - double scale_factor, - CoordTransform const &t, - FaceManagerT &font_manager, - DetectorT &detector) : - width_(width), - height_(height), - scale_factor_(scale_factor), - t_(t), - font_manager_(font_manager), - detector_(detector), - text_() + shield_symbolizer_helper(shield_symbolizer const& sym, + Feature const& feature, + proj_transform const& prj_trans, + unsigned width, + unsigned height, + double scale_factor, + CoordTransform const &t, + FaceManagerT &font_manager, + DetectorT &detector) : + text_symbolizer_helper(sym, feature, prj_trans, width, height, scale_factor, t, font_manager, detector), + sym_(sym) { - + init_marker(); } - text_placement_info_ptr get_placement(shield_symbolizer const& sym, - Feature const& feature, - proj_transform const& prj_trans); -private: - void init_marker(shield_symbolizer const& sym, Feature const& feature); - unsigned width_; - unsigned height_; - double scale_factor_; - CoordTransform const &t_; - FaceManagerT &font_manager_; - DetectorT &detector_; - boost::shared_ptr text_; - box2d label_ext_; + text_placement_info_ptr get_placement(); + std::pair get_marker_position(text_path &p); + marker &get_marker() const; + agg::trans_affine const& get_transform() const; +protected: + text_placement_info_ptr get_point_placement(); + text_placement_info_ptr get_line_placement(); + void init_marker(); + shield_symbolizer const& sym_; + box2d marker_ext_; boost::optional marker_; agg::trans_affine transform_; - int marker_w; - int marker_h; + int marker_w_; + int marker_h_; + int marker_x_; + int marker_y_; + // F***ing templates... + // http://womble.decadent.org.uk/c++/template-faq.html#base-lookup + using text_symbolizer_helper::geometries_to_process_; + using text_symbolizer_helper::placement_; + using text_symbolizer_helper::next_placement; + using text_symbolizer_helper::info_; + using text_symbolizer_helper::geo_itr_; + using text_symbolizer_helper::point_itr_; + using text_symbolizer_helper::points_; + using text_symbolizer_helper::writer_; + 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_; }; - - -template -text_placement_info_ptr shield_symbolizer_helper::get_placement( - shield_symbolizer const& sym, - Feature const& feature, - proj_transform const& prj_trans) -{ - init_marker(sym); - if (!marker_) return text_placement_info_ptr(); -} - -template -void shield_symbolizer_helper::init_marker(shield_symbolizer const& sym, Feature const& feature) -{ - std::string filename = path_processor_type::evaluate(*sym.get_filename(), feature); - boost::array const& m = sym.get_transform(); - transform_.load_from(&m[0]); - marker_.reset(); - if (!filename.empty()) - { - marker_ = marker_cache::instance()->find(filename, true); - } - if (!marker_) { - marker_w = 0; - marker_h = 0; - label_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; - double px2 = px1; - double py2 = py0; - double px3 = px0; - double py3 = py1; - transform_.transform(&px0,&py0); - transform_.transform(&px1,&py1); - transform_.transform(&px2,&py2); - transform_.transform(&px3,&py3); - label_ext_.init(px0, py0, px1, py1); - label_ext_.expand_to_include(px2, py2); - label_ext_.expand_to_include(px3, py3); -} - } //namespace #endif // SYMBOLIZER_HELPERS_HPP diff --git a/src/agg/process_shield_symbolizer.cpp b/src/agg/process_shield_symbolizer.cpp index ccb7f6028..d3db07e25 100644 --- a/src/agg/process_shield_symbolizer.cpp +++ b/src/agg/process_shield_symbolizer.cpp @@ -23,16 +23,13 @@ #include #include -#include #include #include #include #include -#include -#include "agg_basics.h" -#include "agg_rendering_buffer.h" -#include "agg_scanline_u.h" +#include + // boost #include @@ -44,140 +41,28 @@ void agg_renderer::process(shield_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { -#if 0 - text_renderer ren(pixmap_, faces, *strk); + shield_symbolizer_helper, + label_collision_detector4> helper( + sym, feature, prj_trans, + width_, height_, + scale_factor_, + t_, font_manager_, *detector_); + text_renderer ren(pixmap_, font_manager_, *(font_manager_.get_stroker())); - placement_finder finder(*detector_); + text_placement_info_ptr placement; + while ((placement = helper.get_placement())) { + for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) + { + std::pair marker_pos = helper.get_marker_position(placement->placements[ii]); + render_marker(marker_pos.first, marker_pos.second, helper.get_marker(), helper.get_transform(), sym.get_opacity()); - string_info info(text); - - faces->get_string_info(info, text, 0); - - metawriter_with_properties writer = sym.get_metawriter(); - - for (unsigned i = 0; i < feature.num_geometries(); ++i) - { - geometry_type const& geom = feature.get_geometry(i); - if (geom.num_points() > 0 ) - { - path_type path(t_,geom,prj_trans); - - label_placement_enum how_placed = sym.get_label_placement(); - if (how_placed == POINT_PLACEMENT || how_placed == VERTEX_PLACEMENT || how_placed == INTERIOR_PLACEMENT) - { - // for every vertex, try and place a shield/text - geom.rewind(0); - placement text_placement(info, sym, scale_factor_, w, h, false); - text_placement.avoid_edges = sym.get_avoid_edges(); - text_placement.allow_overlap = sym.get_allow_overlap(); - if (writer.first) - text_placement.collect_extents =true; // needed for inmem metawriter - position const& pos = sym.get_displacement(); - position const& shield_pos = sym.get_shield_displacement(); - for( unsigned jj = 0; jj < geom.num_points(); jj++ ) - { - double label_x; - double label_y; - double z=0.0; - - if( how_placed == VERTEX_PLACEMENT ) - geom.vertex(&label_x,&label_y); // by vertex - else if( how_placed == INTERIOR_PLACEMENT ) - geom.label_interior_position(&label_x,&label_y); - else - geom.label_position(&label_x, &label_y); // by middle of line or by point - prj_trans.backward(label_x,label_y, z); - t_.forward(&label_x,&label_y); - - label_x += boost::get<0>(shield_pos); - label_y += boost::get<1>(shield_pos); - - finder.find_point_placement( text_placement, placement_options, - label_x, label_y, 0.0, - sym.get_line_spacing(), - sym.get_character_spacing()); - - // check to see if image overlaps anything too, there is only ever 1 placement found for points and verticies - if( text_placement.placements.size() > 0) - { - double x = floor(text_placement.placements[0].starting_x); - double y = floor(text_placement.placements[0].starting_y); - int px; - int py; - - if( !sym.get_unlock_image() ) - { - // center image at text center position - // remove displacement from image label - double lx = x - boost::get<0>(pos); - double ly = y - boost::get<1>(pos); - px=int(floor(lx - (0.5 * w))) + 1; - py=int(floor(ly - (0.5 * h))) + 1; - label_ext.re_center(lx,ly); - } - else - { // center image at reference location - px=int(floor(label_x - 0.5 * w)); - py=int(floor(label_y - 0.5 * h)); - label_ext.re_center(label_x,label_y); - } - - if ( sym.get_allow_overlap() || detector_->has_placement(label_ext) ) - { - render_marker(px,py,**marker,tr,sym.get_opacity()); - - box2d dim = ren.prepare_glyphs(&text_placement.placements[0]); - ren.render(x,y); - detector_->insert(label_ext); - finder.update_detector(text_placement); - if (writer.first) { - writer.first->add_box(label_ext, feature, t_, writer.second); - writer.first->add_text(text_placement, faces, feature, t_, writer.second); - } - } - } - } - } - - else if (geom.num_points() > 1 && how_placed == LINE_PLACEMENT) - { - placement text_placement(info, sym, scale_factor_, w, h, false); - position const& pos = sym.get_displacement(); - - text_placement.avoid_edges = sym.get_avoid_edges(); - text_placement.additional_boxes.push_back( - box2d(-0.5 * label_ext.width() - boost::get<0>(pos), - -0.5 * label_ext.height() - boost::get<1>(pos), - 0.5 * label_ext.width() - boost::get<0>(pos), - 0.5 * label_ext.height() - boost::get<1>(pos))); - finder.find_point_placements(text_placement, placement_options, path); - - for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ ii) - { - double x = floor(text_placement.placements[ii].starting_x); - double y = floor(text_placement.placements[ii].starting_y); - - double lx = x - boost::get<0>(pos); - double ly = y - boost::get<1>(pos); - int px=int(floor(lx - (0.5*w))) + 1; - int py=int(floor(ly - (0.5*h))) + 1; - label_ext.re_center(lx, ly); - - render_marker(px,py,**marker,tr,sym.get_opacity()); - - box2d dim = ren.prepare_glyphs(&text_placement.placements[ii]); - ren.render(x,y); - if (writer.first) writer.first->add_box(label_ext, feature, t_, writer.second); - } - finder.update_detector(text_placement); - if (writer.first) writer.first->add_text(text_placement, faces, feature, t_, writer.second); - } - } - } + double x = placement->placements[ii].starting_x; + double y = placement->placements[ii].starting_y; + ren.prepare_glyphs(&(placement->placements[ii])); + ren.render(x, y); } } -#endif } diff --git a/src/build.py b/src/build.py index d8839bf7d..1520e9dd9 100644 --- a/src/build.py +++ b/src/build.py @@ -143,6 +143,7 @@ source = Split( memory_datasource.cpp stroke.cpp symbolizer.cpp + symbolizer_helpers.cpp arrow.cpp unicode.cpp markers_symbolizer.cpp diff --git a/src/load_map.cpp b/src/load_map.cpp index 311a2c66f..79f3d5c2e 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -1281,7 +1281,6 @@ void map_parser::parse_text_symbolizer( rule & rule, ptree const & sym ) placement_finder = text_placements_ptr(new text_placements_dummy()); } - text_symbolizer text_symbol = text_symbolizer(placement_finder); placement_finder->properties.from_xml(sym, fontsets_); if (strict_) ensure_font_face(placement_finder->properties.processor.defaults.face_name); if (list) { @@ -1300,6 +1299,8 @@ void map_parser::parse_text_symbolizer( rule & rule, ptree const & sym ) if (strict_) ensure_font_face(p.processor.defaults.face_name); } } + + text_symbolizer text_symbol = text_symbolizer(placement_finder); parse_metawriter_in_symbolizer(text_symbol, sym); rule.append(text_symbol); } @@ -1312,26 +1313,64 @@ void map_parser::parse_text_symbolizer( rule & rule, ptree const & sym ) void map_parser::parse_shield_symbolizer( rule & rule, ptree const & sym ) { + std::string s_common( + "name,face-name,fontset-name,size,fill,orientation," + "dx,dy,placement,vertical-alignment,halo-fill," + "halo-radius,text-ratio,wrap-width,wrap-before," + "wrap-character,text-transform,line-spacing," + "label-position-tolerance,character-spacing," + "spacing,minimum-distance,minimum-padding,minimum-path-length," + "avoid-edges,allow-overlap,opacity,max-char-angle-delta," + "horizontal-alignment,justify-alignment"); - std::stringstream s; - s << "name,face-name,fontset-name,size,fill," - << "dx,dy,placement,vertical-alignment,halo-fill," - << "halo-radius,text-ratio,wrap-width,wrap-before," - << "wrap-character,text-transform,line-spacing," - << "label-position-tolerance,character-spacing," - << "spacing,minimum-distance,minimum-padding," - << "avoid-edges,allow-overlap,opacity,max-char-angle-delta," - << "horizontal-alignment,justify-alignment," - // additional for shield - /* transform instead of orientation */ - << "file,base,transform,shield-dx,shield-dy," - << "text-opacity,unlock-image,no-text," - << "meta-writer,meta-output"; + std::string s_symbolizer(s_common + ",file,base," + "transform,shield-dx,shield-dy,text-opacity," + "unlock-image" + "placements,placement-type,meta-writer,meta-output"); - ensure_attrs(sym, "ShieldSymbolizer", s.str()); + ensure_attrs(sym, "ShieldSymbolizer", s_symbolizer); try { - shield_symbolizer shield_symbol = shield_symbolizer(); + text_placements_ptr placement_finder; + text_placements_list *list = 0; + optional placement_type = get_opt_attr(sym, "placement-type"); + if (placement_type) { + if (*placement_type == "simple") { + placement_finder = text_placements_ptr( + new text_placements_simple( + get_attr(sym, "placements", "X"))); + } else if (*placement_type == "list") { + list = new text_placements_list(); + placement_finder = text_placements_ptr(list); + } else if (*placement_type != "dummy" && *placement_type != "") { + throw config_error(std::string("Unknown placement type '"+*placement_type+"'")); + } + } + if (!placement_finder) { + placement_finder = text_placements_ptr(new text_placements_dummy()); + } + + placement_finder->properties.from_xml(sym, fontsets_); + if (strict_) ensure_font_face(placement_finder->properties.processor.defaults.face_name); + if (list) { + ptree::const_iterator symIter = sym.begin(); + ptree::const_iterator endSym = sym.end(); + for( ;symIter != endSym; ++symIter) { + if (symIter->first.find('<') != std::string::npos) continue; + if (symIter->first != "Placement") + { +// throw config_error("Unknown element '" + symIter->first + "'"); TODO + continue; + } + ensure_attrs(symIter->second, "TextSymbolizer/Placement", s_common); + text_symbolizer_properties & p = list->add(); + p.from_xml(symIter->second, fontsets_); + if (strict_) ensure_font_face(p.processor.defaults.face_name); + } + } + + shield_symbolizer shield_symbol = shield_symbolizer(placement_finder); + /* Symbolizer specific attributes. */ optional transform_wkt = get_opt_attr(sym, "transform"); if (transform_wkt) { @@ -1378,14 +1417,6 @@ void map_parser::parse_shield_symbolizer( rule & rule, ptree const & sym ) shield_symbol.set_unlock_image( * unlock_image ); } - // no text - optional no_text = - get_opt_attr(sym, "no-text"); - if (no_text) - { - shield_symbol.set_no_text( * no_text ); - } - parse_metawriter_in_symbolizer(shield_symbol, sym); std::string image_file = get_attr(sym, "file"); @@ -1418,8 +1449,6 @@ void map_parser::parse_shield_symbolizer( rule & rule, ptree const & sym ) std::clog << "### WARNING: " << msg << endl; } } - text_placements_ptr placement_finder = shield_symbol.get_placement_options(); - placement_finder->properties.from_xml(sym, fontsets_); rule.append(shield_symbol); } catch (const config_error & ex) diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index 764120304..1ab6cbd99 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -103,6 +103,7 @@ placement_finder::placement_finder(text_placement_info &placement_inf dimensions_(detector_.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_() { + placement_info.placements.clear(); //Remove left overs } template @@ -111,6 +112,7 @@ placement_finder::placement_finder(text_placement_info &placement_inf 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_() { + placement_info.placements.clear(); //Remove left overs } template @@ -996,53 +998,6 @@ void placement_finder::clear() detector_.clear(); } -template -void placement_finder::find_placement(double angle, geometry_type const& geom, CoordTransform const& t, proj_transform const& prj_trans) -{ - double label_x=0.0; - double label_y=0.0; - double z=0.0; - if (p.label_placement == POINT_PLACEMENT || - p.label_placement == VERTEX_PLACEMENT || - p.label_placement == INTERIOR_PLACEMENT) - { - unsigned iterations = 1; - if (p.label_placement == VERTEX_PLACEMENT) - { - iterations = geom.num_points(); - geom.rewind(0); - } - for(unsigned jj = 0; jj < iterations; jj++) { - switch (p.label_placement) - { - case POINT_PLACEMENT: - geom.label_position(&label_x, &label_y); - break; - case INTERIOR_PLACEMENT: - geom.label_interior_position(&label_x, &label_y); - break; - case VERTEX_PLACEMENT: - geom.vertex(&label_x, &label_y); - break; - case LINE_PLACEMENT: - case label_placement_enum_MAX: - /*not handled here*/ - break; - } - prj_trans.backward(label_x, label_y, z); - t.forward(&label_x, &label_y); - - find_point_placement(label_x, label_y, angle); - } - update_detector(); - } else if (p.label_placement == LINE_PLACEMENT && geom.num_points() > 1) - { - typedef coord_transform2 path_type; - path_type path(t, geom, prj_trans); - find_line_placements(path); - } -} - typedef coord_transform2 PathType; typedef label_collision_detector4 DetectorType; diff --git a/src/symbolizer_helpers.cpp b/src/symbolizer_helpers.cpp new file mode 100644 index 000000000..ecf5e35dc --- /dev/null +++ b/src/symbolizer_helpers.cpp @@ -0,0 +1,351 @@ +#include + +namespace mapnik { + +template +text_placement_info_ptr text_symbolizer_helper::get_placement() +{ + if (!placement_valid_) return text_placement_info_ptr(); + if (point_placement_) + return get_point_placement(); + else + return get_line_placement(); +} + +template +text_placement_info_ptr text_symbolizer_helper::get_line_placement() +{ + while (geometries_to_process_.size()) + { + if (geo_itr_ == geometries_to_process_.end()) + { + //Just processed the last geometry. Try next placement. + if (!next_placement()) return text_placement_info_ptr(); //No more placements + //Start again from begin of list + geo_itr_ = geometries_to_process_.begin(); + continue; //Reexecute size check + } + //TODO: Avoid calling constructor repeatedly + placement_finder finder(*placement_, *info_, detector_, dims_); + typedef coord_transform2 path_type; + path_type path(t_, **geo_itr_, prj_trans_); + finder.find_line_placements(path); + //Keep reference to current object so we can delete it. + std::list::iterator current_object = geo_itr_; + geo_itr_++; + if (placement_->placements.size()) + { + //Found a placement + geometries_to_process_.erase(current_object); + if (writer_.first) writer_.first->add_text( + *placement_, font_manager_, + feature_, t_, writer_.second); + return placement_; + } + //No placement for this geometry. Keep it in geometries_to_process_ for next try. + } + return text_placement_info_ptr(); +} + +template +text_placement_info_ptr text_symbolizer_helper::get_point_placement() +{ + while (points_.size()) + { + if (point_itr_ == points_.end()) + { + //Just processed the last point. Try next placement. + if (!next_placement()) return text_placement_info_ptr(); //No more placements + //Start again from begin of list + point_itr_ = points_.begin(); + continue; //Reexecute size check + } + placement_finder finder(*placement_, *info_, detector_, dims_); + finder.find_point_placement(point_itr_->first, point_itr_->second, angle_); + //Keep reference to current object so we can delete it. + std::list::iterator current_object = point_itr_; + point_itr_++; + if (placement_->placements.size()) + { + //Found a placement + points_.erase(current_object); + if (writer_.first) writer_.first->add_text( + *placement_, font_manager_, + feature_, t_, writer_.second); + finder.update_detector(); + return placement_; + } + //No placement for this point. Keep it in points_ for next try. + + } + return text_placement_info_ptr(); +} + + +template +void text_symbolizer_helper::initialize_geometries() +{ + unsigned num_geom = feature_.num_geometries(); + for (unsigned i=0; i 0) + { + // TODO - find less costly method than fetching full envelope + box2d gbox = t_.forward(geom.envelope(), prj_trans_); + if (gbox.width() < sym_.get_minimum_path_length()) + { + continue; + } + } + // TODO - calculate length here as well + geometries_to_process_.push_back(const_cast(&geom)); + } + geo_itr_ = geometries_to_process_.begin(); +} + +template +void text_symbolizer_helper::initialize_points() +{ + label_placement_enum how_placed = placement_->properties.label_placement; + if (how_placed == LINE_PLACEMENT) { + point_placement_ = false; + return; + } else { + point_placement_ = true; + } + + double label_x=0.0; + double label_y=0.0; + double z=0.0; + + std::list::const_iterator itr = geometries_to_process_.begin(); + std::list::const_iterator end = geometries_to_process_.end(); + for (; itr != end; itr++) + { + geometry_type const& geom = **itr; + if (how_placed == VERTEX_PLACEMENT) + { + geom.rewind(0); + for(unsigned i = 0; i < geom.num_points(); i++) + { + 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)); + } + } else { + if (how_placed == POINT_PLACEMENT) + { + geom.label_position(&label_x, &label_y); + } else if (how_placed == INTERIOR_PLACEMENT) + { + geom.label_interior_position(&label_x, &label_y); + } else { +#ifdef MAPNIK_DEBUG + std::cerr << "ERROR: Unknown placement type in initialize_points();\n"; +#endif + } + prj_trans_.backward(label_x, label_y, z); + t_.forward(&label_x, &label_y); + points_.push_back(std::make_pair(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.processor.process(text_, feature_); + info_ = &(text_.get_string_info()); + if (placement_->properties.orientation) + { + angle_ = boost::apply_visitor( + evaluate(feature_), + *(placement_->properties.orientation)).to_double(); + } else { + angle_ = 0.0; + } + return true; +} + + +/*****************************************************************************/ + + +template +text_placement_info_ptr shield_symbolizer_helper::get_placement() +{ + if (!placement_valid_ || !marker_) return text_placement_info_ptr(); + if (point_placement_) + return get_point_placement(); + else + return get_line_placement(); +} + +template +text_placement_info_ptr shield_symbolizer_helper::get_point_placement() +{ + position const& shield_pos = sym_.get_shield_displacement(); + while (points_.size()) + { + if (point_itr_ == points_.end()) + { + //Just processed the last point. Try next placement. + if (!next_placement()) return text_placement_info_ptr(); //No more placements + //Start again from begin of list + point_itr_ = points_.begin(); + continue; //Reexecute size check + } + position const& pos = placement_->properties.displacement; + double label_x = point_itr_->first + boost::get<0>(shield_pos); + double label_y = point_itr_->second + boost::get<1>(shield_pos); + + placement_finder finder(*placement_, *info_, detector_, dims_); + finder.find_point_placement(label_x, label_y, angle_); + //Keep reference to current object so we can delete it. + std::list::iterator current_object = point_itr_; + point_itr_++; + if (!placement_->placements.size()) + { + //No placement for this point. Keep it in points_ for next try. + 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 + double x = floor(placement_->placements[0].starting_x); + double y = floor(placement_->placements[0].starting_y); + if (!sym_.get_unlock_image()) + { + // center image at text center position + // remove displacement from image label + double lx = x - boost::get<0>(pos); + double ly = y - boost::get<1>(pos); + marker_x_ = int(floor(lx - (0.5 * marker_w_))) + 1; + marker_y_ = int(floor(ly - (0.5 * marker_h_))) + 1; + marker_ext_.re_center(lx, ly); + } + else + { // center image at reference location + marker_x_ = int(floor(label_x - 0.5 * marker_w_)); + marker_y_ = int(floor(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(); + if (writer_.first) { + writer_.first->add_box(marker_ext_, feature_, t_, writer_.second); + writer_.first->add_text(*placement_, font_manager_, feature_, t_, writer_.second); + } + points_.erase(current_object); + return placement_; + } + } + return text_placement_info_ptr(); + +} + + +template +text_placement_info_ptr shield_symbolizer_helper::get_line_placement() +{ +#if 0 + TODO: Not supported by placement_finder atm + position const& pos = placement_->properties.displacement; + text_placement.additional_boxes.push_back( + box2d(-0.5 * label_ext.width() - boost::get<0>(pos), + -0.5 * label_ext.height() - boost::get<1>(pos), + 0.5 * label_ext.width() - boost::get<0>(pos), + 0.5 * label_ext.height() - boost::get<1>(pos))); +#endif + return text_symbolizer_helper::get_line_placement(); +} + + +template +void shield_symbolizer_helper::init_marker() +{ + std::string filename = path_processor_type::evaluate(*sym_.get_filename(), this->feature_); + boost::array const& m = sym_.get_transform(); + transform_.load_from(&m[0]); + marker_.reset(); + if (!filename.empty()) + { + 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_; + double px2 = px1; + double py2 = py0; + double px3 = px0; + double py3 = py1; + transform_.transform(&px0,&py0); + transform_.transform(&px1,&py1); + transform_.transform(&px2,&py2); + 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); +} + +template +std::pair shield_symbolizer_helper::get_marker_position(text_path &p) +{ + position const& pos = placement_->properties.displacement; + if (placement_->properties.label_placement == LINE_PLACEMENT) { + double x = floor(p.starting_x); + double y = floor(p.starting_y); + + double lx = x - boost::get<0>(pos); + double ly = y - boost::get<1>(pos); + int px = int(floor(lx - (0.5*marker_w_))) + 1; + int py = int(floor(ly - (0.5*marker_h_))) + 1; + marker_ext_.re_center(lx, ly); +// detector_->insert(label_ext); //TODO: Is this done by placement_finder? + + if (writer_.first) writer_.first->add_box(marker_ext_, feature_, t_, writer_.second); + return std::make_pair(px, py); + } else { + return std::make_pair(marker_x_, marker_y_); + } +} + + +template +marker& shield_symbolizer_helper::get_marker() const +{ + return **marker_; +} + +template +agg::trans_affine const& shield_symbolizer_helper::get_transform() const +{ + return transform_; +} + +template class text_symbolizer_helper, label_collision_detector4>; +template class shield_symbolizer_helper, label_collision_detector4>; +} //namespace From 3e59503e25bb4dd4359344d8818429cef5cd8ddc Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 29 Jan 2012 04:57:56 +0100 Subject: [PATCH 72/81] ShieldSymbolizer for Grid renderer. --- src/grid/process_shield_symbolizer.cpp | 209 +++---------------------- src/grid/process_text_symbolizer.cpp | 1 + 2 files changed, 23 insertions(+), 187 deletions(-) diff --git a/src/grid/process_shield_symbolizer.cpp b/src/grid/process_shield_symbolizer.cpp index c286f7409..b1aac00e6 100644 --- a/src/grid/process_shield_symbolizer.cpp +++ b/src/grid/process_shield_symbolizer.cpp @@ -28,8 +28,8 @@ #include #include -#include -#include +#include + #include #include #include @@ -44,201 +44,36 @@ void grid_renderer::process(shield_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { -#if 0 - typedef coord_transform2 path_type; + shield_symbolizer_helper, + label_collision_detector4> helper( + sym, feature, prj_trans, + width_, height_, + scale_factor_, + t_, font_manager_, detector_); bool placement_found = false; - text_placement_info_ptr placement_options = sym.get_placement_options()->get_placement_info(); - placement_options->next(); - placement_options->next_position_only(); + text_renderer ren(pixmap_, font_manager_, *(font_manager_.get_stroker())); - UnicodeString text; - if( sym.get_no_text() ) - text = UnicodeString( " " ); // TODO: fix->use 'space' as the text to render - else - { - expression_ptr name_expr = sym.get_name(); - if (!name_expr) return; - value_type result = boost::apply_visitor(evaluate(feature),*name_expr); - text = result.to_unicode(); - } - - if ( sym.get_text_transform() == UPPERCASE) - { - text = text.toUpper(); - } - else if ( sym.get_text_transform() == LOWERCASE) - { - text = text.toLower(); - } - else if ( sym.get_text_transform() == CAPITALIZE) - { - text = text.toTitle(NULL); - } - - agg::trans_affine tr; - boost::array const& m = sym.get_transform(); - tr.load_from(&m[0]); - tr = agg::trans_affine_scaling(scale_factor_) * tr; - - std::string filename = path_processor_type::evaluate( *sym.get_filename(), feature); - boost::optional marker; - if ( !filename.empty() ) - { - marker = marker_cache::instance()->find(filename, true); - } - else - { - marker.reset(boost::make_shared()); - } - - if (text.length() > 0 && marker) - { - face_set_ptr faces; - - if (sym.get_fontset().size() > 0) + text_placement_info_ptr placement; + while ((placement = helper.get_placement())) { + placement_found = true; + for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) { - faces = font_manager_.get_face_set(sym.get_fontset()); - } - else - { - faces = font_manager_.get_face_set(sym.get_face_name()); - } + std::pair marker_pos = helper.get_marker_position(placement->placements[ii]); + render_marker(feature, pixmap_.get_resolution(), + marker_pos.first, marker_pos.second, + helper.get_marker(), helper.get_transform(), + sym.get_opacity()); - stroker_ptr strk = font_manager_.get_stroker(); - if (strk && faces->size() > 0) - { - text_renderer ren(pixmap_, faces, *strk); - - ren.set_character_size(sym.get_text_size() * scale_factor_ * (1.0/pixmap_.get_resolution())); - ren.set_fill(sym.get_fill()); - ren.set_halo_fill(sym.get_halo_fill()); - ren.set_halo_radius(sym.get_halo_radius() * scale_factor_); - ren.set_opacity(sym.get_text_opacity()); - - placement_finder finder(detector_); - - string_info info(text); - - faces->get_string_info(info, text, 0); - - // TODO- clamp to at least 4 px otherwise interactivity is too small - int w = (*marker)->width()/pixmap_.get_resolution(); - int h = (*marker)->height()/pixmap_.get_resolution(); - - for (unsigned i = 0; i < feature.num_geometries(); ++i) - { - geometry_type const& geom = feature.get_geometry(i); - if (geom.num_points() > 0 ) - { - path_type path(t_,geom,prj_trans); - - label_placement_enum how_placed = sym.get_label_placement(); - if (how_placed == POINT_PLACEMENT || how_placed == VERTEX_PLACEMENT || how_placed == INTERIOR_PLACEMENT) - { - // for every vertex, try and place a shield/text - geom.rewind(0); - placement text_placement(info, sym, scale_factor_, w, h, false); - text_placement.avoid_edges = sym.get_avoid_edges(); - text_placement.allow_overlap = sym.get_allow_overlap(); - position const& pos = sym.get_displacement(); - position const& shield_pos = sym.get_shield_displacement(); - for( unsigned jj = 0; jj < geom.num_points(); jj++ ) - { - double label_x; - double label_y; - double z=0.0; - - if( how_placed == VERTEX_PLACEMENT ) - geom.vertex(&label_x,&label_y); // by vertex - else if( how_placed == INTERIOR_PLACEMENT ) - geom.label_interior_position(&label_x,&label_y); - else - geom.label_position(&label_x, &label_y); // by middle of line or by point - prj_trans.backward(label_x,label_y, z); - t_.forward(&label_x,&label_y); - - label_x += boost::get<0>(shield_pos); - label_y += boost::get<1>(shield_pos); - - finder.find_point_placement( text_placement, placement_options, label_x, label_y, 0.0, - sym.get_line_spacing(), - sym.get_character_spacing()); - - // check to see if image overlaps anything too, there is only ever 1 placement found for points and verticies - if( text_placement.placements.size() > 0) - { - placement_found = true; - double x = floor(text_placement.placements[0].starting_x); - double y = floor(text_placement.placements[0].starting_y); - int px; - int py; - box2d label_ext; - - if( !sym.get_unlock_image() ) - { - // center image at text center position - // remove displacement from image label - double lx = x - boost::get<0>(pos); - double ly = y - boost::get<1>(pos); - px=int(floor(lx - (0.5 * w))); - py=int(floor(ly - (0.5 * h))); - label_ext.init( floor(lx - 0.5 * w), floor(ly - 0.5 * h), ceil (lx + 0.5 * w), ceil (ly + 0.5 * h) ); - } - else - { // center image at reference location - px=int(floor(label_x - 0.5 * w)); - py=int(floor(label_y - 0.5 * h)); - label_ext.init( floor(label_x - 0.5 * w), floor(label_y - 0.5 * h), ceil (label_x + 0.5 * w), ceil (label_y + 0.5 * h)); - } - - if ( sym.get_allow_overlap() || detector_.has_placement(label_ext) ) - { - render_marker(feature,pixmap_.get_resolution(),px,py,**marker,tr,sym.get_opacity()); - - box2d dim = ren.prepare_glyphs(&text_placement.placements[0]); - ren.render_id(feature.id(),x,y,2); - detector_.insert(label_ext); - finder.update_detector(text_placement); - } - } - } - } - - else if (geom.num_points() > 1 && how_placed == LINE_PLACEMENT) - { - placement text_placement(info, sym, scale_factor_, w, h, true); - - text_placement.avoid_edges = sym.get_avoid_edges(); - finder.find_point_placements(text_placement, placement_options, path); - - position const& pos = sym.get_displacement(); - for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ ii) - { - placement_found= true; - double x = floor(text_placement.placements[ii].starting_x); - double y = floor(text_placement.placements[ii].starting_y); - - double lx = x - boost::get<0>(pos); - double ly = y - boost::get<1>(pos); - int px=int(floor(lx - (0.5*w))); - int py=int(floor(ly - (0.5*h))); - - render_marker(feature,pixmap_.get_resolution(),px,py,**marker,tr,sym.get_opacity()); - - box2d dim = ren.prepare_glyphs(&text_placement.placements[ii]); - ren.render_id(feature.id(),x,y,2); - } - finder.update_detector(text_placement); - } - } - } + double x = floor(placement->placements[ii].starting_x); + double y = floor(placement->placements[ii].starting_y); + ren.prepare_glyphs(&(placement->placements[ii])); + ren.render_id(feature.id(), x, y, 2); } } if (placement_found) pixmap_.add_feature(feature); -#endif } 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 041106975..ae61d13d4 100644 --- a/src/grid/process_text_symbolizer.cpp +++ b/src/grid/process_text_symbolizer.cpp @@ -44,6 +44,7 @@ void grid_renderer::process(text_symbolizer const& sym, text_placement_info_ptr placement; while ((placement = helper.get_placement())) { + placement_found = true; for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) { double x = placement->placements[ii].starting_x; From 4a3a3fd82cce2a3ac629bf49f111d5b097cfe4d4 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 29 Jan 2012 05:03:38 +0100 Subject: [PATCH 73/81] ShieldSymbolizer for Cairo. --- src/cairo_renderer.cpp | 211 +++-------------------------------------- 1 file changed, 15 insertions(+), 196 deletions(-) diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 1d060d47d..9be77f314 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -1051,207 +1051,26 @@ void cairo_renderer_base::process(shield_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { -#if 0 - typedef coord_transform2 path_type; + shield_symbolizer_helper, + label_collision_detector4> helper( + sym, feature, prj_trans, + detector_.extent().width(), detector_.extent().height(), + 1.0 /*scale_factor*/, + t_, font_manager_, detector_); - text_placement_info_ptr placement_options = sym.get_placement_options()->get_placement_info(); - placement_options->next(); + cairo_context context(context_); - UnicodeString text; - if( sym.get_no_text() ) - text = UnicodeString( " " ); // TODO: fix->use 'space' as the text to render - else - { - expression_ptr name_expr = sym.get_name(); - if (!name_expr) return; - value_type result = boost::apply_visitor(evaluate(feature),*name_expr); - text = result.to_unicode(); - } - - if ( sym.get_text_transform() == UPPERCASE) - { - text = text.toUpper(); - } - else if ( sym.get_text_transform() == LOWERCASE) - { - text = text.toLower(); - } - else if ( sym.get_text_transform() == CAPITALIZE) - { - text = text.toTitle(NULL); - } - - agg::trans_affine tr; - boost::array const& m = sym.get_transform(); - tr.load_from(&m[0]); - - std::string filename = path_processor_type::evaluate( *sym.get_filename(), feature); - boost::optional marker; - if ( !filename.empty() ) - { - marker = marker_cache::instance()->find(filename, true); - } - else - { - marker.reset(boost::make_shared()); - } - - if (text.length() > 0 && marker) - { - face_set_ptr faces; - - if (sym.get_fontset().size() > 0) + text_placement_info_ptr placement; + while ((placement = helper.get_placement())) { + for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) { - faces = font_manager_.get_face_set(sym.get_fontset()); - } - else - { - faces = font_manager_.get_face_set(sym.get_face_name()); - } - - if (faces->size() > 0) - { - cairo_context context(context_); - string_info info(text); - - placement_finder finder(detector_); - - faces->set_character_sizes(placement_options->text_size); - faces->get_string_info(info, text, 0); - - int w = (*marker)->width(); - int h = (*marker)->height(); - - metawriter_with_properties writer = sym.get_metawriter(); - - for (unsigned i = 0; i < feature.num_geometries(); ++i) - { - geometry_type const& geom = feature.get_geometry(i); - if (geom.num_points() > 0) // don't bother with empty geometries - { - path_type path(t_, geom, prj_trans); - - label_placement_enum how_placed = sym.get_label_placement(); - if (how_placed == POINT_PLACEMENT || how_placed == VERTEX_PLACEMENT || how_placed == INTERIOR_PLACEMENT) - { - // for every vertex, try and place a shield/text - geom.rewind(0); - placement text_placement(info, sym, 1.0, w, h, false); - text_placement.avoid_edges = sym.get_avoid_edges(); - text_placement.allow_overlap = sym.get_allow_overlap(); - if (writer.first) - text_placement.collect_extents = true; // needed for inmem metawriter - position const& pos = sym.get_displacement(); - position const& shield_pos = sym.get_shield_displacement(); - for( unsigned jj = 0; jj < geom.num_points(); jj++ ) - { - double label_x; - double label_y; - double z=0.0; - - if( how_placed == VERTEX_PLACEMENT ) - geom.vertex(&label_x,&label_y); // by vertex - else if( how_placed == INTERIOR_PLACEMENT ) - geom.label_interior_position(&label_x,&label_y); - else - geom.label_position(&label_x, &label_y); // by middle of line or by point - prj_trans.backward(label_x,label_y, z); - t_.forward(&label_x,&label_y); - - label_x += boost::get<0>(shield_pos); - label_y += boost::get<1>(shield_pos); - - finder.find_point_placement(text_placement, placement_options, - label_x, label_y, 0.0, - sym.get_line_spacing(), - sym.get_character_spacing()); - - for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ ii) - { - double x = text_placement.placements[ii].starting_x; - double y = text_placement.placements[ii].starting_y; - - int px; - int py; - box2d label_ext; - - if( !sym.get_unlock_image() ) - { - // center image at text center position - // remove displacement from image label - double lx = x - boost::get<0>(pos); - double ly = y - boost::get<1>(pos); - px=int(floor(lx - (0.5 * w))); - py=int(floor(ly - (0.5 * h))); - label_ext.init( floor(lx - 0.5 * w), floor(ly - 0.5 * h), ceil (lx + 0.5 * w), ceil (ly + 0.5 * h) ); - } - else - { // center image at reference location - px=int(floor(label_x - 0.5 * w)); - py=int(floor(label_y - 0.5 * h)); - label_ext.init( floor(label_x - 0.5 * w), floor(label_y - 0.5 * h), ceil (label_x + 0.5 * w), ceil (label_y + 0.5 * h)); - } - - if ( sym.get_allow_overlap() || detector_.has_placement(label_ext) ) - { - render_marker(px,py,**marker, tr, sym.get_opacity()); - - context.add_text(text_placement.placements[ii], - face_manager_, - faces, - placement_options->text_size, - sym.get_fill(), - sym.get_halo_radius(), - sym.get_halo_fill() - ); - if (writer.first) { - writer.first->add_box(box2d(px,py,px+w,py+h), feature, t_, writer.second); - writer.first->add_text(text_placement, faces, feature, t_, writer.second); //Only 1 placement - } - detector_.insert(label_ext); - } - } - - finder.update_detector(text_placement); - } - } - else if (geom.num_points() > 1 && how_placed == LINE_PLACEMENT) - { - placement text_placement(info, sym, 1.0, w, h, true); - - text_placement.avoid_edges = sym.get_avoid_edges(); - finder.find_point_placements(text_placement, placement_options, path); - - position const& pos = sym.get_displacement(); - for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ ii) - { - double x = text_placement.placements[ii].starting_x; - double y = text_placement.placements[ii].starting_y; - double lx = x - boost::get<0>(pos); - double ly = y - boost::get<1>(pos); - int px=int(floor(lx - (0.5*w))); - int py=int(floor(ly - (0.5*h))); - - render_marker(px,py,**marker, tr, sym.get_opacity()); - - context.add_text(text_placement.placements[ii], - face_manager_, - faces, - placement_options->text_size, - sym.get_fill(), - sym.get_halo_radius(), - sym.get_halo_fill() - ); - if (writer.first) writer.first->add_box(box2d(px,py,px+w,py+h), feature, t_, writer.second); - } - finder.update_detector(text_placement); - if (writer.first) writer.first->add_text(text_placement, faces, feature, t_, writer.second); //More than one placement - } - } - } + std::pair marker_pos = helper.get_marker_position(placement->placements[ii]); + render_marker(marker_pos.first, marker_pos.second, + helper.get_marker(), helper.get_transform(), + sym.get_opacity()); + context.add_text(placement->placements[ii], face_manager_, font_manager_); } } -#endif } void cairo_renderer_base::process(line_pattern_symbolizer const& sym, From e564b00370de8e4f83918ea9007f98ce82ec0b08 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 29 Jan 2012 05:11:59 +0100 Subject: [PATCH 74/81] New test. --- tests/visual_tests/clean.sh | 3 +-- .../shieldsymbolizer-1-500-reference.png | Bin 0 -> 883 bytes tests/visual_tests/shieldsymbolizer-1.xml | 19 ++++++++++++++++++ tests/visual_tests/test.py | 3 ++- 4 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 tests/visual_tests/shieldsymbolizer-1-500-reference.png create mode 100644 tests/visual_tests/shieldsymbolizer-1.xml diff --git a/tests/visual_tests/clean.sh b/tests/visual_tests/clean.sh index 8733c453a..5184aa6ca 100755 --- a/tests/visual_tests/clean.sh +++ b/tests/visual_tests/clean.sh @@ -1,6 +1,5 @@ rm -f list-[0-9][0-9]0-agg.png rm -f simple-[0-9][0-9]0-agg.png -rm -f simple-{E,N,NE,NW,N,SE,SW,S,W}-500-agg.png -rm -f formating-500-agg.png +rm -f *-500-agg.png rm -f *-out.xml diff --git a/tests/visual_tests/shieldsymbolizer-1-500-reference.png b/tests/visual_tests/shieldsymbolizer-1-500-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..820509dd58890285828e4eb71b3ea535f8c6025c GIT binary patch literal 883 zcmeAS@N?(olHy`uVBq!ia0y~yVEh7Pr*NELn`LHy=&Md5-8I8 zu)T4jl;n&oH7Tj+yaQK49AjEN-XFNxtk5;lx`44wO=;FnMpqU`jr^OUyo(abeXm>p ztu2_GCh@oOpd{NtGbV<++3o+h85njOzhDqJuz=Ztp_z|?k;jUG#el8?4RhK4K6!H1 zf@k*LdtYqN&*GD{DtU3?V)b#qX1l)!*YcLmw!Qn|clA6|u=v8o-}`KNew93)msfm% z{o+FBcA)xSZ!#z5noxgWQcJY(>O4oj%NSDlirFZr2`riTN8{a*QN0sIKRkHrrTYu&7S9w=z-+yof z+t}Xs6=yUwPOJayZZ*uir;xT-bjlcJ=Pq@5TS#+}!bZ>fzIUaxNe*mb`eeIC-Jt{_ygh z?>?_QFW+;!{^R$Jli?xo0;u)>_iNt}e$iEhYP5LPx2^hw!SOeb+nDV-puw9D4Bmaq u{ + + + + + My Style + + shape + points.shp + + + + + + diff --git a/tests/visual_tests/test.py b/tests/visual_tests/test.py index ac8ed4f89..616b0cb4b 100755 --- a/tests/visual_tests/test.py +++ b/tests/visual_tests/test.py @@ -12,7 +12,8 @@ widths = [ 800, 600, 400, 300, 250, 200, 150, 100] filenames = ["list", "simple"] filenames_one_width = ["simple-E", "simple-NE", "simple-NW", "simple-N", "simple-SE", "simple-SW", "simple-S", "simple-W", - "formating-1", "formating-2", "formating-3", "formating-4"] + "formating-1", "formating-2", "formating-3", "formating-4", + "shieldsymbolizer-1"] def render(filename, width): print "Rendering style \"%s\" with width %d" % (filename, width) From 822786e41c7a5c451199ff64e029c5fdfe4d7a88 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 29 Jan 2012 13:10:14 +0100 Subject: [PATCH 75/81] Remove no-text attribute. One can simply leave the text empty if one wants this. --- bindings/python/mapnik_shield_symbolizer.cpp | 3 --- include/mapnik/shield_symbolizer.hpp | 3 --- src/save_map.cpp | 4 ---- src/shield_symbolizer.cpp | 13 ------------- tests/data/good_maps/shield_symbolizer.xml | 6 +++--- utils/xml/mapnik2.dtd | 1 - 6 files changed, 3 insertions(+), 27 deletions(-) diff --git a/bindings/python/mapnik_shield_symbolizer.cpp b/bindings/python/mapnik_shield_symbolizer.cpp index e3180845e..bb38d7a56 100644 --- a/bindings/python/mapnik_shield_symbolizer.cpp +++ b/bindings/python/mapnik_shield_symbolizer.cpp @@ -223,9 +223,6 @@ void export_shield_symbolizer() .add_property("wrap_before", &shield_symbolizer::get_wrap_before, &shield_symbolizer::set_wrap_before) - .add_property("no_text", - &shield_symbolizer::get_no_text, - &shield_symbolizer::set_no_text) .add_property("unlock_image", &shield_symbolizer::get_unlock_image, &shield_symbolizer::set_unlock_image) diff --git a/include/mapnik/shield_symbolizer.hpp b/include/mapnik/shield_symbolizer.hpp index b5aaf94e8..59ed30034 100644 --- a/include/mapnik/shield_symbolizer.hpp +++ b/include/mapnik/shield_symbolizer.hpp @@ -50,14 +50,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); - bool get_no_text() const; // do no render text - void set_no_text(bool unlock_image); void set_shield_displacement(double shield_dx,double shield_dy); boost::tuple const& get_shield_displacement() const; private: bool unlock_image_; - bool no_text_; boost::tuple shield_displacement_; }; } diff --git a/src/save_map.cpp b/src/save_map.cpp index 87bc14c4f..551d3e341 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -205,10 +205,6 @@ public: { set_attr( sym_node, "unlock-image", sym.get_unlock_image() ); } - if (sym.get_no_text() != dfl.get_no_text() || explicit_defaults_ ) - { - set_attr( sym_node, "no-text", sym.get_no_text() ); - } if (sym.get_text_opacity() != dfl.get_text_opacity() || explicit_defaults_ ) { set_attr( sym_node, "text-opacity", sym.get_text_opacity() ); diff --git a/src/shield_symbolizer.cpp b/src/shield_symbolizer.cpp index 5de905935..add50900f 100644 --- a/src/shield_symbolizer.cpp +++ b/src/shield_symbolizer.cpp @@ -38,7 +38,6 @@ shield_symbolizer::shield_symbolizer(text_placements_ptr placements) : text_symbolizer(placements), symbolizer_with_image(), unlock_image_(false), - no_text_(false), shield_displacement_(boost::make_tuple(0,0)) { } @@ -52,7 +51,6 @@ shield_symbolizer::shield_symbolizer( : text_symbolizer(name, face_name, size, fill), symbolizer_with_image(file), unlock_image_(false), - no_text_(false), shield_displacement_(boost::make_tuple(0,0)) { } @@ -65,7 +63,6 @@ shield_symbolizer::shield_symbolizer( : text_symbolizer(name, size, fill), symbolizer_with_image(file), unlock_image_(false), - no_text_(false), shield_displacement_(boost::make_tuple(0,0)) { } @@ -80,16 +77,6 @@ bool shield_symbolizer::get_unlock_image() const return unlock_image_; } -void shield_symbolizer::set_no_text(bool no_text) -{ - no_text_ = no_text; -} - -bool shield_symbolizer::get_no_text() const -{ - return no_text_; -} - void shield_symbolizer::set_shield_displacement(double shield_dx,double shield_dy) { shield_displacement_ = boost::make_tuple(shield_dx,shield_dy); diff --git a/tests/data/good_maps/shield_symbolizer.xml b/tests/data/good_maps/shield_symbolizer.xml index 5a4da13ad..e0221fa64 100644 --- a/tests/data/good_maps/shield_symbolizer.xml +++ b/tests/data/good_maps/shield_symbolizer.xml @@ -2,7 +2,7 @@ @@ -35,4 +35,4 @@ - \ No newline at end of file + diff --git a/utils/xml/mapnik2.dtd b/utils/xml/mapnik2.dtd index 9dd639a9c..3bd1a8e43 100644 --- a/utils/xml/mapnik2.dtd +++ b/utils/xml/mapnik2.dtd @@ -196,7 +196,6 @@ line_spacing CDATA "0" min_distance CDATA "0" name CDATA #IMPLIED - no_text (true|false) "false" placement (point|line|vertex) "point" size CDATA "10" spacing CDATA "0" From 49a3b3c52c6cbeff6b02c754fc97df90436645dd Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 29 Jan 2012 17:33:43 +0100 Subject: [PATCH 76/81] Make code easier to read by using std::pair instead of boost::tuple for position. --- bindings/python/mapnik_shield_symbolizer.cpp | 9 ++--- bindings/python/mapnik_text_symbolizer.cpp | 4 +-- include/mapnik/shield_symbolizer.hpp | 4 +-- include/mapnik/symbolizer_helpers.hpp | 6 ++-- include/mapnik/text_placements.hpp | 2 +- src/placement_finder.cpp | 14 ++++---- src/save_map.cpp | 22 ++++++------ src/shield_symbolizer.cpp | 10 +++--- src/symbolizer_helpers.cpp | 24 ++++++------- src/text_placements.cpp | 36 ++++++++------------ src/text_symbolizer.cpp | 2 +- 11 files changed, 62 insertions(+), 71 deletions(-) diff --git a/bindings/python/mapnik_shield_symbolizer.cpp b/bindings/python/mapnik_shield_symbolizer.cpp index bb38d7a56..5da6bad6a 100644 --- a/bindings/python/mapnik_shield_symbolizer.cpp +++ b/bindings/python/mapnik_shield_symbolizer.cpp @@ -37,6 +37,7 @@ using mapnik::path_expression_ptr; using mapnik::guess_type; using mapnik::expression_ptr; using mapnik::parse_path; +using mapnik::position; namespace { @@ -44,8 +45,8 @@ using namespace boost::python; tuple get_shield_displacement(const shield_symbolizer& s) { - boost::tuple pos = s.get_shield_displacement(); - return boost::python::make_tuple(boost::get<0>(pos),boost::get<1>(pos)); + position const& pos = s.get_shield_displacement(); + return boost::python::make_tuple(pos.first, pos.second); } void set_shield_displacement(shield_symbolizer & s, boost::python::tuple arg) @@ -55,8 +56,8 @@ void set_shield_displacement(shield_symbolizer & s, boost::python::tuple arg) tuple get_text_displacement(const shield_symbolizer& t) { - boost::tuple pos = t.get_displacement(); - return boost::python::make_tuple(boost::get<0>(pos),boost::get<1>(pos)); + position const& pos = t.get_displacement(); + return boost::python::make_tuple(pos.first, pos.second); } void set_text_displacement(shield_symbolizer & t, boost::python::tuple arg) diff --git a/bindings/python/mapnik_text_symbolizer.cpp b/bindings/python/mapnik_text_symbolizer.cpp index 31515f11a..ccb5b99e0 100644 --- a/bindings/python/mapnik_text_symbolizer.cpp +++ b/bindings/python/mapnik_text_symbolizer.cpp @@ -39,8 +39,8 @@ using namespace boost::python; tuple get_text_displacement(const text_symbolizer& t) { - position pos = t.get_displacement(); - return boost::python::make_tuple(boost::get<0>(pos),boost::get<1>(pos)); + mapnik::position const& pos = t.get_displacement(); + return boost::python::make_tuple(pos.first, pos.second); } void set_text_displacement(text_symbolizer & t, boost::python::tuple arg) diff --git a/include/mapnik/shield_symbolizer.hpp b/include/mapnik/shield_symbolizer.hpp index 59ed30034..06b12fb2e 100644 --- a/include/mapnik/shield_symbolizer.hpp +++ b/include/mapnik/shield_symbolizer.hpp @@ -51,11 +51,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); - boost::tuple const& get_shield_displacement() const; + position const& get_shield_displacement() const; private: bool unlock_image_; - boost::tuple shield_displacement_; + position shield_displacement_; }; } diff --git a/include/mapnik/symbolizer_helpers.hpp b/include/mapnik/symbolizer_helpers.hpp index 1b50931d3..4869f07eb 100644 --- a/include/mapnik/symbolizer_helpers.hpp +++ b/include/mapnik/symbolizer_helpers.hpp @@ -36,8 +36,6 @@ namespace mapnik { -typedef std::pair point_type; - /** Helper object that does all the TextSymbolizer placment finding * work except actually rendering the object. */ template @@ -100,8 +98,8 @@ protected: /* Using list instead of vector, because we delete random elements and need iterators to stay valid. */ std::list geometries_to_process_; std::list::iterator geo_itr_; - std::list points_; - std::list::iterator point_itr_; + std::list points_; + std::list::iterator point_itr_; double angle_; string_info *info_; bool placement_valid_; diff --git a/include/mapnik/text_placements.hpp b/include/mapnik/text_placements.hpp index bb6ee9df4..d4e159c55 100644 --- a/include/mapnik/text_placements.hpp +++ b/include/mapnik/text_placements.hpp @@ -46,7 +46,7 @@ namespace mapnik { class text_placements; -typedef boost::tuple position; +typedef std::pair position; enum label_placement_enum { POINT_PLACEMENT, diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index 1ab6cbd99..a2d3019b8 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -291,9 +291,9 @@ void placement_finder::init_alignment() { valign_ = p.valign; if (valign_ == V_AUTO) { - if (p.displacement.get<1>() > 0.0) + if (p.displacement.second > 0.0) valign_ = V_BOTTOM; - else if (p.displacement.get<1>() < 0.0) + else if (p.displacement.second < 0.0) valign_ = V_TOP; else valign_ = V_MIDDLE; @@ -301,9 +301,9 @@ void placement_finder::init_alignment() halign_ = p.halign; if (halign_ == H_AUTO) { - if (p.displacement.get<0>() > 0.0) + if (p.displacement.first > 0.0) halign_ = H_RIGHT; - else if (p.displacement.get<0>() < 0.0) + else if (p.displacement.first < 0.0) halign_ = H_LEFT; else halign_ = H_MIDDLE; @@ -333,8 +333,8 @@ void placement_finder::adjust_position(text_path *current_placement, current_placement->starting_x += 0.5 * string_width_; // move center right by 1/2 the string width // adjust text envelope position by user's x-y displacement (dx, dy) - current_placement->starting_x += pi.get_scale_factor() * boost::tuples::get<0>(p.displacement); - current_placement->starting_y += pi.get_scale_factor() * boost::tuples::get<1>(p.displacement); + current_placement->starting_x += pi.get_scale_factor() * p.displacement.first; + current_placement->starting_y += pi.get_scale_factor() * p.displacement.second; } @@ -537,7 +537,7 @@ void placement_finder::find_line_placements(PathT & shape_path) double distance = 0.0; - double displacement = boost::tuples::get<1>(p.displacement); // displace by dy + double displacement = p.displacement.second; // displace by dy //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 diff --git a/src/save_map.cpp b/src/save_map.cpp index 551d3e341..13912a52d 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -189,8 +189,8 @@ public: ptree::value_type("ShieldSymbolizer", ptree()))->second; - add_font_attributes( sym_node, sym); - add_image_attributes( sym_node, sym); + add_font_attributes(sym_node, sym); + add_image_attributes(sym_node, sym); add_metawriter_attributes(sym_node, sym); // pseudo-default-construct a shield_symbolizer. It is used @@ -199,24 +199,24 @@ public: // maybe add a real, explicit default-ctor? - shield_symbolizer dfl(expression_ptr(), "", 0, color(0,0,0), path_expression_ptr()); + shield_symbolizer dfl; - if (sym.get_unlock_image() != dfl.get_unlock_image() || explicit_defaults_ ) + if (sym.get_unlock_image() != dfl.get_unlock_image() || explicit_defaults_) { - set_attr( sym_node, "unlock-image", sym.get_unlock_image() ); + set_attr(sym_node, "unlock-image", sym.get_unlock_image()); } - if (sym.get_text_opacity() != dfl.get_text_opacity() || explicit_defaults_ ) + if (sym.get_text_opacity() != dfl.get_text_opacity() || explicit_defaults_) { - set_attr( sym_node, "text-opacity", sym.get_text_opacity() ); + set_attr(sym_node, "text-opacity", sym.get_text_opacity()); } position displacement = sym.get_shield_displacement(); - if ( displacement.get<0>() != dfl.get_shield_displacement().get<0>() || explicit_defaults_ ) + if (displacement.first != dfl.get_shield_displacement().first || explicit_defaults_) { - set_attr( sym_node, "shield-dx", displacement.get<0>() ); + set_attr(sym_node, "shield-dx", displacement.first); } - if ( displacement.get<1>() != dfl.get_shield_displacement().get<1>() || explicit_defaults_ ) + if (displacement.second != dfl.get_shield_displacement().second || explicit_defaults_) { - set_attr( sym_node, "shield-dy", displacement.get<1>() ); + set_attr(sym_node, "shield-dy", displacement.second); } } diff --git a/src/shield_symbolizer.cpp b/src/shield_symbolizer.cpp index add50900f..c5a03a453 100644 --- a/src/shield_symbolizer.cpp +++ b/src/shield_symbolizer.cpp @@ -38,7 +38,7 @@ shield_symbolizer::shield_symbolizer(text_placements_ptr placements) : text_symbolizer(placements), symbolizer_with_image(), unlock_image_(false), - shield_displacement_(boost::make_tuple(0,0)) + shield_displacement_(0,0) { } @@ -51,7 +51,7 @@ shield_symbolizer::shield_symbolizer( : text_symbolizer(name, face_name, size, fill), symbolizer_with_image(file), unlock_image_(false), - shield_displacement_(boost::make_tuple(0,0)) + shield_displacement_(0, 0) { } @@ -63,7 +63,7 @@ shield_symbolizer::shield_symbolizer( : text_symbolizer(name, size, fill), symbolizer_with_image(file), unlock_image_(false), - shield_displacement_(boost::make_tuple(0,0)) + shield_displacement_(0, 0) { } @@ -79,10 +79,10 @@ bool shield_symbolizer::get_unlock_image() const void shield_symbolizer::set_shield_displacement(double shield_dx,double shield_dy) { - shield_displacement_ = boost::make_tuple(shield_dx,shield_dy); + shield_displacement_ = std::make_pair(shield_dx, shield_dy); } -boost::tuple const& shield_symbolizer::get_shield_displacement() const +position const& shield_symbolizer::get_shield_displacement() const { return shield_displacement_; } diff --git a/src/symbolizer_helpers.cpp b/src/symbolizer_helpers.cpp index ecf5e35dc..0ad411f9c 100644 --- a/src/symbolizer_helpers.cpp +++ b/src/symbolizer_helpers.cpp @@ -63,7 +63,7 @@ text_placement_info_ptr text_symbolizer_helper::get_poi placement_finder finder(*placement_, *info_, detector_, dims_); finder.find_point_placement(point_itr_->first, point_itr_->second, angle_); //Keep reference to current object so we can delete it. - std::list::iterator current_object = point_itr_; + std::list::iterator current_object = point_itr_; point_itr_++; if (placement_->placements.size()) { @@ -208,13 +208,13 @@ text_placement_info_ptr shield_symbolizer_helper::get_p continue; //Reexecute size check } position const& pos = placement_->properties.displacement; - double label_x = point_itr_->first + boost::get<0>(shield_pos); - double label_y = point_itr_->second + boost::get<1>(shield_pos); + double label_x = point_itr_->first + shield_pos.first; + double label_y = point_itr_->second + shield_pos.second; placement_finder finder(*placement_, *info_, detector_, dims_); finder.find_point_placement(label_x, label_y, angle_); //Keep reference to current object so we can delete it. - std::list::iterator current_object = point_itr_; + std::list::iterator current_object = point_itr_; point_itr_++; if (!placement_->placements.size()) { @@ -229,8 +229,8 @@ text_placement_info_ptr shield_symbolizer_helper::get_p { // center image at text center position // remove displacement from image label - double lx = x - boost::get<0>(pos); - double ly = y - boost::get<1>(pos); + double lx = x - pos.first; + double ly = y - pos.second; marker_x_ = int(floor(lx - (0.5 * marker_w_))) + 1; marker_y_ = int(floor(ly - (0.5 * marker_h_))) + 1; marker_ext_.re_center(lx, ly); @@ -266,10 +266,10 @@ text_placement_info_ptr shield_symbolizer_helper::get_l TODO: Not supported by placement_finder atm position const& pos = placement_->properties.displacement; text_placement.additional_boxes.push_back( - box2d(-0.5 * label_ext.width() - boost::get<0>(pos), - -0.5 * label_ext.height() - boost::get<1>(pos), - 0.5 * label_ext.width() - boost::get<0>(pos), - 0.5 * label_ext.height() - boost::get<1>(pos))); + box2d(-0.5 * label_ext.width() - pos.first, + -0.5 * label_ext.height() - pos.second, + 0.5 * label_ext.width() - pos.first, + 0.5 * label_ext.height() - pos.second)); #endif return text_symbolizer_helper::get_line_placement(); } @@ -319,8 +319,8 @@ std::pair shield_symbolizer_helper::get_marke double x = floor(p.starting_x); double y = floor(p.starting_y); - double lx = x - boost::get<0>(pos); - double ly = y - boost::get<1>(pos); + double lx = x - pos.first; + double ly = y - pos.second; int px = int(floor(lx - (0.5*marker_w_))) + 1; int py = int(floor(ly - (0.5*marker_h_))) + 1; marker_ext_.re_center(lx, ly); diff --git a/src/text_placements.cpp b/src/text_placements.cpp index 2ea18f63a..f12772ab9 100644 --- a/src/text_placements.cpp +++ b/src/text_placements.cpp @@ -95,9 +95,9 @@ void text_symbolizer_properties::from_xml(boost::property_tree::ptree const &sym optional orientation_ = get_opt_attr(sym, "orientation"); if (orientation_) orientation = parse_expression(*orientation_, "utf8"); optional dx = get_opt_attr(sym, "dx"); - if (dx) displacement.get<0>() = *dx; + if (dx) displacement.first = *dx; optional dy = get_opt_attr(sym, "dy"); - if (dy) displacement.get<1>() = *dy; + if (dy) displacement.second = *dy; optional max_char_angle_delta_ = get_opt_attr(sym, "max-char-angle-delta"); if (max_char_angle_delta_) max_char_angle_delta=(*max_char_angle_delta_)*(M_PI/180); processor.from_xml(sym, fontsets); @@ -118,13 +118,13 @@ void text_symbolizer_properties::to_xml(boost::property_tree::ptree &node, bool } } - if (displacement.get<0>() != dfl.displacement.get<0>() || explicit_defaults) + if (displacement.first != dfl.displacement.first || explicit_defaults) { - set_attr(node, "dx", displacement.get<0>()); + set_attr(node, "dx", displacement.first); } - if (displacement.get<1>() != dfl.displacement.get<1>() || explicit_defaults) + if (displacement.second != dfl.displacement.second || explicit_defaults) { - set_attr(node, "dy", displacement.get<1>()); + set_attr(node, "dy", displacement.second); } if (label_placement != dfl.label_placement || explicit_defaults) { @@ -389,36 +389,28 @@ bool text_placement_info_simple::next_position_only() displacement = pdisp; break; case NORTH: - displacement = boost::make_tuple(0, -abs(pdisp.get<1>())); + displacement = std::make_pair(0, -abs(pdisp.second)); break; case EAST: - displacement = boost::make_tuple(abs(pdisp.get<0>()), 0); + displacement = std::make_pair(abs(pdisp.first), 0); break; case SOUTH: - displacement = boost::make_tuple(0, abs(pdisp.get<1>())); + displacement = std::make_pair(0, abs(pdisp.second)); break; case WEST: - displacement = boost::make_tuple(-abs(pdisp.get<0>()), 0); + displacement = std::make_pair(-abs(pdisp.first), 0); break; case NORTHEAST: - displacement = boost::make_tuple( - abs(pdisp.get<0>()), - -abs(pdisp.get<1>())); + displacement = std::make_pair(abs(pdisp.first), -abs(pdisp.second)); break; case SOUTHEAST: - displacement = boost::make_tuple( - abs(pdisp.get<0>()), - abs(pdisp.get<1>())); + displacement = std::make_pair(abs(pdisp.first), abs(pdisp.second)); break; case NORTHWEST: - displacement = boost::make_tuple( - -abs(pdisp.get<0>()), - -abs(pdisp.get<1>())); + displacement = std::make_pair(-abs(pdisp.first), -abs(pdisp.second)); break; case SOUTHWEST: - displacement = boost::make_tuple( - -abs(pdisp.get<0>()), - abs(pdisp.get<1>())); + displacement = std::make_pair(-abs(pdisp.first), abs(pdisp.second)); break; default: std::cerr << "WARNING: Unknown placement\n"; diff --git a/src/text_symbolizer.cpp b/src/text_symbolizer.cpp index 2966e73d1..20e26ffdb 100644 --- a/src/text_symbolizer.cpp +++ b/src/text_symbolizer.cpp @@ -342,7 +342,7 @@ label_placement_e text_symbolizer::get_label_placement() const void text_symbolizer::set_displacement(double x, double y) { - placement_options_->properties.displacement = boost::make_tuple(x,y); + placement_options_->properties.displacement = std::make_pair(x,y); } void text_symbolizer::set_displacement(position const& p) From 16e5fefb4dffa8f0124a561d504de32c8234c91f Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 29 Jan 2012 20:04:31 +0100 Subject: [PATCH 77/81] Remove text_placement_info::initialize() to avoid incorrect usage of this object. --- include/mapnik/symbolizer_helpers.hpp | 5 +-- include/mapnik/text_placements.hpp | 24 +++++++------- include/mapnik/text_placements_list.hpp | 9 ++++-- include/mapnik/text_placements_simple.hpp | 11 +++++-- src/text_placements.cpp | 38 +++++++++++------------ 5 files changed, 49 insertions(+), 38 deletions(-) diff --git a/include/mapnik/symbolizer_helpers.hpp b/include/mapnik/symbolizer_helpers.hpp index 4869f07eb..6c9dcdcb5 100644 --- a/include/mapnik/symbolizer_helpers.hpp +++ b/include/mapnik/symbolizer_helpers.hpp @@ -65,8 +65,9 @@ public: { initialize_geometries(); if (!geometries_to_process_.size()) return; //TODO: Test this - placement_ = sym_.get_placement_options()->get_placement_info(); - placement_->init(scale_factor, width, height); + placement_ = sym_.get_placement_options()->get_placement_info( + scale_factor, std::make_pair(width, height), false); + //TODO: has_dimensions? Why? When? if (writer_.first) placement_->collect_extents = true; next_placement(); initialize_points(); diff --git a/include/mapnik/text_placements.hpp b/include/mapnik/text_placements.hpp index d4e159c55..31a6a7019 100644 --- a/include/mapnik/text_placements.hpp +++ b/include/mapnik/text_placements.hpp @@ -47,6 +47,7 @@ namespace mapnik { class text_placements; typedef std::pair position; +typedef std::pair dimension_type; enum label_placement_enum { POINT_PLACEMENT, @@ -134,7 +135,8 @@ class text_placement_info : boost::noncopyable public: /** Constructor. Takes the parent text_placements object as a parameter * to read defaults from it. */ - text_placement_info(text_placements const* parent); + text_placement_info(text_placements const* parent, + double scale_factor_, dimension_type dim, bool has_dimensions_); /** Get next placement. * This function is also called before the first placement is tried. * Each class has to return at least one position! @@ -143,11 +145,6 @@ public: */ virtual bool next()=0; virtual ~text_placement_info() {} - /** Initialize values used by placement finder. Only has to be done once - * per object. - */ - void init(double scale_factor_, - unsigned w = 0, unsigned h = 0, bool has_dimensions_ = false); /** Properties actually used by placement finder and renderer. Values in * here are modified each time next() is called. */ @@ -158,7 +155,7 @@ public: /* TODO: Don't know what this is used for. */ bool has_dimensions; /* TODO: Don't know what this is used for. */ - std::pair dimensions; + dimension_type dimensions; /** Set scale factor. */ void set_scale_factor(double factor) { scale_factor = factor; } /** Get scale factor. */ @@ -204,7 +201,9 @@ public: * return text_placement_info_ptr(new text_placement_info_XXX(this)); * } */ - virtual text_placement_info_ptr get_placement_info() const =0; + virtual text_placement_info_ptr get_placement_info( + double scale_factor_, dimension_type dim, + bool has_dimensions_) const =0; /** Get a list of all expressions used in any placement. * This function is used to collect attributes. */ @@ -227,7 +226,8 @@ class text_placements_info_dummy; class MAPNIK_DECL text_placements_dummy: public text_placements { public: - text_placement_info_ptr get_placement_info() const; + text_placement_info_ptr get_placement_info( + double scale_factor, dimension_type dim, bool has_dimensions) const; friend class text_placement_info_dummy; }; @@ -235,8 +235,10 @@ public: class MAPNIK_DECL text_placement_info_dummy : public text_placement_info { public: - text_placement_info_dummy(text_placements_dummy const* parent) : text_placement_info(parent), - state(0), parent_(parent) {} + text_placement_info_dummy(text_placements_dummy const* parent, + double scale_factor, dimension_type dim, bool has_dimensions) + : text_placement_info(parent, scale_factor, dim, has_dimensions), + state(0), parent_(parent) {} bool next(); private: unsigned state; diff --git a/include/mapnik/text_placements_list.hpp b/include/mapnik/text_placements_list.hpp index e43fe724a..3453c3010 100644 --- a/include/mapnik/text_placements_list.hpp +++ b/include/mapnik/text_placements_list.hpp @@ -33,7 +33,8 @@ class text_placements_list: public text_placements { public: text_placements_list(); - text_placement_info_ptr get_placement_info() const; + text_placement_info_ptr get_placement_info( + double scale_factor, dimension_type dim, bool has_dimensions) const; virtual std::set get_all_expressions(); text_symbolizer_properties & add(); text_symbolizer_properties & get(unsigned i); @@ -48,8 +49,10 @@ private: class text_placement_info_list : public text_placement_info { public: - text_placement_info_list(text_placements_list const* parent) : - text_placement_info(parent), state(0), parent_(parent) {} + text_placement_info_list(text_placements_list const* parent, + double scale_factor, dimension_type dim, bool has_dimensions) : + text_placement_info(parent, scale_factor, dim, has_dimensions), + state(0), parent_(parent) {} bool next(); private: unsigned state; diff --git a/include/mapnik/text_placements_simple.hpp b/include/mapnik/text_placements_simple.hpp index 2c9efed7c..575dee4cd 100644 --- a/include/mapnik/text_placements_simple.hpp +++ b/include/mapnik/text_placements_simple.hpp @@ -49,7 +49,8 @@ class text_placements_simple: public text_placements public: text_placements_simple(); text_placements_simple(std::string positions); - text_placement_info_ptr get_placement_info() const; + text_placement_info_ptr get_placement_info( + double scale_factor, dimension_type dim, bool has_dimensions) const; void set_positions(std::string positions); std::string get_positions(); private: @@ -64,8 +65,12 @@ private: class text_placement_info_simple : public text_placement_info { public: - text_placement_info_simple(text_placements_simple const* parent) : - text_placement_info(parent), state(0), position_state(0), parent_(parent) {} + text_placement_info_simple(text_placements_simple const* parent, + double scale_factor, dimension_type dim, bool has_dimensions) + : text_placement_info(parent, scale_factor, dim, has_dimensions), + state(0), position_state(0), parent_(parent) + { + } bool next(); protected: bool next_position_only(); diff --git a/src/text_placements.cpp b/src/text_placements.cpp index f12772ab9..457e24d78 100644 --- a/src/text_placements.cpp +++ b/src/text_placements.cpp @@ -328,11 +328,13 @@ std::set text_placements::get_all_expressions() /************************************************************************/ -text_placement_info::text_placement_info(text_placements const* parent): - properties(parent->properties), - scale_factor(1), - has_dimensions(false), - collect_extents(false) +text_placement_info::text_placement_info(text_placements const* parent, + double scale_factor_, dimension_type dim, bool has_dimensions_) + : properties(parent->properties), + scale_factor(scale_factor_), + has_dimensions(has_dimensions_), + dimensions(dim), + collect_extents(false) { } @@ -344,17 +346,11 @@ bool text_placement_info_dummy::next() return true; } -text_placement_info_ptr text_placements_dummy::get_placement_info() const +text_placement_info_ptr text_placements_dummy::get_placement_info( + double scale_factor, dimension_type dim, bool has_dimensions) const { - return text_placement_info_ptr(new text_placement_info_dummy(this)); -} - -void text_placement_info::init(double scale_factor_, - unsigned w, unsigned h, bool has_dimensions_) -{ - scale_factor = scale_factor_; - dimensions = std::make_pair(w, h); - has_dimensions = has_dimensions_; + return text_placement_info_ptr(new text_placement_info_dummy( + this, scale_factor, dim, has_dimensions)); } /************************************************************************/ @@ -419,9 +415,11 @@ bool text_placement_info_simple::next_position_only() return true; } -text_placement_info_ptr text_placements_simple::get_placement_info() const +text_placement_info_ptr text_placements_simple::get_placement_info( + double scale_factor, dimension_type dim, bool has_dimensions) const { - return text_placement_info_ptr(new text_placement_info_simple(this)); + return text_placement_info_ptr(new text_placement_info_simple(this, + scale_factor, dim, has_dimensions)); } /** Position string: [POS][SIZE] @@ -517,9 +515,11 @@ text_symbolizer_properties & text_placements_list::get(unsigned i) /***************************************************************************/ -text_placement_info_ptr text_placements_list::get_placement_info() const +text_placement_info_ptr text_placements_list::get_placement_info( + double scale_factor, dimension_type dim, bool has_dimensions) const { - return text_placement_info_ptr(new text_placement_info_list(this)); + return text_placement_info_ptr(new text_placement_info_list(this, + scale_factor, dim, has_dimensions)); } text_placements_list::text_placements_list() : text_placements(), list_(0) From 49225d7468fee759c500911a76b5b3e9fe68bd0d Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 29 Jan 2012 20:28:32 +0100 Subject: [PATCH 78/81] Handle additional boxes from ShieldSymbolizer. --- include/mapnik/text_placements.hpp | 8 +++++++- src/placement_finder.cpp | 7 ++----- src/symbolizer_helpers.cpp | 16 ++++++++-------- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/include/mapnik/text_placements.hpp b/include/mapnik/text_placements.hpp index 31a6a7019..80bb5f5f7 100644 --- a/include/mapnik/text_placements.hpp +++ b/include/mapnik/text_placements.hpp @@ -172,9 +172,15 @@ public: //Output by placement finder /** Bounding box of all texts placed. */ box2d extents; + /** 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. + */ + std::vector > additional_boxes; + /* TODO */ std::queue< box2d > envelopes; - /* TODO */ + /** Used to return all placements found. */ boost::ptr_vector placements; }; diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index a2d3019b8..da5583033 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -458,12 +458,10 @@ void placement_finder::find_point_placement(double label_x, double la x += cwidth; // move position to next character } -#if 0 - //TODO // check the placement of any additional envelopes - if (!p.allow_overlap && !p.additional_boxes.empty()) + if (!p.allow_overlap && !pi.additional_boxes.empty()) { - BOOST_FOREACH(box2d box, p.additional_boxes) + BOOST_FOREACH(box2d box, pi.additional_boxes) { box2d pt(box.minx() + current_placement->starting_x, box.miny() + current_placement->starting_y, @@ -476,7 +474,6 @@ void placement_finder::find_point_placement(double label_x, double la c_envelopes.push(pt); } } -#endif // since there was no early exit, add the character envelopes to the placements' envelopes while( !c_envelopes.empty() ) diff --git a/src/symbolizer_helpers.cpp b/src/symbolizer_helpers.cpp index 0ad411f9c..72b9cc8f3 100644 --- a/src/symbolizer_helpers.cpp +++ b/src/symbolizer_helpers.cpp @@ -262,15 +262,15 @@ text_placement_info_ptr shield_symbolizer_helper::get_p template text_placement_info_ptr shield_symbolizer_helper::get_line_placement() { -#if 0 - TODO: Not supported by placement_finder atm position const& pos = placement_->properties.displacement; - text_placement.additional_boxes.push_back( - box2d(-0.5 * label_ext.width() - pos.first, - -0.5 * label_ext.height() - pos.second, - 0.5 * label_ext.width() - pos.first, - 0.5 * label_ext.height() - pos.second)); -#endif + placement_->additional_boxes.push_back( + /*TODO: I'm not sure this is correct. It's what the old code did, but + I think transfroms can make the marker non-centered. + */ + 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)); return text_symbolizer_helper::get_line_placement(); } From 89ce69961683f253d32498a511d4c39456360f83 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 29 Jan 2012 22:48:24 +0100 Subject: [PATCH 79/81] Update text rendering dependency graph. --- docs/textrendering.gv | 36 +++++-- docs/textrendering.png | Bin 0 -> 117655 bytes docs/textrendering.svg | 226 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 252 insertions(+), 10 deletions(-) create mode 100644 docs/textrendering.png create mode 100644 docs/textrendering.svg diff --git a/docs/textrendering.gv b/docs/textrendering.gv index 90a897a6a..26bf9fb1f 100644 --- a/docs/textrendering.gv +++ b/docs/textrendering.gv @@ -1,13 +1,14 @@ -#process with: dot textrendering.gv -Tpng > textrendering.png +/* process with: dot textrendering.gv -Tsvg > textrendering.svg */ digraph textrendering { -# Classes without important virtual members: Round -# Classes with important virtual members: Rect -# Pointers [style=dashed] -# Red: function is called + /* Classes without important virtual members: Round + Classes with important virtual members: Rect + Pointers [style=dashed] */ + + Renderer [color=red] + rankdir="TD"; text_placements[shape=box] text_placement_info[shape=box] - Renderer - + node_ -> text_processor [label="tree_", style=dashed] TextSymbolizer -> text_placements [label="placement_options_", style=dashed] text_placements -> text_symbolizer_properties [label="properties"] text_placements -> text_placement_info [label="get_placement_info()", style=dashed] @@ -16,9 +17,24 @@ digraph textrendering { text_placement_info -> text_placement_info [label="next()"] text_symbolizer_properties -> text_processor [label="processor"] text_processor -> processed_text [label="process()", style=dashed] - processed_text -> string_info [label="get_string_info()"] + processed_text -> string_info [label="get_string_info()", style=dashed] text_path -> Renderer [color=red, label="used by"] - Renderer -> text_placement_info [color=red, label="init()"] - Renderer -> processed_text [color=red, label="initializes"] + processed_text -> Renderer [color=red, label="owned by"] + Renderer -> text_symbolizer_helper [color=red, label="creates"] + text_symbolizer_helper -> placement_finder [color=red, label="creates"] + placement_finder -> text_path [color=red, label="creates"] + string_info -> placement_finder [color=red, label="used by"] + text_processor -> Renderer [color=red, label="called by"] + text_placement_info -> Renderer [color=red, label="used by"] + + node_[label="node"] + node_ -> text_node [style=dashed] + node_ -> list_node [style=dashed] + node_ -> format_node [style=dashed] + list_node -> text_node [style=dashed] + list_node -> format_node [style=dashed] + format_node -> text_node [style=dashed] + { rank=same; text_path text_symbolizer_helper } + { rank=same; node_ TextSymbolizer} } diff --git a/docs/textrendering.png b/docs/textrendering.png new file mode 100644 index 0000000000000000000000000000000000000000..ee7b33c4b9dfcbee35c43773b3a917c2af466c27 GIT binary patch literal 117655 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 + + + + + +textrendering + + +Renderer + +Renderer + + +text_symbolizer_helper + +text_symbolizer_helper + + +Renderer->text_symbolizer_helper + + +creates + + +text_placements + +text_placements + + +text_placement_info + +text_placement_info + + +text_placements->text_placement_info + + +get_placement_info() + + +text_symbolizer_properties + +text_symbolizer_properties + + +text_placements->text_symbolizer_properties + + +properties + + +text_placement_info->Renderer + + +used by + + +text_placement_info->text_placement_info + + +next() + + +text_placement_info->text_symbolizer_properties + + +properties + + +text_path + +text_path + + +text_placement_info->text_path + + +placements + + +node_ + +node + + +text_processor + +text_processor + + +node_->text_processor + + +tree_ + + +text_node + +text_node + + +node_->text_node + + + + +list_node + +list_node + + +node_->list_node + + + + +format_node + +format_node + + +node_->format_node + + + + +text_processor->Renderer + + +called by + + +processed_text + +processed_text + + +text_processor->processed_text + + +process() + + +TextSymbolizer + +TextSymbolizer + + +TextSymbolizer->text_placements + + +placement_options_ + + +text_symbolizer_properties->text_processor + + +processor + + +text_path->Renderer + + +used by + + +processed_text->Renderer + + +owned by + + +string_info + +string_info + + +processed_text->string_info + + +get_string_info() + + +placement_finder + +placement_finder + + +string_info->placement_finder + + +used by + + +text_symbolizer_helper->placement_finder + + +creates + + +placement_finder->text_path + + +creates + + +list_node->text_node + + + + +list_node->format_node + + + + +format_node->text_node + + + + + From e4340c0f890028a81b8172f0e9084006217ec763 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Mon, 30 Jan 2012 03:32:25 +0100 Subject: [PATCH 80/81] Python bindings for text_symbolizer_properties. --- bindings/python/mapnik_python.cpp | 2 + bindings/python/mapnik_text_placement.cpp | 115 ++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 bindings/python/mapnik_text_placement.cpp diff --git a/bindings/python/mapnik_python.cpp b/bindings/python/mapnik_python.cpp index bb9198c2e..1f26e9a0b 100644 --- a/bindings/python/mapnik_python.cpp +++ b/bindings/python/mapnik_python.cpp @@ -60,6 +60,7 @@ void export_polygon_symbolizer(); void export_polygon_pattern_symbolizer(); void export_raster_symbolizer(); void export_text_symbolizer(); +void export_text_placement(); void export_shield_symbolizer(); void export_font_engine(); void export_projection(); @@ -425,6 +426,7 @@ BOOST_PYTHON_MODULE(_mapnik) export_polygon_symbolizer(); export_polygon_pattern_symbolizer(); export_raster_symbolizer(); + export_text_placement(); export_text_symbolizer(); export_shield_symbolizer(); export_font_engine(); diff --git a/bindings/python/mapnik_text_placement.cpp b/bindings/python/mapnik_text_placement.cpp new file mode 100644 index 000000000..74f7d8d73 --- /dev/null +++ b/bindings/python/mapnik_text_placement.cpp @@ -0,0 +1,115 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2006 Artem Pavlenko, Jean-Francois Doyon + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + *****************************************************************************/ +//$Id$ + +#include + +#include +#include "mapnik_enumeration.hpp" +#include + +using namespace mapnik; +//using mapnik::color; +//using mapnik::text_symbolizer; +//using mapnik::expr_node; +//using mapnik::expression_ptr; +//using mapnik::to_expression_string; + +namespace { +using namespace boost::python; + +tuple get_displacement(text_symbolizer_properties const& t) +{ + return boost::python::make_tuple(t.displacement.first, t.displacement.second); +} + +void set_displacement(text_symbolizer_properties &t, boost::python::tuple arg) +{ + double x = extract(arg[0]); + double y = extract(arg[0]); + t.displacement = std::make_pair(x, y); +} + +} + +void export_text_placement() +{ + using namespace boost::python; + + enumeration_("label_placement") + .value("LINE_PLACEMENT",LINE_PLACEMENT) + .value("POINT_PLACEMENT",POINT_PLACEMENT) + .value("VERTEX_PLACEMENT",VERTEX_PLACEMENT) + .value("INTERIOR_PLACEMENT",INTERIOR_PLACEMENT) + ; + enumeration_("vertical_alignment") + .value("TOP",V_TOP) + .value("MIDDLE",V_MIDDLE) + .value("BOTTOM",V_BOTTOM) + .value("AUTO",V_AUTO) + ; + + enumeration_("horizontal_alignment") + .value("LEFT",H_LEFT) + .value("MIDDLE",H_MIDDLE) + .value("RIGHT",H_RIGHT) + .value("AUTO",H_AUTO) + ; + + enumeration_("justify_alignment") + .value("LEFT",J_LEFT) + .value("MIDDLE",J_MIDDLE) + .value("RIGHT",J_RIGHT) + ; + + enumeration_("text_transform") + .value("NONE",NONE) + .value("UPPERCASE",UPPERCASE) + .value("LOWERCASE",LOWERCASE) + .value("CAPITALIZE",CAPITALIZE) + ; + + + class_("TextSymbolizerProperties") + .def_readwrite("orientation", &text_symbolizer_properties::orientation) + .add_property("displacement", + &get_displacement, + &set_displacement) + .def_readwrite("label_placement", &text_symbolizer_properties::label_placement) + .def_readwrite("horizontal_alignment", &text_symbolizer_properties::halign) + .def_readwrite("justify_alignment", &text_symbolizer_properties::jalign) + .def_readwrite("vertical_alignment", &text_symbolizer_properties::valign) + .def_readwrite("label_spacing", &text_symbolizer_properties::label_spacing) + .def_readwrite("label_position_tolerance", &text_symbolizer_properties::label_position_tolerance) + .def_readwrite("avoid_edges", &text_symbolizer_properties::avoid_edges) + .def_readwrite("minimum_distance", &text_symbolizer_properties::minimum_distance) + .def_readwrite("minimum_padding", &text_symbolizer_properties::minimum_padding) + .def_readwrite("minimum_path_length", &text_symbolizer_properties::minimum_path_length) + .def_readwrite("maximum_angle_char_delta", &text_symbolizer_properties::max_char_angle_delta) + .def_readwrite("force_odd_labels", &text_symbolizer_properties::force_odd_labels) + .def_readwrite("allow_overlap", &text_symbolizer_properties::allow_overlap) + .def_readwrite("text_ratio", &text_symbolizer_properties::text_ratio) + .def_readwrite("wrap_width", &text_symbolizer_properties::wrap_width) + /* TODO: text_processor */ + /* from_xml, to_xml operate on mapnik's internal XML tree and don't make sense in python.*/ + ; +} From 1a16e9c5ab70032f50f017ef2134bc57dae0b656 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Tue, 31 Jan 2012 16:24:58 +0100 Subject: [PATCH 81/81] Remove class text_processor. --- include/mapnik/text_placements.hpp | 106 ++++++++++++++---------- include/mapnik/text_processing.hpp | 126 +++++++++++------------------ src/load_map.cpp | 8 +- src/symbolizer_helpers.cpp | 2 +- src/text_placements.cpp | 61 +++++++++++--- src/text_processing.cpp | 57 +------------ src/text_symbolizer.cpp | 54 ++++++------- 7 files changed, 193 insertions(+), 221 deletions(-) diff --git a/include/mapnik/text_placements.hpp b/include/mapnik/text_placements.hpp index 80bb5f5f7..195b2ac8d 100644 --- a/include/mapnik/text_placements.hpp +++ b/include/mapnik/text_placements.hpp @@ -49,57 +49,50 @@ class text_placements; typedef std::pair position; typedef std::pair dimension_type; -enum label_placement_enum { - POINT_PLACEMENT, - LINE_PLACEMENT, - VERTEX_PLACEMENT, - INTERIOR_PLACEMENT, - label_placement_enum_MAX -}; - -DEFINE_ENUM( label_placement_e, label_placement_enum ); - -enum vertical_alignment +struct char_properties { - V_TOP = 0, - V_MIDDLE, - V_BOTTOM, - V_AUTO, - vertical_alignment_MAX + char_properties(); + /** Construct object from XML. */ + void from_xml(boost::property_tree::ptree const &sym, std::map const & fontsets); + /** Write object to XML ptree. */ + void to_xml(boost::property_tree::ptree &node, bool explicit_defaults, char_properties const &dfl=char_properties()) const; + std::string face_name; + font_set fontset; + float text_size; + 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; + color halo_fill; + double halo_radius; }; -DEFINE_ENUM( vertical_alignment_e, vertical_alignment ); - -enum horizontal_alignment -{ - H_LEFT = 0, - H_MIDDLE, - H_RIGHT, - H_AUTO, - horizontal_alignment_MAX -}; - -DEFINE_ENUM( horizontal_alignment_e, horizontal_alignment ); - -enum justify_alignment -{ - J_LEFT = 0, - J_MIDDLE, - J_RIGHT, - justify_alignment_MAX -}; - -DEFINE_ENUM( justify_alignment_e, justify_alignment ); - /** Contains all text symbolizer properties which are not directly related to text formating. */ struct text_symbolizer_properties { text_symbolizer_properties(); - /** Load all values and also the ```processor``` object from XML ptree. */ + /** Load all values from XML ptree. */ void from_xml(boost::property_tree::ptree const &sym, std::map const & fontsets); /** Save all values to XML ptree (but does not create a new parent node!). */ void to_xml(boost::property_tree::ptree &node, bool explicit_defaults, text_symbolizer_properties const &dfl=text_symbolizer_properties()) const; + /** 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 const& feature) const; + /** Automatically create processing instructions for a single expression. */ + void set_old_style_expression(expression_ptr expr); + /** Sets new format tree. */ + void set_format_tree(formating::node_ptr tree); + /** Get format tree. */ + formating::node_ptr format_tree() const; + /** Get a list of all expressions used in any placement. + * This function is used to collect attributes. */ + std::set get_all_expressions() const; + //Per symbolizer options expression_ptr orientation; position displacement; @@ -121,10 +114,39 @@ struct text_symbolizer_properties bool allow_overlap; unsigned text_ratio; unsigned wrap_width; - /** Contains everything related to text formating */ - text_processor processor; + /** Default values for char_properties. */ + char_properties default_format; +private: + formating::node_ptr tree_; }; +class processed_text : boost::noncopyable +{ +public: + class processed_expression + { + public: + processed_expression(char_properties const& properties, UnicodeString const& text) : + p(properties), str(text) {} + char_properties p; + UnicodeString str; + }; +public: + processed_text(face_manager & font_manager, double scale_factor); + void push_back(processed_expression const& exp); + unsigned 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 &get_string_info(); +private: + expression_list expr_list_; + face_manager & font_manager_; + double scale_factor_; + string_info info_; +}; /** Generate a possible placement and store results of placement_finder. * This placement has first to be tested by placement_finder to verify it diff --git a/include/mapnik/text_processing.hpp b/include/mapnik/text_processing.hpp index 8bc4dcc1d..bd5698fc7 100644 --- a/include/mapnik/text_processing.hpp +++ b/include/mapnik/text_processing.hpp @@ -23,6 +23,8 @@ #define MAPNIK_TEXT_PROCESSING_HPP #include +#include + #include #include #include @@ -37,6 +39,50 @@ #include namespace mapnik { +class processed_text; +struct char_properties; + +enum label_placement_enum { + POINT_PLACEMENT, + LINE_PLACEMENT, + VERTEX_PLACEMENT, + INTERIOR_PLACEMENT, + label_placement_enum_MAX +}; + +DEFINE_ENUM( label_placement_e, label_placement_enum ); + +enum vertical_alignment +{ + V_TOP = 0, + V_MIDDLE, + V_BOTTOM, + V_AUTO, + vertical_alignment_MAX +}; + +DEFINE_ENUM( vertical_alignment_e, vertical_alignment ); + +enum horizontal_alignment +{ + H_LEFT = 0, + H_MIDDLE, + H_RIGHT, + H_AUTO, + horizontal_alignment_MAX +}; + +DEFINE_ENUM( horizontal_alignment_e, horizontal_alignment ); + +enum justify_alignment +{ + J_LEFT = 0, + J_MIDDLE, + J_RIGHT, + justify_alignment_MAX +}; + +DEFINE_ENUM( justify_alignment_e, justify_alignment ); enum text_transform { @@ -48,58 +94,6 @@ enum text_transform }; DEFINE_ENUM( text_transform_e, text_transform ); - -struct char_properties -{ - char_properties(); - /** Construct object from XML. */ - void from_xml(boost::property_tree::ptree const &sym, std::map const & fontsets); - /** Write object to XML ptree. */ - void to_xml(boost::property_tree::ptree &node, bool explicit_defaults, char_properties const &dfl=char_properties()) const; - std::string face_name; - font_set fontset; - float text_size; - 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; - color halo_fill; - double halo_radius; -}; - -class processed_expression -{ -public: - processed_expression(char_properties const& properties, UnicodeString const& text) : - p(properties), str(text) {} - char_properties p; - UnicodeString str; -}; - - -class processed_text : boost::noncopyable -{ -public: - processed_text(face_manager & font_manager, double scale_factor); - void push_back(processed_expression const& exp); - unsigned 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 &get_string_info(); -private: - expression_list expr_list_; - face_manager & font_manager_; - double scale_factor_; - string_info info_; -}; - - namespace formating { class node; typedef boost::shared_ptr node_ptr; @@ -180,34 +174,6 @@ private: } //namespace formating -/** Stores formating information and uses this to produce formated text for a given feature. */ -class text_processor -{ -public: - text_processor(); - /** Construct object from XML. */ - void from_xml(boost::property_tree::ptree const& pt, std::map const &fontsets); - /** Write object to XML ptree. */ - void to_xml(boost::property_tree::ptree &node, bool explicit_defaults, text_processor const& dfl) const; - - /** 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 const& feature) const; - /** Automatically create processing instructions for a single expression. */ - void set_old_style_expression(expression_ptr expr); - /** Sets new format tree. */ - void set_format_tree(formating::node_ptr tree); - /** Get format tree. */ - formating::node_ptr get_format_tree() const; - /** Get a list of all expressions used in any placement. This function is used to collect attributes. */ - std::set get_all_expressions() const; - /** Default values for char_properties. */ - char_properties defaults; -private: - formating::node_ptr tree_; -}; - } /* namespace mapnik*/ #endif diff --git a/src/load_map.cpp b/src/load_map.cpp index 79f3d5c2e..b6ddae541 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -1282,7 +1282,7 @@ void map_parser::parse_text_symbolizer( rule & rule, ptree const & sym ) } placement_finder->properties.from_xml(sym, fontsets_); - if (strict_) ensure_font_face(placement_finder->properties.processor.defaults.face_name); + if (strict_) ensure_font_face(placement_finder->properties.default_format.face_name); if (list) { ptree::const_iterator symIter = sym.begin(); ptree::const_iterator endSym = sym.end(); @@ -1296,7 +1296,7 @@ void map_parser::parse_text_symbolizer( rule & rule, ptree const & sym ) ensure_attrs(symIter->second, "TextSymbolizer/Placement", s_common.str()); text_symbolizer_properties & p = list->add(); p.from_xml(symIter->second, fontsets_); - if (strict_) ensure_font_face(p.processor.defaults.face_name); + if (strict_) ensure_font_face(p.default_format.face_name); } } @@ -1351,7 +1351,7 @@ void map_parser::parse_shield_symbolizer( rule & rule, ptree const & sym ) } placement_finder->properties.from_xml(sym, fontsets_); - if (strict_) ensure_font_face(placement_finder->properties.processor.defaults.face_name); + if (strict_) ensure_font_face(placement_finder->properties.default_format.face_name); if (list) { ptree::const_iterator symIter = sym.begin(); ptree::const_iterator endSym = sym.end(); @@ -1365,7 +1365,7 @@ void map_parser::parse_shield_symbolizer( rule & rule, ptree const & sym ) ensure_attrs(symIter->second, "TextSymbolizer/Placement", s_common); text_symbolizer_properties & p = list->add(); p.from_xml(symIter->second, fontsets_); - if (strict_) ensure_font_face(p.processor.defaults.face_name); + if (strict_) ensure_font_face(p.default_format.face_name); } } diff --git a/src/symbolizer_helpers.cpp b/src/symbolizer_helpers.cpp index 72b9cc8f3..a71811d25 100644 --- a/src/symbolizer_helpers.cpp +++ b/src/symbolizer_helpers.cpp @@ -166,7 +166,7 @@ bool text_symbolizer_helper::next_placement() placement_valid_ = false; return false; } - placement_->properties.processor.process(text_, feature_); + placement_->properties.process(text_, feature_); info_ = &(text_.get_string_info()); if (placement_->properties.orientation) { diff --git a/src/text_placements.cpp b/src/text_placements.cpp index 457e24d78..d3703ce02 100644 --- a/src/text_placements.cpp +++ b/src/text_placements.cpp @@ -58,11 +58,33 @@ text_symbolizer_properties::text_symbolizer_properties() : allow_overlap(false), text_ratio(0), wrap_width(0), - processor() + tree_() { } +void text_symbolizer_properties::process(processed_text &output, Feature const& feature) const +{ + output.clear(); + if (tree_) { + tree_->apply(default_format, feature, output); + } else { +#ifdef MAPNIK_DEBUG + std::cerr << "Warning: text_symbolizer_properties can't produce text: No formating tree!\n"; +#endif + } +} + +void text_symbolizer_properties::set_format_tree(formating::node_ptr tree) +{ + tree_ = tree; +} + +formating::node_ptr text_symbolizer_properties::format_tree() const +{ + return tree_; +} + void text_symbolizer_properties::from_xml(boost::property_tree::ptree const &sym, std::map const & fontsets) { optional placement_ = get_opt_attr(sym, "placement"); @@ -100,12 +122,16 @@ void text_symbolizer_properties::from_xml(boost::property_tree::ptree const &sym if (dy) displacement.second = *dy; optional max_char_angle_delta_ = get_opt_attr(sym, "max-char-angle-delta"); if (max_char_angle_delta_) max_char_angle_delta=(*max_char_angle_delta_)*(M_PI/180); - processor.from_xml(sym, fontsets); + optional name_ = get_opt_attr(sym, "name"); if (name_) { std::clog << "### WARNING: Using 'name' in TextSymbolizer/ShieldSymbolizer is deprecated!\n"; - processor.set_old_style_expression(parse_expression(*name_, "utf8")); + set_old_style_expression(parse_expression(*name_, "utf8")); } + + default_format.from_xml(sym, fontsets); + formating::node_ptr n(formating::node::from_xml(sym)); + if (n) set_format_tree(n); } void text_symbolizer_properties::to_xml(boost::property_tree::ptree &node, bool explicit_defaults, text_symbolizer_properties const &dfl) const @@ -186,7 +212,21 @@ void text_symbolizer_properties::to_xml(boost::property_tree::ptree &node, bool { set_attr(node, "vertical-alignment", valign); } - processor.to_xml(node, explicit_defaults, dfl.processor); + default_format.to_xml(node, explicit_defaults, dfl.default_format); + if (tree_) tree_->to_xml(node); +} + + +std::set text_symbolizer_properties::get_all_expressions() const +{ + std::set result; + if (tree_) tree_->add_expressions(result); + return result; +} + +void text_symbolizer_properties::set_old_style_expression(expression_ptr expr) +{ + tree_ = formating::node_ptr(new formating::text_node(expr)); } char_properties::char_properties() : @@ -319,7 +359,7 @@ text_placements::text_placements() : properties() std::set text_placements::get_all_expressions() { std::set result, tmp; - tmp = properties.processor.get_all_expressions(); + tmp = properties.get_all_expressions(); result.insert(tmp.begin(), tmp.end()); result.insert(properties.orientation); return result; @@ -358,11 +398,10 @@ text_placement_info_ptr text_placements_dummy::get_placement_info( bool text_placement_info_simple::next() { while (1) { - if (state == 0) { - properties.processor.defaults.text_size = parent_->properties.processor.defaults.text_size; - } else { + if (state > 0) + { if (state > parent_->text_sizes_.size()) return false; - properties.processor.defaults.text_size = parent_->text_sizes_[state-1]; + properties.default_format.text_size = parent_->text_sizes_[state-1]; } if (!next_position_only()) { state++; @@ -530,14 +569,14 @@ text_placements_list::text_placements_list() : text_placements(), list_(0) std::set text_placements_list::get_all_expressions() { std::set result, tmp; - tmp = properties.processor.get_all_expressions(); + tmp = properties.get_all_expressions(); result.insert(tmp.begin(), tmp.end()); result.insert(properties.orientation); std::vector::const_iterator it; for (it=list_.begin(); it != list_.end(); it++) { - tmp = it->processor.get_all_expressions(); + tmp = it->get_all_expressions(); result.insert(tmp.begin(), tmp.end()); result.insert(it->orientation); } diff --git a/src/text_processing.cpp b/src/text_processing.cpp index 8f65cbce9..b571e34d2 100644 --- a/src/text_processing.cpp +++ b/src/text_processing.cpp @@ -170,7 +170,7 @@ void text_node::apply(char_properties const& p, Feature const& feature, processe text_str = text_str.toTitle(NULL); } if (text_str.length() > 0) { - output.push_back(processed_expression(p, text_str)); + output.push_back(processed_text::processed_expression(p, text_str)); } else { #ifdef MAPNIK_DEBUG std::cerr << "Warning: Empty expression.\n"; @@ -346,61 +346,6 @@ void format_node::set_halo_radius(optional radius) /************************************************************/ -text_processor::text_processor(): - tree_() -{ -} - -void text_processor::set_format_tree(formating::node_ptr tree) -{ - tree_ = tree; -} - -formating::node_ptr text_processor::get_format_tree() const -{ - return tree_; -} - -void text_processor::from_xml(const boost::property_tree::ptree &pt, std::map const &fontsets) -{ - defaults.from_xml(pt, fontsets); - formating::node_ptr n = formating::node::from_xml(pt); - if (n) set_format_tree(n); -} - - -void text_processor::to_xml(boost::property_tree::ptree &node, bool explicit_defaults, text_processor const& dfl) const -{ - defaults.to_xml(node, explicit_defaults, dfl.defaults); - if (tree_) tree_->to_xml(node); -} - -void text_processor::process(processed_text &output, Feature const& feature) const -{ - output.clear(); - if (tree_) { - tree_->apply(defaults, feature, output); - } else { -#ifdef MAPNIK_DEBUG - std::cerr << "Warning: text_processor can't produce text: No formating tree!\n"; -#endif - } -} - -std::set text_processor::get_all_expressions() const -{ - std::set result; - if (tree_) tree_->add_expressions(result); - return result; -} - -void text_processor::set_old_style_expression(expression_ptr expr) -{ - tree_ = formating::node_ptr(new formating::text_node(expr)); -} - -/************************************************************/ - void processed_text::push_back(processed_expression const& exp) { expr_list_.push_back(exp); diff --git a/src/text_symbolizer.cpp b/src/text_symbolizer.cpp index 20e26ffdb..ed67c4c8e 100644 --- a/src/text_symbolizer.cpp +++ b/src/text_symbolizer.cpp @@ -137,7 +137,7 @@ expression_ptr text_symbolizer::get_name() const void text_symbolizer::set_name(expression_ptr name) { - placement_options_->properties.processor.set_old_style_expression(name); + placement_options_->properties.set_old_style_expression(name); } expression_ptr text_symbolizer::get_orientation() const @@ -152,22 +152,22 @@ void text_symbolizer::set_orientation(expression_ptr orientation) std::string const& text_symbolizer::get_face_name() const { - return placement_options_->properties.processor.defaults.face_name; + return placement_options_->properties.default_format.face_name; } void text_symbolizer::set_face_name(std::string face_name) { - placement_options_->properties.processor.defaults.face_name = face_name; + placement_options_->properties.default_format.face_name = face_name; } void text_symbolizer::set_fontset(font_set const& fontset) { - placement_options_->properties.processor.defaults.fontset = fontset; + placement_options_->properties.default_format.fontset = fontset; } font_set const& text_symbolizer::get_fontset() const { - return placement_options_->properties.processor.defaults.fontset; + return placement_options_->properties.default_format.fontset; } unsigned text_symbolizer::get_text_ratio() const @@ -192,62 +192,62 @@ void text_symbolizer::set_wrap_width(unsigned width) bool text_symbolizer::get_wrap_before() const { - return placement_options_->properties.processor.defaults.wrap_before; + return placement_options_->properties.default_format.wrap_before; } void text_symbolizer::set_wrap_before(bool wrap_before) { - placement_options_->properties.processor.defaults.wrap_before = wrap_before; + placement_options_->properties.default_format.wrap_before = wrap_before; } unsigned char text_symbolizer::get_wrap_char() const { - return placement_options_->properties.processor.defaults.wrap_char; + return placement_options_->properties.default_format.wrap_char; } std::string text_symbolizer::get_wrap_char_string() const { - return std::string(1, placement_options_->properties.processor.defaults.wrap_char); + return std::string(1, placement_options_->properties.default_format.wrap_char); } void text_symbolizer::set_wrap_char(unsigned char character) { - placement_options_->properties.processor.defaults.wrap_char = character; + placement_options_->properties.default_format.wrap_char = character; } void text_symbolizer::set_wrap_char_from_string(std::string const& character) { - placement_options_->properties.processor.defaults.wrap_char = (character)[0]; + placement_options_->properties.default_format.wrap_char = (character)[0]; } text_transform_e text_symbolizer::get_text_transform() const { - return placement_options_->properties.processor.defaults.text_transform; + return placement_options_->properties.default_format.text_transform; } void text_symbolizer::set_text_transform(text_transform_e convert) { - placement_options_->properties.processor.defaults.text_transform = convert; + placement_options_->properties.default_format.text_transform = convert; } unsigned text_symbolizer::get_line_spacing() const { - return placement_options_->properties.processor.defaults.line_spacing; + return placement_options_->properties.default_format.line_spacing; } void text_symbolizer::set_line_spacing(unsigned spacing) { - placement_options_->properties.processor.defaults.line_spacing = spacing; + placement_options_->properties.default_format.line_spacing = spacing; } unsigned text_symbolizer::get_character_spacing() const { - return placement_options_->properties.processor.defaults.character_spacing; + return placement_options_->properties.default_format.character_spacing; } void text_symbolizer::set_character_spacing(unsigned spacing) { - placement_options_->properties.processor.defaults.character_spacing = spacing; + placement_options_->properties.default_format.character_spacing = spacing; } unsigned text_symbolizer::get_label_spacing() const @@ -292,42 +292,42 @@ void text_symbolizer::set_max_char_angle_delta(double angle) void text_symbolizer::set_text_size(float size) { - placement_options_->properties.processor.defaults.text_size = size; + placement_options_->properties.default_format.text_size = size; } float text_symbolizer::get_text_size() const { - return placement_options_->properties.processor.defaults.text_size; + return placement_options_->properties.default_format.text_size; } void text_symbolizer::set_fill(color const& fill) { - placement_options_->properties.processor.defaults.fill = fill; + placement_options_->properties.default_format.fill = fill; } color const& text_symbolizer::get_fill() const { - return placement_options_->properties.processor.defaults.fill; + return placement_options_->properties.default_format.fill; } void text_symbolizer::set_halo_fill(color const& fill) { - placement_options_->properties.processor.defaults.halo_fill = fill; + placement_options_->properties.default_format.halo_fill = fill; } color const& text_symbolizer::get_halo_fill() const { - return placement_options_->properties.processor.defaults.halo_fill; + return placement_options_->properties.default_format.halo_fill; } void text_symbolizer::set_halo_radius(double radius) { - placement_options_->properties.processor.defaults.halo_radius = radius; + placement_options_->properties.default_format.halo_radius = radius; } double text_symbolizer::get_halo_radius() const { - return placement_options_->properties.processor.defaults.halo_radius; + return placement_options_->properties.default_format.halo_radius; } void text_symbolizer::set_label_placement(label_placement_e label_p) @@ -407,12 +407,12 @@ bool text_symbolizer::get_allow_overlap() const void text_symbolizer::set_text_opacity(double text_opacity) { - placement_options_->properties.processor.defaults.text_opacity = text_opacity; + placement_options_->properties.default_format.text_opacity = text_opacity; } double text_symbolizer::get_text_opacity() const { - return placement_options_->properties.processor.defaults.text_opacity; + return placement_options_->properties.default_format.text_opacity; } void text_symbolizer::set_vertical_alignment(vertical_alignment_e valign)