mapnik/src/text/placement_finder.cpp

685 lines
21 KiB
C++
Raw Normal View History

/*****************************************************************************
Patch from David Eastcott : 1. Modified Text Symbolizer a) corrected line fragment centering (for 2nd and subsequent lines, when line breaks occur). b) adjusted vertical alignment calculation so that: i) middle -> has the center of the text line(s) at the point origin ii) bottom -> has the text line(s) below the point origin iii) top -> has the text line(s) above the point origin c) added new text_symbolizer attribute: 'wrap_before', value range: true/false, default == false allows line breaks at first wrap_char before wrap_width as an alternative to the original which was to create the line break at the first wrap_char after wrap_width d) added new text_symbolizer attribute: 'horizontal_alignment', value range: left/middle/right, default == middle i) left -> has all text line(s) to left of the point origin ii) middle -> has all text line(s) centered on the the point origin iii) right -> has all text line(s) to the right of the point origin NOTE: dx, dy position adjustments are applied after alignments and before Justify. e) added new text_symbolizer attribute: 'justify_alignment', value range: left/middle/right, default == middle i) left -> after alignments, has all text line(s) are left justified (left to right reading) ii) middle -> after alignments, has all text line(s) center justified iii) right -> after alignments, has all text line(s) right justified (right to left reading) f) added new text_symbolizer attribute: 'opacity', value range: 0.0 thru 1.0; 1.0 == fully opaque g) modified positioning to compensate for both line_spacing and character_spacing, to ensure proper centering of the text envelope. Also ensure that centering occurs correctly even if no wrapping occurs. Line spacing is uniform and consistent and compensates for errors between text_size and the actual size (ci.height is inconsistent, depending on case and character); fixes issue with multi-line text where some lines have a slight gap and others are compressed together. 2. Modified shield_symbolizer a) added the attributes: i) allow_overlap ii) vertical_alignment iii) horizontal_alignment iv) justify_alignment v) wrap_width vi) wrap_character vii) wrap_before viii) text_convert ix) line_spacing x) character_spacing xi) opacity b) added new shield_symbolizer attribute: 'unlock_image', value range: true/false, default == false i) false == image and text placement behaviour same as before ii) true == image placement independant of text, image is always centered at geometry point, text placed per attributes, dx/dy only affect text. Allows user to create point markers with text, but both the text and image rendering collision detection are done as a pair (they come and go together - solves problem if using point_symbolizer and text_symbolizers where one or the other are omitted due to overlaps, but not both) c) extended choices for the attribute 'placement' to include vertex; effect is limited to the shield_symbolizer Allows an attempted placement at every vertex available, gives additional shield placement volume when using line geometry d) ensured that the text placement was not updating the detector unless a shield image was actually placed. e) added new shield_symbolizer attribute: 'no_text', value range: true/false, default = false When set true, the text for the feature is ignored ('space' subsituted) so that pure graphic symbols can be used and no text is rendered over top of them.
2009-10-19 15:52:53 +02:00
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2013 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
2007-10-08 19:42:41 +02:00
//mapnik
2013-01-30 18:32:20 +01:00
#include <mapnik/debug.hpp>
#include <mapnik/label_collision_detector.hpp>
#include <mapnik/ctrans.hpp>
#include <mapnik/expression_evaluator.hpp>
2013-11-08 05:09:22 +01:00
#include <mapnik/text/placement_finder.hpp>
#include <mapnik/text/layout.hpp>
#include <mapnik/text/text_properties.hpp>
#include <mapnik/text/placements_list.hpp>
#include <mapnik/text/vertex_cache.hpp>
// agg
#include "agg_conv_clip_polyline.h"
// stl
2007-10-08 19:42:41 +02:00
#include <vector>
2006-11-28 11:38:56 +01:00
namespace mapnik
{
Patch from David Eastcott : 1. Modified Text Symbolizer a) corrected line fragment centering (for 2nd and subsequent lines, when line breaks occur). b) adjusted vertical alignment calculation so that: i) middle -> has the center of the text line(s) at the point origin ii) bottom -> has the text line(s) below the point origin iii) top -> has the text line(s) above the point origin c) added new text_symbolizer attribute: 'wrap_before', value range: true/false, default == false allows line breaks at first wrap_char before wrap_width as an alternative to the original which was to create the line break at the first wrap_char after wrap_width d) added new text_symbolizer attribute: 'horizontal_alignment', value range: left/middle/right, default == middle i) left -> has all text line(s) to left of the point origin ii) middle -> has all text line(s) centered on the the point origin iii) right -> has all text line(s) to the right of the point origin NOTE: dx, dy position adjustments are applied after alignments and before Justify. e) added new text_symbolizer attribute: 'justify_alignment', value range: left/middle/right, default == middle i) left -> after alignments, has all text line(s) are left justified (left to right reading) ii) middle -> after alignments, has all text line(s) center justified iii) right -> after alignments, has all text line(s) right justified (right to left reading) f) added new text_symbolizer attribute: 'opacity', value range: 0.0 thru 1.0; 1.0 == fully opaque g) modified positioning to compensate for both line_spacing and character_spacing, to ensure proper centering of the text envelope. Also ensure that centering occurs correctly even if no wrapping occurs. Line spacing is uniform and consistent and compensates for errors between text_size and the actual size (ci.height is inconsistent, depending on case and character); fixes issue with multi-line text where some lines have a slight gap and others are compressed together. 2. Modified shield_symbolizer a) added the attributes: i) allow_overlap ii) vertical_alignment iii) horizontal_alignment iv) justify_alignment v) wrap_width vi) wrap_character vii) wrap_before viii) text_convert ix) line_spacing x) character_spacing xi) opacity b) added new shield_symbolizer attribute: 'unlock_image', value range: true/false, default == false i) false == image and text placement behaviour same as before ii) true == image placement independant of text, image is always centered at geometry point, text placed per attributes, dx/dy only affect text. Allows user to create point markers with text, but both the text and image rendering collision detection are done as a pair (they come and go together - solves problem if using point_symbolizer and text_symbolizers where one or the other are omitted due to overlaps, but not both) c) extended choices for the attribute 'placement' to include vertex; effect is limited to the shield_symbolizer Allows an attempted placement at every vertex available, gives additional shield placement volume when using line geometry d) ensured that the text placement was not updating the detector unless a shield image was actually placed. e) added new shield_symbolizer attribute: 'no_text', value range: true/false, default = false When set true, the text for the feature is ignored ('space' subsituted) so that pure graphic symbols can be used and no text is rendered over top of them.
2009-10-19 15:52:53 +02:00
class tolerance_iterator
{
public:
tolerance_iterator(double label_position_tolerance, double spacing)
: tolerance_(label_position_tolerance > 0 ?
label_position_tolerance : spacing/2.0),
tolerance_delta_(std::max(1.0, tolerance_/100.0)),
value_(0),
initialized_(false),
values_tried_(0)
{
}
~tolerance_iterator()
{
//std::cout << "values tried:" << values_tried_ << "\n";
}
double get() const
{
return -value_;
}
bool next()
{
++values_tried_;
if (values_tried_ > 255)
{
/* This point should not be reached during normal operation. But I can think of
* cases where very bad spacing and or tolerance values are choosen and the
* placement finder tries an excessive number of placements.
* 255 is an arbitrarily chosen limit.
*/
MAPNIK_LOG_WARN(placement_finder) << "Tried a huge number of placements. Please check "
"'label-position-tolerance' and 'spacing' parameters "
"of your TextSymbolizers.\n";
return false;
}
if (!initialized_)
2010-06-02 13:03:30 +02:00
{
initialized_ = true;
return true; //Always return value 0 as the first value.
2010-06-02 13:03:30 +02:00
}
if (value_ == 0)
2010-06-02 13:03:30 +02:00
{
value_ = tolerance_delta_;
return true;
2010-06-02 13:03:30 +02:00
}
value_ = -value_;
if (value_ > 0)
{
value_ += tolerance_delta_;
}
if (value_ > tolerance_)
{
return false;
}
return true;
}
private:
double tolerance_;
double tolerance_delta_;
double value_;
bool initialized_;
unsigned values_tried_;
};
// Output is centered around (0,0)
static void rotated_box2d(box2d<double> & box, rotation const& rot, double width, double height)
{
double new_width = width * rot.cos + height * rot.sin;
double new_height = width * rot.sin + height * rot.cos;
box.init(-new_width/2., -new_height/2., new_width/2., new_height/2.);
2012-01-22 18:41:04 +01:00
}
pixel_position pixel_position::rotate(rotation const& rot) const
{
return pixel_position(x * rot.cos - y * rot.sin, x * rot.sin + y * rot.cos);
}
2012-01-22 18:41:04 +01:00
placement_finder::placement_finder(feature_impl const& feature,
DetectorType &detector,
box2d<double> const& extent,
text_placement_info_ptr placement_info,
face_manager_freetype & font_manager,
double scale_factor)
: feature_(feature),
detector_(detector),
extent_(extent),
layout_(font_manager, scale_factor),
info_(placement_info),
valid_(true),
scale_factor_(scale_factor),
placements_(),
has_marker_(false),
marker_(),
marker_box_()
{
}
bool placement_finder::next_position()
2012-01-22 18:41:04 +01:00
{
if (!valid_)
{
MAPNIK_LOG_WARN(placement_finder) << "next_position() called while last call already returned false!\n";
return false;
}
if (!info_->next())
{
valid_ = false;
return false;
}
Patch from David Eastcott : 1. Modified Text Symbolizer a) corrected line fragment centering (for 2nd and subsequent lines, when line breaks occur). b) adjusted vertical alignment calculation so that: i) middle -> has the center of the text line(s) at the point origin ii) bottom -> has the text line(s) below the point origin iii) top -> has the text line(s) above the point origin c) added new text_symbolizer attribute: 'wrap_before', value range: true/false, default == false allows line breaks at first wrap_char before wrap_width as an alternative to the original which was to create the line break at the first wrap_char after wrap_width d) added new text_symbolizer attribute: 'horizontal_alignment', value range: left/middle/right, default == middle i) left -> has all text line(s) to left of the point origin ii) middle -> has all text line(s) centered on the the point origin iii) right -> has all text line(s) to the right of the point origin NOTE: dx, dy position adjustments are applied after alignments and before Justify. e) added new text_symbolizer attribute: 'justify_alignment', value range: left/middle/right, default == middle i) left -> after alignments, has all text line(s) are left justified (left to right reading) ii) middle -> after alignments, has all text line(s) center justified iii) right -> after alignments, has all text line(s) right justified (right to left reading) f) added new text_symbolizer attribute: 'opacity', value range: 0.0 thru 1.0; 1.0 == fully opaque g) modified positioning to compensate for both line_spacing and character_spacing, to ensure proper centering of the text envelope. Also ensure that centering occurs correctly even if no wrapping occurs. Line spacing is uniform and consistent and compensates for errors between text_size and the actual size (ci.height is inconsistent, depending on case and character); fixes issue with multi-line text where some lines have a slight gap and others are compressed together. 2. Modified shield_symbolizer a) added the attributes: i) allow_overlap ii) vertical_alignment iii) horizontal_alignment iv) justify_alignment v) wrap_width vi) wrap_character vii) wrap_before viii) text_convert ix) line_spacing x) character_spacing xi) opacity b) added new shield_symbolizer attribute: 'unlock_image', value range: true/false, default == false i) false == image and text placement behaviour same as before ii) true == image placement independant of text, image is always centered at geometry point, text placed per attributes, dx/dy only affect text. Allows user to create point markers with text, but both the text and image rendering collision detection are done as a pair (they come and go together - solves problem if using point_symbolizer and text_symbolizers where one or the other are omitted due to overlaps, but not both) c) extended choices for the attribute 'placement' to include vertex; effect is limited to the shield_symbolizer Allows an attempted placement at every vertex available, gives additional shield placement volume when using line geometry d) ensured that the text placement was not updating the detector unless a shield image was actually placed. e) added new shield_symbolizer attribute: 'no_text', value range: true/false, default = false When set true, the text for the feature is ignored ('space' subsituted) so that pure graphic symbols can be used and no text is rendered over top of them.
2009-10-19 15:52:53 +02:00
info_->properties.process(layout_, feature_);
layout_.layout(info_->properties.layout_defaults->wrap_width * scale_factor_, info_->properties.layout_defaults->text_ratio, info_->properties.layout_defaults->wrap_before);
Patch from David Eastcott : 1. Modified Text Symbolizer a) corrected line fragment centering (for 2nd and subsequent lines, when line breaks occur). b) adjusted vertical alignment calculation so that: i) middle -> has the center of the text line(s) at the point origin ii) bottom -> has the text line(s) below the point origin iii) top -> has the text line(s) above the point origin c) added new text_symbolizer attribute: 'wrap_before', value range: true/false, default == false allows line breaks at first wrap_char before wrap_width as an alternative to the original which was to create the line break at the first wrap_char after wrap_width d) added new text_symbolizer attribute: 'horizontal_alignment', value range: left/middle/right, default == middle i) left -> has all text line(s) to left of the point origin ii) middle -> has all text line(s) centered on the the point origin iii) right -> has all text line(s) to the right of the point origin NOTE: dx, dy position adjustments are applied after alignments and before Justify. e) added new text_symbolizer attribute: 'justify_alignment', value range: left/middle/right, default == middle i) left -> after alignments, has all text line(s) are left justified (left to right reading) ii) middle -> after alignments, has all text line(s) center justified iii) right -> after alignments, has all text line(s) right justified (right to left reading) f) added new text_symbolizer attribute: 'opacity', value range: 0.0 thru 1.0; 1.0 == fully opaque g) modified positioning to compensate for both line_spacing and character_spacing, to ensure proper centering of the text envelope. Also ensure that centering occurs correctly even if no wrapping occurs. Line spacing is uniform and consistent and compensates for errors between text_size and the actual size (ci.height is inconsistent, depending on case and character); fixes issue with multi-line text where some lines have a slight gap and others are compressed together. 2. Modified shield_symbolizer a) added the attributes: i) allow_overlap ii) vertical_alignment iii) horizontal_alignment iv) justify_alignment v) wrap_width vi) wrap_character vii) wrap_before viii) text_convert ix) line_spacing x) character_spacing xi) opacity b) added new shield_symbolizer attribute: 'unlock_image', value range: true/false, default == false i) false == image and text placement behaviour same as before ii) true == image placement independant of text, image is always centered at geometry point, text placed per attributes, dx/dy only affect text. Allows user to create point markers with text, but both the text and image rendering collision detection are done as a pair (they come and go together - solves problem if using point_symbolizer and text_symbolizers where one or the other are omitted due to overlaps, but not both) c) extended choices for the attribute 'placement' to include vertex; effect is limited to the shield_symbolizer Allows an attempted placement at every vertex available, gives additional shield placement volume when using line geometry d) ensured that the text placement was not updating the detector unless a shield image was actually placed. e) added new shield_symbolizer attribute: 'no_text', value range: true/false, default = false When set true, the text for the feature is ignored ('space' subsituted) so that pure graphic symbols can be used and no text is rendered over top of them.
2009-10-19 15:52:53 +02:00
if (info_->properties.layout_defaults->orientation)
{
// https://github.com/mapnik/mapnik/issues/1352
mapnik::evaluate<feature_impl, value_type> evaluator(feature_);
orientation_.init(
boost::apply_visitor(
evaluator,
*(info_->properties.layout_defaults->orientation)).to_double() * M_PI / 180.0);
}
else
{
orientation_.reset();
}
init_alignment();
return true;
}
void placement_finder::init_alignment()
2012-01-22 18:41:04 +01:00
{
text_layout_properties const& p = *(info_->properties.layout_defaults);
2012-01-22 18:41:04 +01:00
valign_ = p.valign;
if (valign_ == V_AUTO)
{
if (p.displacement.y > 0.0)
{
2012-01-22 18:41:04 +01:00
valign_ = V_BOTTOM;
}
else if (p.displacement.y < 0.0)
{
2012-01-22 18:41:04 +01:00
valign_ = V_TOP;
}
else
{
2012-01-22 18:41:04 +01:00
valign_ = V_MIDDLE;
}
}
halign_point_ = p.halign;
halign_line_ = p.halign;
if (halign_point_ == H_AUTO)
{
if (p.displacement.x > 0.0)
{
halign_point_ = H_RIGHT;
halign_line_ = H_LEFT;
}
else if (p.displacement.x < 0.0)
{
halign_point_ = H_LEFT;
halign_line_= H_RIGHT;
}
else
{
halign_point_ = H_MIDDLE;
halign_line_ = H_MIDDLE;
}
}
jalign_ = p.jalign;
if (jalign_ == J_AUTO)
{
if (p.displacement.x > 0.0)
{
jalign_ = J_LEFT;
}
else if (p.displacement.x < 0.0)
{
jalign_ = J_RIGHT;
}
else
{
jalign_ = J_MIDDLE;
}
}
2012-01-22 18:41:04 +01:00
}
pixel_position placement_finder::alignment_offset() const //TODO
2012-01-22 18:41:04 +01:00
{
pixel_position result(0,0);
2012-01-22 18:41:04 +01:00
// if needed, adjust for desired vertical alignment
if (valign_ == V_TOP)
{
result.y = -0.5 * layout_.height(); // move center up by 1/2 the total height
}
else if (valign_ == V_BOTTOM)
{
result.y = 0.5 * layout_.height(); // move center down by the 1/2 the total height
2012-01-22 18:41:04 +01:00
}
// set horizontal position to middle of text
if (halign_point_ == H_LEFT)
{
result.x = -0.5 * layout_.width(); // move center left by 1/2 the string width
}
else if (halign_point_ == H_RIGHT)
{
result.x = 0.5 * layout_.width(); // move center right by 1/2 the string width
}
return result;
2012-01-22 18:41:04 +01:00
}
double placement_finder::jalign_offset(double line_width) const //TODO
2012-01-22 18:41:04 +01:00
{
if (jalign_ == J_MIDDLE) return -(line_width / 2.0);
if (jalign_ == J_LEFT) return -(layout_.width() / 2.0);
if (jalign_ == J_RIGHT) return (layout_.width() / 2.0) - line_width;
return 0;
}
bool placement_finder::find_point_placement(pixel_position const& pos)
{
glyph_positions_ptr glyphs = std::make_shared<glyph_positions>();
/* Find text origin. */
pixel_position displacement = scale_factor_ * info_->properties.layout_defaults->displacement + alignment_offset();
if (info_->properties.layout_defaults->rotate_displacement) displacement = displacement.rotate(!orientation_);
glyphs->set_base_point(pos + displacement);
box2d<double> bbox;
rotated_box2d(bbox, orientation_, layout_.width(), layout_.height());
bbox.re_center(glyphs->get_base_point().x, glyphs->get_base_point().y);
/* For point placements it is faster to just check the bounding box. */
if (collision(bbox)) return false;
/* add_marker first checks for collision and then updates the detector.*/
if (has_marker_ && !add_marker(glyphs, pos)) return false;
if (layout_.num_lines()) detector_.insert(bbox, layout_.text());
/* IMPORTANT NOTE:
x and y are relative to the center of the text
coordinate system:
x: grows from left to right
y: grows from bottom to top (opposite of normal computer graphics)
*/
double x, y;
// set for upper left corner of text envelope for the first line, top left of first character
y = layout_.height() / 2.0;
glyphs->reserve(layout_.glyphs_count());
for ( auto const& line : layout_)
{
y -= line.height(); //Automatically handles first line differently
x = jalign_offset(line.width());
for (auto const& glyph : line)
2010-06-02 13:03:30 +02:00
{
Patch from David Eastcott : 1. Modified Text Symbolizer a) corrected line fragment centering (for 2nd and subsequent lines, when line breaks occur). b) adjusted vertical alignment calculation so that: i) middle -> has the center of the text line(s) at the point origin ii) bottom -> has the text line(s) below the point origin iii) top -> has the text line(s) above the point origin c) added new text_symbolizer attribute: 'wrap_before', value range: true/false, default == false allows line breaks at first wrap_char before wrap_width as an alternative to the original which was to create the line break at the first wrap_char after wrap_width d) added new text_symbolizer attribute: 'horizontal_alignment', value range: left/middle/right, default == middle i) left -> has all text line(s) to left of the point origin ii) middle -> has all text line(s) centered on the the point origin iii) right -> has all text line(s) to the right of the point origin NOTE: dx, dy position adjustments are applied after alignments and before Justify. e) added new text_symbolizer attribute: 'justify_alignment', value range: left/middle/right, default == middle i) left -> after alignments, has all text line(s) are left justified (left to right reading) ii) middle -> after alignments, has all text line(s) center justified iii) right -> after alignments, has all text line(s) right justified (right to left reading) f) added new text_symbolizer attribute: 'opacity', value range: 0.0 thru 1.0; 1.0 == fully opaque g) modified positioning to compensate for both line_spacing and character_spacing, to ensure proper centering of the text envelope. Also ensure that centering occurs correctly even if no wrapping occurs. Line spacing is uniform and consistent and compensates for errors between text_size and the actual size (ci.height is inconsistent, depending on case and character); fixes issue with multi-line text where some lines have a slight gap and others are compressed together. 2. Modified shield_symbolizer a) added the attributes: i) allow_overlap ii) vertical_alignment iii) horizontal_alignment iv) justify_alignment v) wrap_width vi) wrap_character vii) wrap_before viii) text_convert ix) line_spacing x) character_spacing xi) opacity b) added new shield_symbolizer attribute: 'unlock_image', value range: true/false, default == false i) false == image and text placement behaviour same as before ii) true == image placement independant of text, image is always centered at geometry point, text placed per attributes, dx/dy only affect text. Allows user to create point markers with text, but both the text and image rendering collision detection are done as a pair (they come and go together - solves problem if using point_symbolizer and text_symbolizers where one or the other are omitted due to overlaps, but not both) c) extended choices for the attribute 'placement' to include vertex; effect is limited to the shield_symbolizer Allows an attempted placement at every vertex available, gives additional shield placement volume when using line geometry d) ensured that the text placement was not updating the detector unless a shield image was actually placed. e) added new shield_symbolizer attribute: 'no_text', value range: true/false, default = false When set true, the text for the feature is ignored ('space' subsituted) so that pure graphic symbols can be used and no text is rendered over top of them.
2009-10-19 15:52:53 +02:00
// place the character relative to the center of the string envelope
glyphs->push_back(glyph, pixel_position(x, y).rotate(orientation_), orientation_);
if (glyph.width)
2012-03-13 15:56:11 +01:00
{
//Only advance if glyph is not part of a multiple glyph sequence
x += glyph.width + glyph.format->character_spacing * scale_factor_;
2012-01-22 18:41:04 +01:00
}
2011-12-15 15:57:57 +01:00
}
}
placements_.push_back(glyphs);
return true;
}
template <typename T>
bool placement_finder::find_line_placements(T & path, bool points)
{
if (!layout_.num_lines()) return true; //TODO
vertex_cache pp(path);
bool success = false;
while (pp.next_subpath())
{
if (points)
2010-06-02 13:03:30 +02:00
{
if (pp.length() <= 0.001)
{
success = find_point_placement(pp.current_position()) || success;
continue;
}
2010-06-02 13:03:30 +02:00
}
else
{
if ((pp.length() < info_->properties.minimum_path_length * scale_factor_)
||
(pp.length() <= 0.001) /* Clipping removed whole geometry */
||
(pp.length() < layout_.width()))
{
continue;
}
2010-06-02 13:03:30 +02:00
}
double spacing = get_spacing(pp.length(), points ? 0. : layout_.width());
horizontal_alignment_e halign = info_->properties.layout_defaults->halign;
if (halign == H_LEFT)
2010-06-02 13:03:30 +02:00
{
// Don't move
2010-06-02 13:03:30 +02:00
}
else if (halign == H_MIDDLE || halign == H_AUTO)
2010-06-02 13:03:30 +02:00
{
pp.forward(spacing/2.0);
}
else if (halign == H_RIGHT)
{
pp.forward(pp.length());
}
path_move_dx(pp);
do
{
tolerance_iterator tolerance_offset(info_->properties.label_position_tolerance * scale_factor_, spacing); //TODO: Handle halign
while (tolerance_offset.next())
{
vertex_cache::scoped_state state(pp);
if (pp.move(tolerance_offset.get())
&& (
(points && find_point_placement(pp.current_position()))
|| (!points && single_line_placement(pp, info_->properties.upright))))
2010-06-02 13:03:30 +02:00
{
success = true;
break;
2010-06-02 13:03:30 +02:00
}
}
} while (pp.forward(spacing));
}
return success;
}
text_upright_e placement_finder::simplify_upright(text_upright_e upright, double angle) const
{
if (upright == UPRIGHT_AUTO)
{
return (std::fabs(normalize_angle(angle)) > 0.5*M_PI) ? UPRIGHT_LEFT : UPRIGHT_RIGHT;
}
if (upright == UPRIGHT_LEFT_ONLY)
{
return UPRIGHT_LEFT;
}
if (upright == UPRIGHT_RIGHT_ONLY)
{
return UPRIGHT_RIGHT;
}
return upright;
}
bool placement_finder::single_line_placement(vertex_cache &pp, text_upright_e orientation)
{
/********************************************************************************
* IMPORTANT NOTE: See note about coordinate systems in find_point_placement()! *
********************************************************************************/
vertex_cache::scoped_state s(pp);
glyph_positions_ptr glyphs = std::make_shared<glyph_positions>();
std::vector<box2d<double> > bboxes;
bboxes.reserve(layout_.text().length());
int upside_down_glyph_count = 0;
text_upright_e real_orientation = simplify_upright(orientation, pp.angle());
double sign = (real_orientation == UPRIGHT_LEFT) ? -1 : 1;
double offset = alignment_offset().y + info_->properties.layout_defaults->displacement.y * scale_factor_ + sign * layout_.height()/2.;
glyphs->reserve(layout_.glyphs_count());
for (auto const& line : layout_)
{
//Only subtract half the line height here and half at the end because text is automatically
//centered on the line
offset -= sign * line.height()/2;
vertex_cache & off_pp = pp.get_offseted(offset, sign*layout_.width());
vertex_cache::scoped_state off_state(off_pp); //TODO: Remove this when a clean implementation in vertex_cache::get_offseted was done
if (!off_pp.move(sign * jalign_offset(line.width()) - alignment_offset().x)) return false;
double last_cluster_angle = 999;
int current_cluster = -1;
pixel_position cluster_offset;
double angle;
rotation rot;
double last_glyph_spacing = 0.;
2010-06-02 13:03:30 +02:00
for (auto const& glyph : line)
2010-06-02 13:03:30 +02:00
{
if (current_cluster != static_cast<int>(glyph.char_index))
{
if (!off_pp.move(sign * (layout_.cluster_width(current_cluster) + last_glyph_spacing)))
2010-06-02 13:03:30 +02:00
{
return false;
2010-06-02 13:03:30 +02:00
}
current_cluster = glyph.char_index;
last_glyph_spacing = glyph.format->character_spacing * scale_factor_;
//Only calculate new angle at the start of each cluster!
angle = normalize_angle(off_pp.angle(sign * layout_.cluster_width(current_cluster)));
rot.init(angle);
if ((info_->properties.max_char_angle_delta > 0) && (last_cluster_angle != 999) &&
std::fabs(normalize_angle(angle-last_cluster_angle)) > info_->properties.max_char_angle_delta)
{
return false;
}
cluster_offset.clear();
last_cluster_angle = angle;
}
if (std::abs(angle) > M_PI/2) ++upside_down_glyph_count;
pixel_position pos = off_pp.current_position() + cluster_offset;
//Center the text on the line
double char_height = line.max_char_height();
pos.y = -pos.y - char_height/2.0*rot.cos;
pos.x = pos.x + char_height/2.0*rot.sin;
Patch from David Eastcott : 1. Modified Text Symbolizer a) corrected line fragment centering (for 2nd and subsequent lines, when line breaks occur). b) adjusted vertical alignment calculation so that: i) middle -> has the center of the text line(s) at the point origin ii) bottom -> has the text line(s) below the point origin iii) top -> has the text line(s) above the point origin c) added new text_symbolizer attribute: 'wrap_before', value range: true/false, default == false allows line breaks at first wrap_char before wrap_width as an alternative to the original which was to create the line break at the first wrap_char after wrap_width d) added new text_symbolizer attribute: 'horizontal_alignment', value range: left/middle/right, default == middle i) left -> has all text line(s) to left of the point origin ii) middle -> has all text line(s) centered on the the point origin iii) right -> has all text line(s) to the right of the point origin NOTE: dx, dy position adjustments are applied after alignments and before Justify. e) added new text_symbolizer attribute: 'justify_alignment', value range: left/middle/right, default == middle i) left -> after alignments, has all text line(s) are left justified (left to right reading) ii) middle -> after alignments, has all text line(s) center justified iii) right -> after alignments, has all text line(s) right justified (right to left reading) f) added new text_symbolizer attribute: 'opacity', value range: 0.0 thru 1.0; 1.0 == fully opaque g) modified positioning to compensate for both line_spacing and character_spacing, to ensure proper centering of the text envelope. Also ensure that centering occurs correctly even if no wrapping occurs. Line spacing is uniform and consistent and compensates for errors between text_size and the actual size (ci.height is inconsistent, depending on case and character); fixes issue with multi-line text where some lines have a slight gap and others are compressed together. 2. Modified shield_symbolizer a) added the attributes: i) allow_overlap ii) vertical_alignment iii) horizontal_alignment iv) justify_alignment v) wrap_width vi) wrap_character vii) wrap_before viii) text_convert ix) line_spacing x) character_spacing xi) opacity b) added new shield_symbolizer attribute: 'unlock_image', value range: true/false, default == false i) false == image and text placement behaviour same as before ii) true == image placement independant of text, image is always centered at geometry point, text placed per attributes, dx/dy only affect text. Allows user to create point markers with text, but both the text and image rendering collision detection are done as a pair (they come and go together - solves problem if using point_symbolizer and text_symbolizers where one or the other are omitted due to overlaps, but not both) c) extended choices for the attribute 'placement' to include vertex; effect is limited to the shield_symbolizer Allows an attempted placement at every vertex available, gives additional shield placement volume when using line geometry d) ensured that the text placement was not updating the detector unless a shield image was actually placed. e) added new shield_symbolizer attribute: 'no_text', value range: true/false, default = false When set true, the text for the feature is ignored ('space' subsituted) so that pure graphic symbols can be used and no text is rendered over top of them.
2009-10-19 15:52:53 +02:00
cluster_offset.x += rot.cos * glyph.width;
cluster_offset.y -= rot.sin * glyph.width;
Patch from David Eastcott : 1. Modified Text Symbolizer a) corrected line fragment centering (for 2nd and subsequent lines, when line breaks occur). b) adjusted vertical alignment calculation so that: i) middle -> has the center of the text line(s) at the point origin ii) bottom -> has the text line(s) below the point origin iii) top -> has the text line(s) above the point origin c) added new text_symbolizer attribute: 'wrap_before', value range: true/false, default == false allows line breaks at first wrap_char before wrap_width as an alternative to the original which was to create the line break at the first wrap_char after wrap_width d) added new text_symbolizer attribute: 'horizontal_alignment', value range: left/middle/right, default == middle i) left -> has all text line(s) to left of the point origin ii) middle -> has all text line(s) centered on the the point origin iii) right -> has all text line(s) to the right of the point origin NOTE: dx, dy position adjustments are applied after alignments and before Justify. e) added new text_symbolizer attribute: 'justify_alignment', value range: left/middle/right, default == middle i) left -> after alignments, has all text line(s) are left justified (left to right reading) ii) middle -> after alignments, has all text line(s) center justified iii) right -> after alignments, has all text line(s) right justified (right to left reading) f) added new text_symbolizer attribute: 'opacity', value range: 0.0 thru 1.0; 1.0 == fully opaque g) modified positioning to compensate for both line_spacing and character_spacing, to ensure proper centering of the text envelope. Also ensure that centering occurs correctly even if no wrapping occurs. Line spacing is uniform and consistent and compensates for errors between text_size and the actual size (ci.height is inconsistent, depending on case and character); fixes issue with multi-line text where some lines have a slight gap and others are compressed together. 2. Modified shield_symbolizer a) added the attributes: i) allow_overlap ii) vertical_alignment iii) horizontal_alignment iv) justify_alignment v) wrap_width vi) wrap_character vii) wrap_before viii) text_convert ix) line_spacing x) character_spacing xi) opacity b) added new shield_symbolizer attribute: 'unlock_image', value range: true/false, default == false i) false == image and text placement behaviour same as before ii) true == image placement independant of text, image is always centered at geometry point, text placed per attributes, dx/dy only affect text. Allows user to create point markers with text, but both the text and image rendering collision detection are done as a pair (they come and go together - solves problem if using point_symbolizer and text_symbolizers where one or the other are omitted due to overlaps, but not both) c) extended choices for the attribute 'placement' to include vertex; effect is limited to the shield_symbolizer Allows an attempted placement at every vertex available, gives additional shield placement volume when using line geometry d) ensured that the text placement was not updating the detector unless a shield image was actually placed. e) added new shield_symbolizer attribute: 'no_text', value range: true/false, default = false When set true, the text for the feature is ignored ('space' subsituted) so that pure graphic symbols can be used and no text is rendered over top of them.
2009-10-19 15:52:53 +02:00
box2d<double> bbox = get_bbox(glyph, pos, rot);
if (collision(bbox)) return false;
bboxes.push_back(bbox);
glyphs->push_back(glyph, pos, rot);
2010-06-02 13:03:30 +02:00
}
//See comment above
offset -= sign * line.height()/2;
}
if (upside_down_glyph_count > (layout_.text().length()/2))
{
if (orientation == UPRIGHT_AUTO)
2010-06-02 13:03:30 +02:00
{
//Try again with oposite orientation
s.restore();
return single_line_placement(pp, real_orientation == UPRIGHT_RIGHT ? UPRIGHT_LEFT : UPRIGHT_RIGHT);
2010-06-02 13:03:30 +02:00
}
//upright==left_only or right_only and more than 50% of characters upside down => no placement
if (orientation == UPRIGHT_LEFT_ONLY || orientation == UPRIGHT_RIGHT_ONLY)
2010-06-02 13:03:30 +02:00
{
return false;
2010-06-02 13:03:30 +02:00
}
}
for (box2d<double> const& bbox : bboxes)
{
detector_.insert(bbox, layout_.text());
}
placements_.push_back(glyphs);
return true;
}
void placement_finder::path_move_dx(vertex_cache &pp)
{
double dx = info_->properties.layout_defaults->displacement.x * scale_factor_;
if (dx != 0.0)
{
vertex_cache::state state = pp.save_state();
if (!pp.move(dx)) pp.restore_state(state);
}
}
double placement_finder::normalize_angle(double angle)
{
while (angle >= M_PI)
{
angle -= 2.0 * M_PI;
}
while (angle < -M_PI)
{
angle += 2.0 * M_PI;
}
return angle;
}
double placement_finder::get_spacing(double path_length, double layout_width) const
{
int num_labels = 1;
if (info_->properties.label_spacing > 0)
{
num_labels = static_cast<int>(floor(
path_length / (info_->properties.label_spacing * scale_factor_ + layout_width)));
}
Patch from David Eastcott : 1. Modified Text Symbolizer a) corrected line fragment centering (for 2nd and subsequent lines, when line breaks occur). b) adjusted vertical alignment calculation so that: i) middle -> has the center of the text line(s) at the point origin ii) bottom -> has the text line(s) below the point origin iii) top -> has the text line(s) above the point origin c) added new text_symbolizer attribute: 'wrap_before', value range: true/false, default == false allows line breaks at first wrap_char before wrap_width as an alternative to the original which was to create the line break at the first wrap_char after wrap_width d) added new text_symbolizer attribute: 'horizontal_alignment', value range: left/middle/right, default == middle i) left -> has all text line(s) to left of the point origin ii) middle -> has all text line(s) centered on the the point origin iii) right -> has all text line(s) to the right of the point origin NOTE: dx, dy position adjustments are applied after alignments and before Justify. e) added new text_symbolizer attribute: 'justify_alignment', value range: left/middle/right, default == middle i) left -> after alignments, has all text line(s) are left justified (left to right reading) ii) middle -> after alignments, has all text line(s) center justified iii) right -> after alignments, has all text line(s) right justified (right to left reading) f) added new text_symbolizer attribute: 'opacity', value range: 0.0 thru 1.0; 1.0 == fully opaque g) modified positioning to compensate for both line_spacing and character_spacing, to ensure proper centering of the text envelope. Also ensure that centering occurs correctly even if no wrapping occurs. Line spacing is uniform and consistent and compensates for errors between text_size and the actual size (ci.height is inconsistent, depending on case and character); fixes issue with multi-line text where some lines have a slight gap and others are compressed together. 2. Modified shield_symbolizer a) added the attributes: i) allow_overlap ii) vertical_alignment iii) horizontal_alignment iv) justify_alignment v) wrap_width vi) wrap_character vii) wrap_before viii) text_convert ix) line_spacing x) character_spacing xi) opacity b) added new shield_symbolizer attribute: 'unlock_image', value range: true/false, default == false i) false == image and text placement behaviour same as before ii) true == image placement independant of text, image is always centered at geometry point, text placed per attributes, dx/dy only affect text. Allows user to create point markers with text, but both the text and image rendering collision detection are done as a pair (they come and go together - solves problem if using point_symbolizer and text_symbolizers where one or the other are omitted due to overlaps, but not both) c) extended choices for the attribute 'placement' to include vertex; effect is limited to the shield_symbolizer Allows an attempted placement at every vertex available, gives additional shield placement volume when using line geometry d) ensured that the text placement was not updating the detector unless a shield image was actually placed. e) added new shield_symbolizer attribute: 'no_text', value range: true/false, default = false When set true, the text for the feature is ignored ('space' subsituted) so that pure graphic symbols can be used and no text is rendered over top of them.
2009-10-19 15:52:53 +02:00
if (info_->properties.force_odd_labels && num_labels % 2 == 0)
{
--num_labels;
}
if (num_labels <= 0)
{
num_labels = 1;
}
return path_length / num_labels;
}
bool placement_finder::collision(const box2d<double> &box) const
{
if (!detector_.extent().intersects(box)
||
(info_->properties.avoid_edges && !extent_.contains(box))
||
(info_->properties.minimum_padding > 0 &&
!extent_.contains(box + (scale_factor_ * info_->properties.minimum_padding)))
||
(!info_->properties.allow_overlap &&
!detector_.has_point_placement(box, info_->properties.minimum_distance * scale_factor_))
)
{
return true;
}
return false;
}
2012-02-02 02:53:35 +01:00
void placement_finder::set_marker(marker_info_ptr m, box2d<double> box, bool marker_unlocked, pixel_position const& marker_displacement)
{
marker_ = m;
marker_box_ = box * scale_factor_;
marker_displacement_ = marker_displacement * scale_factor_;
marker_unlocked_ = marker_unlocked;
has_marker_ = true;
}
bool placement_finder::add_marker(glyph_positions_ptr glyphs, pixel_position const& pos) const
{
pixel_position real_pos = (marker_unlocked_ ? pos : glyphs->get_base_point()) + marker_displacement_;
box2d<double> bbox = marker_box_;
bbox.move(real_pos.x, real_pos.y);
glyphs->set_marker(marker_, real_pos);
if (collision(bbox)) return false;
detector_.insert(bbox);
return true;
}
box2d<double> placement_finder::get_bbox(glyph_info const& glyph, pixel_position const& pos, rotation const& rot)
{
/*
(0/ymax) (width/ymax)
***************
* *
(0/0)* *
* *
***************
(0/ymin) (width/ymin)
Add glyph offset in y direction, but not in x direction (as we use the full cluster width anyways)!
*/
double width = layout_.cluster_width(glyph.char_index);
if (glyph.width <= 0) width = -width;
pixel_position tmp, tmp2;
tmp.set(0, glyph.ymax);
tmp = tmp.rotate(rot);
tmp2.set(width, glyph.ymax);
tmp2 = tmp2.rotate(rot);
box2d<double> bbox(tmp.x, -tmp.y,
tmp2.x, -tmp2.y);
tmp.set(width, glyph.ymin);
tmp = tmp.rotate(rot);
bbox.expand_to_include(tmp.x, -tmp.y);
tmp.set(0, glyph.ymin);
tmp = tmp.rotate(rot);
bbox.expand_to_include(tmp.x, -tmp.y);
pixel_position pos2 = pos + pixel_position(0, glyph.offset.y).rotate(rot);
bbox.move(pos2.x , -pos2.y);
return bbox;
}
/*********************************************************************************************/
glyph_positions::glyph_positions()
: data_(),
base_point_(),
marker_(),
marker_pos_(),
bbox_()
{
}
glyph_positions::const_iterator glyph_positions::begin() const
{
return data_.begin();
}
glyph_positions::const_iterator glyph_positions::end() const
{
return data_.end();
}
void glyph_positions::push_back(glyph_info const& glyph, pixel_position const offset, rotation const& rot)
{
data_.push_back(glyph_position(glyph, offset, rot));
}
void glyph_positions::reserve(unsigned count)
{
data_.reserve(count);
}
pixel_position const& glyph_positions::get_base_point() const
{
return base_point_;
}
void glyph_positions::set_base_point(pixel_position const base_point)
{
base_point_ = base_point;
}
void glyph_positions::set_marker(marker_info_ptr marker, pixel_position const& marker_pos)
{
marker_ = marker;
marker_pos_ = marker_pos;
}
marker_info_ptr glyph_positions::marker() const
{
return marker_;
}
pixel_position const& glyph_positions::marker_pos() const
{
return marker_pos_;
}
/*************************************************************************************/
typedef agg::conv_clip_polyline<geometry_type> clipped_geometry_type;
typedef coord_transform<CoordTransform,clipped_geometry_type> ClippedPathType;
typedef coord_transform<CoordTransform,geometry_type> PathType;
template bool placement_finder::find_line_placements<ClippedPathType>(ClippedPathType &, bool);
template bool placement_finder::find_line_placements<PathType>(PathType &, bool);
}// ns mapnik