From d1f16bb227f911190133beec06d342fad7fd0d0b Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Thu, 26 Jan 2012 13:04:08 +0000 Subject: [PATCH] merge textplacement-merge into feature_impl --- bindings/python/mapnik/__init__.py | 1 - bindings/python/mapnik_glyph_symbolizer.cpp | 121 ---- bindings/python/mapnik_python.cpp | 3 +- bindings/python/mapnik_rule.cpp | 2 - bindings/python/mapnik_shield_symbolizer.cpp | 14 - bindings/python/mapnik_symbolizer.cpp | 16 - bindings/python/mapnik_text_symbolizer.cpp | 52 +- docs/textrendering.gv | 24 + include/mapnik/agg_renderer.hpp | 5 +- include/mapnik/attribute_collector.hpp | 69 +- include/mapnik/cairo_renderer.hpp | 3 - .../mapnik/char_info.hpp | 39 +- include/mapnik/font_engine_freetype.hpp | 274 +------- include/mapnik/glyph_symbolizer.hpp | 215 ------ include/mapnik/grid/grid.hpp | 1 - include/mapnik/grid/grid_renderer.hpp | 3 - include/mapnik/jpeg_io.hpp | 3 + include/mapnik/label_placement.hpp | 53 -- include/mapnik/metawriter.hpp | 6 +- include/mapnik/metawriter_inmem.hpp | 4 +- include/mapnik/metawriter_json.hpp | 4 +- include/mapnik/octree.hpp | 1 + include/mapnik/placement_finder.hpp | 95 +-- include/mapnik/ptree_helpers.hpp | 23 +- include/mapnik/rule.hpp | 9 +- include/mapnik/shield_symbolizer.hpp | 2 + include/mapnik/svg_renderer.hpp | 3 - include/mapnik/symbolizer.hpp | 4 +- include/mapnik/symbolizer_helpers.hpp | 165 +++++ include/mapnik/text_path.hpp | 137 ++-- include/mapnik/text_placements.hpp | 193 ++++-- include/mapnik/text_placements_list.hpp | 61 ++ include/mapnik/text_placements_simple.hpp | 2 +- include/mapnik/text_processing.hpp | 134 ++++ include/mapnik/text_symbolizer.hpp | 100 ++- src/agg/agg_renderer.cpp | 1 + src/agg/process_glyph_symbolizer.cpp | 100 --- 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/agg/process_shield_symbolizer.cpp | 4 +- src/agg/process_text_symbolizer.cpp | 229 +------ src/build.py | 5 +- src/cairo_renderer.cpp | 244 +------ src/font_engine_freetype.cpp | 223 +++++- src/glyph_symbolizer.cpp | 149 ---- 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 + .../process_polygon_pattern_symbolizer.cpp | 1 + src/grid/process_polygon_symbolizer.cpp | 1 + src/grid/process_shield_symbolizer.cpp | 5 +- src/grid/process_text_symbolizer.cpp | 129 +--- src/libxml2_loader.cpp | 10 +- src/load_map.cpp | 640 +++--------------- src/metawriter.cpp | 43 +- src/metawriter_inmem.cpp | 6 +- src/placement_finder.cpp | 603 +++++++++-------- src/save_map.cpp | 244 +------ src/shield_symbolizer.cpp | 9 + src/svg/process_glyph_symbolizer.cpp | 40 -- src/symbolizer.cpp | 8 +- src/text_placements.cpp | 431 +++++++++++- src/text_processing.cpp | 447 ++++++++++++ src/text_symbolizer.cpp | 314 +++------ tests/data/good_maps/also_and_else_filter.xml | 2 +- tests/data/good_maps/glyph_symbolizer.xml | 8 - tests/data/good_maps/sqlite_attachdb.xml | 2 +- tests/data/good_maps/unique_filter_map.xml | 2 +- tests/python_tests/glyph_symbolizer_test.py | 110 --- tests/python_tests/object_test.py | 4 +- .../{data/placement => visual_tests}/clean.sh | 0 .../visual_tests/formating-500-reference.png | Bin 0 -> 8139 bytes tests/visual_tests/formating.xml | 21 + tests/visual_tests/list-100-reference.png | Bin 0 -> 1690 bytes tests/visual_tests/list-150-reference.png | Bin 0 -> 2723 bytes tests/visual_tests/list-200-reference.png | Bin 0 -> 3710 bytes tests/visual_tests/list-250-reference.png | Bin 0 -> 3632 bytes tests/visual_tests/list-300-reference.png | Bin 0 -> 3663 bytes tests/visual_tests/list-400-reference.png | Bin 0 -> 4469 bytes tests/visual_tests/list-600-reference.png | Bin 0 -> 4224 bytes tests/visual_tests/list-800-reference.png | Bin 0 -> 4148 bytes tests/visual_tests/list.xml | 27 + tests/visual_tests/points.dbf | Bin 0 -> 889 bytes tests/visual_tests/points.osm | 43 ++ tests/visual_tests/points.shp | Bin 0 -> 380 bytes tests/visual_tests/simple-100-reference.png | Bin 0 -> 2269 bytes tests/visual_tests/simple-150-reference.png | Bin 0 -> 3484 bytes tests/visual_tests/simple-200-reference.png | Bin 0 -> 3909 bytes tests/visual_tests/simple-250-reference.png | Bin 0 -> 4362 bytes tests/visual_tests/simple-300-reference.png | Bin 0 -> 4645 bytes tests/visual_tests/simple-400-reference.png | Bin 0 -> 5922 bytes tests/visual_tests/simple-600-reference.png | Bin 0 -> 5600 bytes tests/visual_tests/simple-800-reference.png | Bin 0 -> 4402 bytes .../simple-E-500-reference.png | Bin .../placement => visual_tests}/simple-E.xml | 0 .../simple-N-500-reference.png | Bin .../placement => visual_tests}/simple-N.xml | 0 .../simple-NE-500-reference.png | Bin .../placement => visual_tests}/simple-NE.xml | 0 .../simple-NW-500-reference.png | Bin .../placement => visual_tests}/simple-NW.xml | 0 .../simple-S-500-reference.png | Bin .../placement => visual_tests}/simple-S.xml | 0 .../simple-SE-500-reference.png | Bin .../placement => visual_tests}/simple-SE.xml | 0 .../simple-SW-500-reference.png | Bin .../placement => visual_tests}/simple-SW.xml | 0 .../simple-W-500-reference.png | Bin .../placement => visual_tests}/simple-W.xml | 0 tests/visual_tests/simple.xml | 24 + .../{data/placement => visual_tests}/test.py | 5 +- utils/upgrade_map_xml/upgrade_map_xml.py | 3 - workspace/bindings.pri | 1 - workspace/mapnik.pro | 6 - 118 files changed, 2556 insertions(+), 3440 deletions(-) delete mode 100644 bindings/python/mapnik_glyph_symbolizer.cpp create mode 100644 docs/textrendering.gv rename src/grid/process_glyph_symbolizer.cpp => include/mapnik/char_info.hpp (62%) delete mode 100644 include/mapnik/glyph_symbolizer.hpp delete mode 100644 include/mapnik/label_placement.hpp create mode 100644 include/mapnik/symbolizer_helpers.hpp create mode 100644 include/mapnik/text_placements_list.hpp create mode 100644 include/mapnik/text_processing.hpp delete mode 100644 src/agg/process_glyph_symbolizer.cpp delete mode 100644 src/glyph_symbolizer.cpp delete mode 100644 src/svg/process_glyph_symbolizer.cpp create mode 100644 src/text_processing.cpp delete mode 100644 tests/data/good_maps/glyph_symbolizer.xml delete mode 100644 tests/python_tests/glyph_symbolizer_test.py rename tests/{data/placement => visual_tests}/clean.sh (100%) create mode 100644 tests/visual_tests/formating-500-reference.png create mode 100644 tests/visual_tests/formating.xml create mode 100644 tests/visual_tests/list-100-reference.png create mode 100644 tests/visual_tests/list-150-reference.png create mode 100644 tests/visual_tests/list-200-reference.png create mode 100644 tests/visual_tests/list-250-reference.png create mode 100644 tests/visual_tests/list-300-reference.png create mode 100644 tests/visual_tests/list-400-reference.png create mode 100644 tests/visual_tests/list-600-reference.png create mode 100644 tests/visual_tests/list-800-reference.png create mode 100644 tests/visual_tests/list.xml create mode 100644 tests/visual_tests/points.dbf create mode 100644 tests/visual_tests/points.osm create mode 100644 tests/visual_tests/points.shp create mode 100644 tests/visual_tests/simple-100-reference.png create mode 100644 tests/visual_tests/simple-150-reference.png create mode 100644 tests/visual_tests/simple-200-reference.png create mode 100644 tests/visual_tests/simple-250-reference.png create mode 100644 tests/visual_tests/simple-300-reference.png create mode 100644 tests/visual_tests/simple-400-reference.png create mode 100644 tests/visual_tests/simple-600-reference.png create mode 100644 tests/visual_tests/simple-800-reference.png 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%) create mode 100644 tests/visual_tests/simple.xml rename tests/{data/placement => visual_tests}/test.py (89%) diff --git a/bindings/python/mapnik/__init__.py b/bindings/python/mapnik/__init__.py index aa96897d9..f646133cb 100644 --- a/bindings/python/mapnik/__init__.py +++ b/bindings/python/mapnik/__init__.py @@ -611,7 +611,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..bb9198c2e 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(); @@ -84,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" @@ -434,7 +434,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_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_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/bindings/python/mapnik_text_symbolizer.cpp b/bindings/python/mapnik_text_symbolizer.cpp index 69d793ba7..31515f11a 100644 --- a/bindings/python/mapnik_text_symbolizer.cpp +++ b/bindings/python/mapnik_text_symbolizer.cpp @@ -48,27 +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])); -} - -void set_placement_options(text_symbolizer & t, placement_type_e arg, std::string const& placements) -{ - t.set_placement_options(arg, placements); -} - -void set_placement_options_2(text_symbolizer & t, placement_type_e arg) -{ - t.set_placement_options(arg, ""); -} - } struct text_symbolizer_pickle_suite : boost::python::pickle_suite @@ -86,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 @@ -105,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 ); } @@ -146,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])); @@ -190,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") @@ -205,11 +179,6 @@ void export_text_symbolizer() .value("CAPITALIZE",CAPITALIZE) ; - enumeration_("placement_type") - .value("SIMPLE",T_SIMPLE) - .value("DUMMY",T_DUMMY) - ; - class_("TextSymbolizer",init()) /* // todo - all python classes can have kwargs and default constructors @@ -226,9 +195,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, @@ -302,10 +268,6 @@ void export_text_symbolizer() &text_symbolizer::get_text_opacity, &text_symbolizer::set_text_opacity, "Set/get the text opacity") - .add_property("placement", - &text_symbolizer::get_label_placement, - &text_symbolizer::set_label_placement, - "Set/get the placement of the label") .add_property("text_transform", &text_symbolizer::get_text_transform, &text_symbolizer::set_text_transform, @@ -329,9 +291,5 @@ void export_text_symbolizer() .add_property("wrap_before", &text_symbolizer::get_wrap_before, &text_symbolizer::set_wrap_before) - .add_property("placement_type", &text_symbolizer::get_placement_type) - .add_property("placements", &text_symbolizer::get_placements) - .def("set_placement_options", set_placement_options) - .def("set_placement_options", set_placement_options_2) ; } diff --git a/docs/textrendering.gv b/docs/textrendering.gv new file mode 100644 index 000000000..90a897a6a --- /dev/null +++ b/docs/textrendering.gv @@ -0,0 +1,24 @@ +#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_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()"] + text_path -> Renderer [color=red, label="used by"] + Renderer -> text_placement_info [color=red, label="init()"] + Renderer -> processed_text [color=red, label="initializes"] + +} 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..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(); @@ -167,45 +157,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/src/grid/process_glyph_symbolizer.cpp b/include/mapnik/char_info.hpp similarity index 62% rename from src/grid/process_glyph_symbolizer.cpp rename to include/mapnik/char_info.hpp index b21453a7a..a1417735f 100644 --- a/src/grid/process_glyph_symbolizer.cpp +++ b/include/mapnik/char_info.hpp @@ -19,23 +19,34 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ -// mapnik -#include -#include +#ifndef CHAR_INFO_HPP +#define CHAR_INFO_HPP + +#include namespace mapnik { +struct char_properties; -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"; -} +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) + { + } -template void grid_renderer::process(glyph_symbolizer const&, - Feature const&, - proj_transform const&); + unsigned c; + double width; + double line_height; + double ymin; + double ymax; + double avg_height; + char_properties *format; + double height() const { return ymax-ymin; } +}; } +#endif diff --git a/include/mapnik/font_engine_freetype.hpp b/include/mapnik/font_engine_freetype.hpp index e72dc4538..5ded5076d 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,9 +172,9 @@ 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); + void get_string_info(string_info & info, UnicodeString const& ustr, char_properties *format); void set_pixel_sizes(unsigned size) { @@ -200,7 +193,7 @@ public: } private: std::vector faces_; - std::map dimension_cache_; + std::map dimension_cache_; }; // FT_Stroker wrapper @@ -313,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_; @@ -330,247 +335,21 @@ 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_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; - } - - 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; @@ -583,7 +362,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); } } } @@ -610,14 +389,11 @@ private: } pixmap_type & pixmap_; - face_set_ptr faces_; + 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; } #endif // MAPNIK_FONT_ENGINE_FREETYPE_HPP 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.hpp b/include/mapnik/grid/grid.hpp index 03679eab2..398826139 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 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/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 } 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/include/mapnik/metawriter.hpp b/include/mapnik/metawriter.hpp index a25be915b..68792f4e4 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 @@ -103,8 +103,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/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/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/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/include/mapnik/rule.hpp b/include/mapnik/rule.hpp index 1fa8def03..9895578c3 100644 --- a/include/mapnik/rule.hpp +++ b/include/mapnik/rule.hpp @@ -34,7 +34,6 @@ #include #include #include -#include #include #include #include @@ -109,11 +108,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/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/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/include/mapnik/symbolizer.hpp b/include/mapnik/symbolizer.hpp index 546c4c1fe..159664617 100644 --- a/include/mapnik/symbolizer.hpp +++ b/include/mapnik/symbolizer.hpp @@ -96,10 +96,10 @@ 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_; + float image_opacity_; transform_type matrix_; }; } diff --git a/include/mapnik/symbolizer_helpers.hpp b/include/mapnik/symbolizer_helpers.hpp new file mode 100644 index 000000000..e471bb026 --- /dev/null +++ b/include/mapnik/symbolizer_helpers.hpp @@ -0,0 +1,165 @@ +/***************************************************************************** + * + * 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_); + + 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); + + 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 +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/include/mapnik/text_path.hpp b/include/mapnik/text_path.hpp index eeebd97c4..b7d7daaf1 100644 --- a/include/mapnik/text_path.hpp +++ b/include/mapnik/text_path.hpp @@ -23,6 +23,12 @@ #ifndef MAPNIK_TEXT_PATH_HPP #define MAPNIK_TEXT_PATH_HPP +// mapnik +#include + +//stl +#include + // boost #include #include @@ -32,41 +38,39 @@ 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_; - double width_; - double height_; + UnicodeString text_; bool is_rtl; public: string_info(UnicodeString const& text) - : text_(text), - width_(0), - height_(0), - is_rtl(false) {} - - void add_info(int c, double width, double height) + : characters_(), + text_(text), + is_rtl(false) { - characters_.push_back(new character_info(c, width, height)); + + } + + 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; } unsigned num_characters() const @@ -74,29 +78,25 @@ 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); } - - 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 { @@ -108,69 +108,84 @@ 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 + +/** List of all characters and their positions and formats for a placement. */ +class text_path : boost::noncopyable { struct character_node { 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; } }; + 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) {} - - //text_path(text_path const& other) : - // itr_(0), - // nodes_(other.nodes_), - // string_dimensions(other.string_dimensions) - //{} + : itr_(0), + starting_x(0), + starting_y(0) + + { + + } ~text_path() {} - void add_node(int c, double x, double y, double angle) + /** 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)); + nodes_.push_back(character_node(c, x, y, angle, format)); } - void vertex(int *c, double *x, double *y, double *angle) + /** 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); + 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 7921031c0..fdb6530dc 100644 --- a/include/mapnik/text_placements.hpp +++ b/include/mapnik/text_placements.hpp @@ -24,20 +24,30 @@ #define MAPNIK_TEXT_PLACEMENTS_HPP // mapnik -#include -#include +#include +#include +#include +#include +#include // stl #include #include +#include +#include // boost #include #include #include +#include namespace mapnik { +class text_placements; + +typedef text_path placement_element; + typedef boost::tuple position; enum label_placement_enum { @@ -82,89 +92,140 @@ enum justify_alignment DEFINE_ENUM( justify_alignment_e, justify_alignment ); -enum text_transform +/** Contains all text symbolizer properties which are not directly related to text formating. */ +struct text_symbolizer_properties { - NONE = 0, - UPPERCASE, - LOWERCASE, - CAPITALIZE, - text_transform_MAX -}; + 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; -DEFINE_ENUM( text_transform_e, text_transform ); - -enum placement_type -{ - T_SIMPLE = 0, - T_DUMMY, - placement_type_MAX -}; - -DEFINE_ENUM( placement_type_e, placement_type ); - -class text_placements; - -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. - * 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 ~text_placement_info() {} - - /* NOTE: Values are public and non-virtual to avoid any performance problems. */ + //Per symbolizer options + expression_ptr orientation; position displacement; - float text_size; + label_placement_e label_placement; horizontal_alignment_e halign; justify_alignment_e jalign; vertical_alignment_e valign; + /** 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; + /** Always try render an odd amount of labels */ + bool force_odd_labels; + bool allow_overlap; + unsigned text_ratio; + unsigned wrap_width; + /** 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! + */ + 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 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() : - text_size_(10), halign_(H_MIDDLE), jalign_(J_MIDDLE), valign_(V_MIDDLE) {} + 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(); - 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_; } - + /** Destructor. */ virtual ~text_placements() {} -protected: - float text_size_; - position displacement_; - horizontal_alignment_e halign_; - justify_alignment_e jalign_; - vertical_alignment_e valign_; - friend class text_placement_info; + + /** 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: @@ -172,20 +233,20 @@ 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: 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_; }; + } //namespace #endif // MAPNIK_TEXT_PLACEMENTS_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 diff --git a/include/mapnik/text_placements_simple.hpp b/include/mapnik/text_placements_simple.hpp index 9a52825e8..36d46c49b 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/include/mapnik/text_processing.hpp b/include/mapnik/text_processing.hpp new file mode 100644 index 000000000..450acb0bb --- /dev/null +++ b/include/mapnik/text_processing.hpp @@ -0,0 +1,134 @@ +/***************************************************************************** + * + * 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(); + /** 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; + 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 +{ +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 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); + /** 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); +private: + std::list list_; + bool clear_on_write; //Clear list once +}; + +} /* namespace */ + +#endif diff --git a/include/mapnik/text_symbolizer.hpp b/include/mapnik/text_symbolizer.hpp index 03beb5b04..3519bc186 100644 --- a/include/mapnik/text_symbolizer.hpp +++ b/include/mapnik/text_symbolizer.hpp @@ -26,8 +26,6 @@ // mapnik #include #include -#include -#include #include #include @@ -38,6 +36,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 { @@ -45,6 +50,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(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) @@ -54,73 +60,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; - void set_anchor(double x, double y); - position const& get_anchor() 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); void set_placement_options(placement_type_e arg, std::string const& placements); @@ -128,32 +132,6 @@ struct MAPNIK_DECL text_symbolizer : public symbolizer_base std::string get_placements() const; 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_; - position anchor_; - 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_; }; } 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_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/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/agg/process_shield_symbolizer.cpp b/src/agg/process_shield_symbolizer.cpp index 7d2c02741..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; @@ -139,7 +140,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(); @@ -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 91e73b2e6..fd857a576 100644 --- a/src/agg/process_text_symbolizer.cpp +++ b/src/agg/process_text_symbolizer.cpp @@ -24,231 +24,32 @@ // mapnik #include #include -#include +#include namespace mapnik { -struct largest_bbox_comp -{ - bool operator() (geometry_type const* g0, geometry_type const* g1) const - { - box2d b0 = g0->envelope(); - box2d b1 = g1->envelope(); - return b0.width()*b0.height() < b1.width()*b1.height(); - } - -}; - -typedef boost::tuple point_type; -typedef std::vector label_points_type; - -void get_label_points(label_points_type & labels, - Feature const& feature, - text_symbolizer const& sym, - proj_transform const& prj_trans, - CoordTransform const& t - ) -{ - 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; - } - } - geometries_to_process.push_back(const_cast(&geom)); - } - - vector::const_iterator largest = std::max_element(geometries_to_process.begin(), - geometries_to_process.end(), - largest_bbox_comp()); - if (largest != geometries_to_process.end()) - { - double z=0.0; - double label_x,label_y; - if (sym.get_label_placement() == POINT_PLACEMENT) - (*largest)->label_position(&label_x, &label_y); - else if (sym.get_label_placement() == INTERIOR_PLACEMENT) - (*largest)->label_interior_position(&label_x, &label_y); - else // default to bbox center - { - box2d box = (*largest)->envelope(); - coord2d c = box.center(); - label_x = c.x; - label_y = c.y; - } - prj_trans.backward(label_x,label_y, z); - t.forward(&label_x,&label_y); - - labels.push_back(boost::make_tuple(label_x,label_y)); - } - - /* - std::sort(geometries_to_process.begin(), geometries_to_process.end(), largest_bbox_first()); - BOOST_FOREACH( geometry_type * g, geometries_to_process) - { - double z=0.0; - double label_x,label_y; - if (sym.get_label_placement() == POINT_PLACEMENT) - g->label_position(&label_x, &label_y); - else - g->label_interior_position(&label_x, &label_y); - prj_trans.backward(label_x,label_y, z); - t.forward(&label_x,&label_y); - - labels.push_back(boost::make_tuple(label_x,label_y)); - } - */ -} - template void agg_renderer::process(text_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { - label_points_type labels; - get_label_points(labels, feature, sym, prj_trans, t_); - - if (labels.size() == 0) return; - - typedef coord_transform2 path_type; + /* 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); - 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) + if (!placement) return; + + text_renderer ren(pixmap_, font_manager_, *(font_manager_.get_stroker())); + for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) { - text = text.toUpper(); - } - else if ( sym.get_text_transform() == LOWERCASE) - { - text = text.toLower(); - } - else if ( sym.get_text_transform() == CAPITALIZE) - { - text = text.toTitle(0); - } - - if ( text.length() <= 0 ) return; - 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() + "'"); - } - -///////////////////////// -////////////// renderer - text_renderer ren(pixmap_, faces, *strk); - 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); - - -/////////// - - bool placement_found = false; - text_placement_info_ptr placement_options = sym.get_placement_options()->get_placement_info(); - while (!placement_found && placement_options->next()) - { - - ren.set_character_size(placement_options->text_size * scale_factor_); - - string_info info(text); - faces->get_string_info(info); - metawriter_with_properties writer = sym.get_metawriter(); - - //BOOST_FOREACH( geometry_type * geom, geometries_to_process ) - BOOST_FOREACH (point_type const& pt, labels) - { - //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, boost::get<0>(pt),boost::get<1>(pt), - 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); - } - } + 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/build.py b/src/build.py index 511d2aa50..b5a4330d0 100644 --- a/src/build.py +++ b/src/build.py @@ -147,11 +147,11 @@ source = Split( symbolizer.cpp arrow.cpp unicode.cpp - glyph_symbolizer.cpp markers_symbolizer.cpp metawriter.cpp raster_colorizer.cpp text_placements.cpp + text_processing.cpp wkt/wkt_factory.cpp metawriter_inmem.cpp metawriter_factory.cpp @@ -223,7 +223,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 @@ -244,7 +243,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 @@ -264,7 +262,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 a31b0de9a..13db7f47e 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; @@ -587,8 +584,13 @@ public: { int c; double x, y, angle; + char_properties *format; - path.vertex(&c, &x, &y, &angle); + 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); @@ -608,42 +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; - - path.vertex(&c, &x, &y, &angle); - - 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); } } @@ -1080,11 +1051,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() ) @@ -1146,7 +1117,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(); @@ -1280,6 +1251,7 @@ void cairo_renderer_base::process(shield_symbolizer const& sym, } } } +#endif } void cairo_renderer_base::process(line_pattern_symbolizer const& sym, @@ -1459,187 +1431,21 @@ 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) { - 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); - - 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_); } } diff --git a/src/font_engine_freetype.cpp b/src/font_engine_freetype.cpp index 188aaa59d..5e58fa89c 100644 --- a/src/font_engine_freetype.cpp +++ b/src/font_engine_freetype.cpp @@ -19,10 +19,12 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ // mapnik #include +#include +#include +#include // boost #include @@ -192,9 +194,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,33 +225,30 @@ 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; } -void font_face_set::get_string_info(string_info & info) + +void font_face_set::get_string_info(string_info & info, UnicodeString const& ustr, char_properties *format) { - unsigned width = 0; - unsigned height = 0; + double avg_height = character_dimensions('X').height(); 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); @@ -270,10 +270,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); - width += char_dim.width; - height = (char_dim.height > height) ? char_dim.height : height; + char_info char_dim = character_dimensions(ch); + char_dim.format = format; + char_dim.avg_height = avg_height; + info.add_info(char_dim); } } @@ -286,11 +286,198 @@ void font_face_set::get_string_info(string_info & info) #endif ubidi_close(bidi); - info.set_dimensions(width, height); +} + +template +text_renderer::text_renderer (pixmap_type & pixmap, face_manager &font_manager_, stroker & s) + : pixmap_(pixmap), + font_manager_(font_manager_), + stroker_(s) +{ + +} + +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; + char_properties *properties; + + path->vertex(&c, &x, &y, &angle, &properties); + +#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); + + face_set_ptr faces = font_manager_.get_face_set(properties->face_name, properties->fontset); + faces->set_character_sizes(properties->text_size); + + 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, properties)); + } + + 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; + 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) + { + 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, pos->properties->halo_fill.rgba(), + bit->left, + height - bit->top, pos->properties->text_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, pos->properties->fill.rgba(), + bit->left, + height - bit->top, pos->properties->text_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; + 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) + { + 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_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_manager&, stroker&); +template box2dtext_renderer::prepare_glyphs(text_path*); } 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/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" diff --git a/src/grid/process_shield_symbolizer.cpp b/src/grid/process_shield_symbolizer.cpp index d0f61cd67..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; @@ -120,7 +121,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(); @@ -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 e88aed6c5..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,120 +34,26 @@ void grid_renderer::process(text_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { - 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); - 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); + pixmap_.add_feature(feature); + } template void grid_renderer::process(text_symbolizer const&, 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 41c07c1d4..c6e3f47e8 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -48,6 +48,10 @@ #include #include +#include +#include +#include +#include // boost #include @@ -108,7 +112,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); @@ -653,7 +656,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; @@ -846,10 +849,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" && @@ -1246,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+"'")); } @@ -1276,214 +1281,25 @@ 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 + "'"); + 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); } } - 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); } @@ -1498,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," @@ -1508,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 ) @@ -1553,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 ) { @@ -1763,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) { @@ -1994,117 +1651,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/metawriter.cpp b/src/metawriter.cpp index f3d72a9a0..e54dcac2a 100644 --- a/src/metawriter.cpp +++ b/src/metawriter.cpp @@ -23,7 +23,7 @@ // Mapnik #include #include -#include +#include // Boost #include @@ -174,8 +174,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) @@ -192,18 +192,19 @@ 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) { 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; @@ -216,14 +217,13 @@ 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); - font_face_set::dimension_t 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"; - + 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); + 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, @@ -239,9 +239,10 @@ 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; - font_face_set::dimension_t ci = face->character_dimensions(c); + 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; double sina = sin(angle); @@ -250,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); diff --git a/src/metawriter_inmem.cpp b/src/metawriter_inmem.cpp index d32b6a571..12cec9573 100644 --- a/src/metawriter_inmem.cpp +++ b/src/metawriter_inmem.cpp @@ -69,9 +69,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) { diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index c965d03cf..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,245 +180,270 @@ 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++) { - character_info ci; - ci = p.info.at(ii); + char_info const& ci = info_.at(ii); + unsigned c = ci.c; - double cwidth = ci.width + character_spacing; - - unsigned c = ci.character; - 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++) { - character_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.character; + 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; - 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 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) { - character_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.character; - //Coordinates this character will start at if (segment_length == 0) { // Not allowed to place across on 0 length segments or discontinuities @@ -826,19 +795,20 @@ 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, -render_y + current_placement->starting_y, - render_angle); + render_angle, ci.format); //Normalise to 0 <= angle < 2PI while (render_angle >= 2*M_PI) @@ -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,57 +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 - character_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; - current_placement->vertex(&c, &x, &y, &angle); + 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; } @@ -932,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(); @@ -1000,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); } } } @@ -1032,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 diff --git a/src/save_map.cpp b/src/save_map.cpp index 3cf56b014..87bc14c4f 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include // boost #include @@ -310,100 +312,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(); @@ -451,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)); + } } } 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, 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/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 diff --git a/src/text_placements.cpp b/src/text_placements.cpp index 9ed8b20b2..a9b9d77c4 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,14 +41,298 @@ 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); + } +} + +/************************************************************************/ + +text_placements::text_placements() : properties() +{ +} + +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; +} /************************************************************************/ 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), + collect_extents(false) { } @@ -55,73 +344,81 @@ 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)); } +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_; +} /************************************************************************/ 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"; @@ -130,13 +427,12 @@ 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)); } -/** 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 @@ -196,4 +492,75 @@ 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() +{ + 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 diff --git a/src/text_processing.cpp b/src/text_processing.cpp new file mode 100644 index 000000000..7d616ff18 --- /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_character_sizes(p.text_size * scale_factor_); + faces->get_string_info(info_, itr->str, &(itr->p)); + info_.add_text(itr->str); + } + return info_; +} + + + +} /* namespace */ diff --git a/src/text_symbolizer.cpp b/src/text_symbolizer.cpp index 2b43527e2..2966e73d1 100644 --- a/src/text_symbolizer.cpp +++ b/src/text_symbolizer.cpp @@ -22,15 +22,11 @@ //$Id$ -#include - //mapnik #include -#include -#include - // boost #include +#include namespace mapnik { @@ -90,471 +86,363 @@ static const char * text_transform_strings[] = { IMPLEMENT_ENUM( text_transform_e, text_transform_strings ) -static const char * placement_type_strings[] = { - "dummy", - "simple", - "" -}; - - -IMPLEMENT_ENUM( placement_type_e, placement_type_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), - anchor_(0.0,0.5), - 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), - anchor_(0.0,0.5), - 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_), - anchor_(rhs.anchor_), - 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_; - anchor_ = other.anchor_; - 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_anchor(double x, double y) +void text_symbolizer::set_displacement(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)); + placement_options_->properties.displacement = boost::make_tuple(x,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 { - 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 @@ -567,47 +455,5 @@ void text_symbolizer::set_placement_options(text_placements_ptr placement_option placement_options_ = placement_options; } -void text_symbolizer::set_placement_options(placement_type_e arg, std::string const& placements) -{ - text_placements_ptr placement_finder; - switch (arg) - { - case T_SIMPLE: - placement_finder = text_placements_ptr( - new text_placements_simple(placements)); - break; - - case T_DUMMY: - placement_finder = text_placements_ptr(new text_placements_dummy()); - break; - - default: - throw config_error(std::string("Unknown placement type")); - break; - } - this->set_placement_options(placement_finder); -} - -placement_type_e text_symbolizer::get_placement_type() const -{ - text_placements_ptr placement_finder = this->get_placement_options(); - if (dynamic_cast(placement_finder.get()) != NULL) - { - return T_SIMPLE; - } - return T_DUMMY; -} - -std::string text_symbolizer::get_placements() const -{ - text_placements_ptr placement_finder = this->get_placement_options(); - text_placements_simple *placements_simple = dynamic_cast(placement_finder.get()); - - if (placements_simple != NULL) - { - return placements_simple->get_positions(); - } - return ""; -} } 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/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/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/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/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')) 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/visual_tests/formating-500-reference.png b/tests/visual_tests/formating-500-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..0ab02b598955b5f4ab0ff0b001234dc24332d7bd GIT binary patch literal 8139 zcmcI}XEa>z*ESL@`cI;lAfopm!C;8qLK5BREf{U|7DjK0AP7c_lISJcC_$9yy&FUu zy&K(o@_YX4d7n@3dcQnh=A3oboOAEJ?|ol;U)PR$rKv>nfbIbn78Z$$@=F~oEbJ5D z-iiPZ_;u-S55U4={-p8}{Kh+DHxqaGN#~<(GA}Q+<-ImWZt} zZbAPl$;}! zTrLt}t6WF)`0rgC8=N1wzQLk}v4oQ6;`C$_0oO;ecqRlap+#82XgLCwm$)Cn)WDVd zJ#izs9Dz2r)o>W_>VwDsd#|>iFGn!X#|X5brEkwypwtRh=a<{%3kS;`3q0jbf27Zs z?qQDl1iX;Sa!)i`Clgt=%UfGkL>#9pbcFp6+VB_VoLfy)A4U3T!Y=)y3Djb=sU33n-V*@WKHCE## zwm8L6-YdyEpEVO3OFBQi0c$lIYvFZ#j1xyqA6{H7-bthTTW-#b9K3H2B4mr~ZAxG5i12KCOqpy19;YuR{k5Q~q*LoZ7RZ0&_o6+q5KQV> z|5k|L207E7xTU@O{%wBNp?u+UlaYPX?O9!GnkK^gPgHFZJFZT`54+ftar$vu*t)is zcRUf~541Z%$I&4%$<2MlhdgPyIp8m(Yb@OhM zSOda+yig!ybwdNBV_#X zxQ5d1?$*u?25RAAIa}EPi(_I;uiNLRX}Y)yQrr2OPewg_M0xz`?5&0+*)fXtme#vt z<~=C}Hnd)Q4I75m7yChX^!f1d)B6f>)G>}lOEG-4slVeiN#cIdSjK9nOBi_0STAI_ z5859gHxPb1ivwbF{mpl`S9^!u%ys_Vj9&9!c3er;+t3WOEZdgxFSYUUkag$$_J2F0Hi2X~32F`;_WSHt9}c+s}ZUpMWN6h*f$m zmm&AK74}L$MI$V%6Eo#M+?e!wu!nyEcMl)9Y-9xmnuHiZ8#>EmfJE3^f|GfQ^b5+H z+mzM^?j!f5)zrxNtN<4(qJyiN5B0s5!xk`$L8fZ%ZMf9ar@$#?Yus5V=*jjn+#4|7 z8?f3P(+0O*?IAxS@~BV#hG5E$yow2*`);ea9LiXrt;Fc@b%V&hv-uoIOpmW#Pv>4$ zKDP%VS9Y}!Mh&rRwS4x^>pkeFkP^$I=mc|v#-Ha0^ms3rww|C$paVpO*&LC{8VK-* zi~l&Gghhb3kbp?v`2gS8x%|zM)#e$nGe;8t4iiz(nWkGVP945$B|EzCXP#K|C%$l!JOx)y~+YH&z|DZOfK3 z9T+e5BDcO5JC1+OcZA2+J)NU9bnSfPq*{>d<`whCYJszS`Y0)%D3%QV#Sx^WSs#Q1 zbNfdBHP!hHIliq*EDCP{4(DuN4^>(d8T3JD@Bv4@cSVjTJnU+X z>u^HP^oLaH9H(JX*!zaTrninWNR0&We$Bj*$-|W7A6nF99+3g@kYUPIQ3!Ctf=YHu zB!+k-NwBj`3pi6*m*p+v{f1uGf|auRPj_p^G|&D*g+1ofHE%B8J0+zb=O#Ib%he8+`EMVp)V z-OCdWq->YM^)dF{%pXxKNUrjVVTGgY8&a%?t2ZtF-FKfjYWEx|3{Xy(lJPT3f$A|@ z+H}-=hLqfI8Y@e28V>G?_8{m<=7TvPGdrM5*m9dN6~wo;o)8-K~9*SF)iZJ0$id|_7L7Juut z+iM)7{Ku0$zml5g9@3s@_BC_vnvwYS|INJYqC<;Arc6${U}}hW8`a<|8{OTimG`?Z zkozsj-$^KGqw8s0a!-p?rPX}^ohLv?J#2dbnsf0gm4NYUYqe93Bz_O{E@F2Gpn{F?+ zCq=F>t#=@F6J0XMLxK>0%hzD1 z>3DG6caLK>2Yx`8P(tuWME`5L8>}N^+d1gUh)DeRY?Pw8VD(8l>GPIEZ(@TBaS0jL z(8s!&p?nQ-tOakQG-pWV42t8gU*>>qA&Hzy{d2htwN&H|nRg=lo4@%l&C1$_#4B1W zirm)tsD7sx5{xkhT_5JtWI=LFBnZl#eoW|!)g|lwZg=px`~>ZtA{puOpLf(Gf|D$-;W?@zf6=WLUxYt3;-*Rwkjs3GetN6YnoLMr+Fllf*^77ZR^4r3N zTDBzj+V~SB3D-09NnnSav?Gtjetkr0y+cW5IAev}P>jO;lfX$MXAn|xyxhJZtxNY= zgdc*I;z-Ax|9Lc?+`jA9>=%bJ`!<~(o^+Xt4=?E5YI|8rD?fc4M?U}k1| zd^~OC!UbY_Njm&F$I}8Pefx@PBN?jjx*bE;Z?Q<}?TpHIqeR9WZUr|X& z@G7+Y8r9Lf98N#ksV}pU;jTFAo2HSNGITcQyua6a7l;g(HXzJQAapS^`ZGurcWcLAq{za1s7s zVYeLxeG}(h;3)Rq*})6@iWj!@k&236YgwPO;b3^mTt}($+kURD$-hxk+(gsg(}uMQ zMzi@Wo4nX>I(CHC_tq3cfvhoAvXT#Eoyx9m{D>%s=y?%Hij&IL&fXrQA+fHO>Sghk ztyv@q%+!R=;UP|u)b8swob+RPS&Fn{2m>x(X z^vJ#rdTD@Phw^n8OT-mwGUqg3jlb^jndSAdue$h!5+KSHmb*B+w@^m!gV{`x(6Jk) zxF18J^PW_8rgsllXB12^s)5-QXEejodC|i`Wju5vr|Qu$CgAZh;m=><`(RiX&VW_@V%grt zrLV$r(w7(CZ74dw6L3H}(GiyXeP+KRDe^Zji}K>#R^r4VTs|Hlkk6vP=U%p?1FY{$ z0S~eX&SGtn%wC8PLZ=wJMa_Ay#5pi)uIIF^>o*-p6y6Y1wU2et!F5Ms8`4 z?;MeaCwaX6oZ;J*ZM)2CNq1R@@cn+$WIb%e_Q(V)!ouY`zbK;13+R)eYA7zzS*~HS zsyzibLW#Ta6WH18W#nnUy)qGheReuKV{it$3)YEr^t z0<-nT6Rx0N_TRr|6Ono?Npg2uC2HuZsWA;$MW-WbZht)`8|v_o(d%I$yB?tUnK7px zIA2b^zDX}?T;6mPovAE(I{q3N#etYEt!RHBHn7YhCeHC>5MY{5TmRBkMd2Q*1vk{G zU0$4S;)_DtMV&)Ocv7$7E$=65oC_PZ|35q)>)LhkKXhkwN#oKvG=< zCAz4*X)eQ8F<{Pp2(&#b;yi5Xf2gqiQtLk~vocQ|bdSrqPz9_vFe=8=4XkLdl#myr zN(UQc?n-XgPHKuR%y5gQjrlQY*w^xx;4EYR>T~Gei;Wt^o*g%l3>(?ar z>Q^SM;_2%m6@9>-ni#-F(KJ(+HrzAuOp(|DK&&yH;!=-#W5wv+TGGVqy@V~kRg`CV zbKPfp`)5$*&R;~oKL48%6Y=GLlg)WIsm-V9juwrL+hs^zjpDUkJTCNi?b%ef(8i(+ z@P>2QF>Cm|TOXh2&4?1S2R^fZMY?%7?So_fjy28BC5c`fx*)|uHQvh5w^&P3-lMN* zms+m&s#`m%Kbr&8QxLvN-#o6YKrOp3R+23w>@<=6I?B%p+ETRqhmHhn$8h+4_qg^*32m}XC|{-B-63D!GF@qG28Jg>l@E@=axHtk9@ zS3guyp#CU|{?~!L4hUQMvRtjV)?bz>adXgC-QI(BpuD8n46w}k2;BjPRsYbnlVH?1i*v528e779vj(QyGpR;}0paldo_fvUG z9H~2?YB^A|?IF90=4vG)U4K?!)s z=wUa5ur>5Y6QKT>Jj##bQ!0w-r9BBao9XuDF%7!$7?W~I|8thF6fKzYmv=%wgJqUO zDdzz+u*AqN({S~MT^$Tc?!(43B}e5fD?P z)_cMBE*2}M`pXDsX_mYV{R9pX?O?6*@HPBa=l zY7y(g@_Nu^djN=x^At^Um1sBI-27pC`=|U?R}?*QE`r@<*)=UhfzBolK7f;@=KCTf2jsQG~Jj+Nbv11oS}-m zE(X@1EZ?nfdw>e-IwWZPNh648boSiCznl>RFKN0dOC%AW zAGXqL&cCe)G>;RVEXt1&MVBVk0lQzETO%YtYr2?+SQ+`;()#Stf(2lBnR zm6U!dY5AxWf2D1brWXGI)XSZ#ZODx`1qDJpD@qmR8V8y*n080Am0ysBrXHTMqvN!ST?t6?@8N@d?xMa z+ROH|eE8>MFGVSQ1{YZq>1U4WMwNn`3cxAuzj-{w7D8bw$#*ZlVf{#})9oXEHH-cl-Vwpfqq)d3Ds{EQc zD<^APg@NX+=q1jthI+h>+YZ#C+zx+rnx9k$c$>TS1E=0ZHb$f z=#~-E-1_O^P=X9|yo5C*^6tt^*ymXOmc5Hz`Fx&t$B1lW-I6;kLh6``^Ne!$JIBnk z64F7%-QyxilBqydt0g}b<3>P4@S!cQHz?vlr|_~80UHfpbptxRy0f3;F5gou z&A0YSpx*`N4y0}3)6m*cRUU&UyePtxzVGG}F>c!msD1B!LM4qN9mSOrW2GrH{t)5r zLkiv~=J>(ti!)1*eI3cNp%~fYQ2w*~H^OEti#8r#NAWUI>_$ZGa6Ix)`#DDf0u|-T z*IAaE4t!*_ou@0&4t9E=!2>ih#9;udacONzZQm_K6|?NZ}4_Dd&?&xJ%Palb;>?fJH0{ zz%xLPQtApOB>0$;9?*AELZdKDNlI8w!5fC)SH$UPM}K5`s6_B7XgZNiYhL%O#?AXE z&z}u_<~5S6)f7zBb~~Gu!vDxJ)7XRz4nL+i2_yYyk8ho3#+Rx+gmFH(M=Z8SsY7A< zrElGB4J)j@Me~SW+P~hU1Gk^^tEs&nsEv`KfaSIwkTaYXh2);x%F-yjI{l;6TSTqU z$_B!)KK~H0{mat~b`JbwMzqP0=2CDmu!JMCg2l%td+}ggB4}NyT0+MjuKAVCH;swT zg*zBu?*)IXR1ta8NqtTvK2u2L*%VCxrLy_W=~P`S;dXBk16t+cg7l2rVn;|~HQg;c z)ic;g$R7%UP%U5)xjyf1BCsf1iEj5u1qEeLJb&~dL7t}eA0IZaMQ?MnNZD>ba=P2& zLCK-@>dR!$i9|;_;t4>4f*R!%u`$HLgR%cjxRL!LX8#S7%iAZC@jj&6qogWb<{C~Qft+$}}uu2b*4natvH zlx-2pU$G+^vUQcZ!ytkt({5XftYnse6^WYFYtv|Sb+MPzoyc~$mk5=;{B&Hw6b#`Z z()35GC<^o|vB<3c?i;%6fWL835=zGYx>i?TswuJg1Q{L^>+{lhC$!SaH*`N3kA(3w zkL9qY7-2P5QFh-;m<@b;YEKjQAB-Sc^lM?e(Ujb^;$Nd+Qyg!(=iC#}B5i2gI+3V3 z{jo`^h^N2$Uaj@t3C(vlp#I9PjM$~g98>Wo9pPc?J#b%|(O#0x(F^m#)z1zyw`X4p zN;U&)R=*x|*&aFY9=>-;u#xvASaNEc@}t+ZY&f}C!f+L^-j<4^iB7YfX>-pl>Bs6R zQ4aXUDMuW7!*`UB{?h=Pn&V%fR8s!N@aMg`Kylu1%t3Co=u`GxlW-hH^y2z6)cq=UyuGBJEQUc_Xo`XQ)~VI8f!1zdGLm?PbknY14DW& N6$Q + + + + + My Style + + shape + points.shp + + + + + + diff --git a/tests/visual_tests/list-100-reference.png b/tests/visual_tests/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/visual_tests/list-150-reference.png b/tests/visual_tests/list-150-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..61dc7964dedc354d03f0e101b37204bb63339a53 GIT binary patch literal 2723 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 literal 0 HcmV?d00001 diff --git a/tests/visual_tests/list-250-reference.png b/tests/visual_tests/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/visual_tests/list-400-reference.png b/tests/visual_tests/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/visual_tests/list-600-reference.png b/tests/visual_tests/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/visual_tests/list-800-reference.png b/tests/visual_tests/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/visual_tests/list.xml b/tests/visual_tests/list.xml new file mode 100644 index 000000000..93ef3726d --- /dev/null +++ b/tests/visual_tests/list.xml @@ -0,0 +1,27 @@ + + + + + + My Style + + + shape + points.shp + + + + + + diff --git a/tests/visual_tests/points.dbf b/tests/visual_tests/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/visual_tests/points.shp b/tests/visual_tests/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/visual_tests/simple-100-reference.png b/tests/visual_tests/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/visual_tests/simple-200-reference.png b/tests/visual_tests/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/visual_tests/simple-300-reference.png b/tests/visual_tests/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/visual_tests/simple-600-reference.png b/tests/visual_tests/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/visual_tests/simple-800-reference.png b/tests/visual_tests/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-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/visual_tests/simple.xml b/tests/visual_tests/simple.xml new file mode 100644 index 000000000..cc008f598 --- /dev/null +++ b/tests/visual_tests/simple.xml @@ -0,0 +1,24 @@ + + + + + + My Style + + + shape + points.shp + + + + + + diff --git a/tests/data/placement/test.py b/tests/visual_tests/test.py similarity index 89% rename from tests/data/placement/test.py rename to tests/visual_tests/test.py index 1fabccbce..7fb48166a 100755 --- a/tests/data/placement/test.py +++ b/tests/visual_tests/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) + 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..5f9f9c615 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 \ @@ -92,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 \ @@ -162,7 +160,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 +171,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 +184,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 +205,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 \