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.
This commit is contained in:
parent
df7bad25f9
commit
a3a5859466
11 changed files with 1106 additions and 726 deletions
|
@ -397,7 +397,8 @@ namespace mapnik
|
|||
faces_(faces),
|
||||
fill_(0,0,0),
|
||||
halo_fill_(255,255,255),
|
||||
halo_radius_(0) {}
|
||||
halo_radius_(0),
|
||||
opacity_(1.0) {}
|
||||
|
||||
void set_pixel_size(unsigned size)
|
||||
{
|
||||
|
@ -419,6 +420,11 @@ namespace mapnik
|
|||
halo_radius_=radius;
|
||||
}
|
||||
|
||||
void set_opacity( double opacity=1.0)
|
||||
{
|
||||
opacity_=opacity;
|
||||
}
|
||||
|
||||
Envelope<double> prepare_glyphs(text_path *path)
|
||||
{
|
||||
//clear glyphs
|
||||
|
@ -561,7 +567,7 @@ namespace mapnik
|
|||
{
|
||||
for (int n=-halo_radius_; n <=halo_radius_; ++n)
|
||||
for (int m=-halo_radius_;m <= halo_radius_; ++m)
|
||||
pixmap_.blendPixel(i+m,j+n,rgba,gray);
|
||||
pixmap_.blendPixel2(i+m,j+n,rgba,gray,opacity_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -580,7 +586,7 @@ namespace mapnik
|
|||
int gray=bitmap->buffer[q*bitmap->width+p];
|
||||
if (gray)
|
||||
{
|
||||
pixmap_.blendPixel(i,j,rgba,gray);
|
||||
pixmap_.blendPixel2(i,j,rgba,gray,opacity_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -594,6 +600,7 @@ namespace mapnik
|
|||
unsigned text_ratio_;
|
||||
unsigned wrap_width_;
|
||||
glyphs_t glyphs_;
|
||||
double opacity_;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -187,11 +187,16 @@ namespace mapnik
|
|||
}
|
||||
}
|
||||
inline void blendPixel(int x,int y,unsigned int rgba1,int t)
|
||||
{
|
||||
blendPixel2(x,y,rgba1,t,1.0); // do not change opacity
|
||||
}
|
||||
|
||||
inline void blendPixel2(int x,int y,unsigned int rgba1,int t,double opacity)
|
||||
{
|
||||
if (checkBounds(x,y))
|
||||
{
|
||||
unsigned rgba0 = data_(x,y);
|
||||
unsigned a1 = (rgba1 >> 24) & 0xff; //t;
|
||||
unsigned a1 = (int)(((rgba1 >> 24) & 0xff) * opacity) & 0xff; // adjust for desired opacity
|
||||
a1 = (t*a1) / 255;
|
||||
if (a1 == 0) return;
|
||||
unsigned r1 = rgba1 & 0xff;
|
||||
|
|
|
@ -58,6 +58,7 @@ namespace mapnik
|
|||
boost::ptr_vector<placement_element> placements;
|
||||
|
||||
int wrap_width;
|
||||
bool wrap_before; // wraps text at wrap_char immediately before current word
|
||||
unsigned char wrap_char;
|
||||
int text_ratio;
|
||||
|
||||
|
@ -71,6 +72,7 @@ namespace mapnik
|
|||
bool has_dimensions;
|
||||
bool allow_overlap;
|
||||
std::pair<double, double> dimensions;
|
||||
int text_size;
|
||||
};
|
||||
|
||||
|
||||
|
@ -82,7 +84,7 @@ namespace mapnik
|
|||
placement_finder(DetectorT & detector);
|
||||
|
||||
//Try place a single label at the given point
|
||||
void find_point_placement(placement & p, double pos_x, double pos_y, vertical_alignment_e = MIDDLE, unsigned line_spacing=0, unsigned character_spacing=0);
|
||||
void find_point_placement(placement & p, double pos_x, double pos_y, vertical_alignment_e = MIDDLE, unsigned line_spacing=0, unsigned character_spacing=0, horizontal_alignment_e = H_MIDDLE, justify_alignment_e = J_MIDDLE);
|
||||
|
||||
//Iterate over the given path, placing point labels with respect to label_spacing
|
||||
template <typename T>
|
||||
|
|
|
@ -49,6 +49,16 @@ namespace mapnik
|
|||
std::string const& file,
|
||||
std::string const& type,
|
||||
unsigned width,unsigned height);
|
||||
|
||||
bool get_unlock_image() const; // image is not locked to the text placement
|
||||
void set_unlock_image(bool unlock_image);
|
||||
bool get_no_text() const; // do no render text
|
||||
void set_no_text(bool unlock_image);
|
||||
|
||||
private:
|
||||
|
||||
bool unlock_image_;
|
||||
bool no_text_;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ namespace mapnik
|
|||
enum label_placement_enum {
|
||||
POINT_PLACEMENT,
|
||||
LINE_PLACEMENT,
|
||||
VERTEX_PLACEMENT,
|
||||
label_placement_enum_MAX
|
||||
};
|
||||
|
||||
|
@ -55,6 +56,26 @@ namespace mapnik
|
|||
|
||||
DEFINE_ENUM( vertical_alignment_e, vertical_alignment );
|
||||
|
||||
enum horizontal_alignment
|
||||
{
|
||||
H_LEFT = 0,
|
||||
H_MIDDLE,
|
||||
H_RIGHT,
|
||||
horizontal_alignment_MAX
|
||||
};
|
||||
|
||||
DEFINE_ENUM( horizontal_alignment_e, horizontal_alignment );
|
||||
|
||||
enum justify_alignment
|
||||
{
|
||||
J_LEFT = 0,
|
||||
J_MIDDLE,
|
||||
J_RIGHT,
|
||||
justify_alignment_MAX
|
||||
};
|
||||
|
||||
DEFINE_ENUM( justify_alignment_e, justify_alignment );
|
||||
|
||||
enum text_convert
|
||||
{
|
||||
NONE = 0,
|
||||
|
@ -124,6 +145,15 @@ namespace mapnik
|
|||
double get_minimum_distance() const;
|
||||
void set_allow_overlap(bool overlap);
|
||||
bool get_allow_overlap() const;
|
||||
void set_opacity(double opacity);
|
||||
double get_opacity() const;
|
||||
bool get_wrap_before() const; // wrap text at wrap_char immediately before current work
|
||||
void set_wrap_before(bool wrap_before);
|
||||
void set_horizontal_alignment(horizontal_alignment_e valign);
|
||||
horizontal_alignment_e get_horizontal_alignment() const;
|
||||
void set_justify_alignment(justify_alignment_e valign);
|
||||
justify_alignment_e get_justify_alignment() const;
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::string face_name_;
|
||||
|
@ -149,6 +179,10 @@ namespace mapnik
|
|||
bool avoid_edges_;
|
||||
double minimum_distance_;
|
||||
bool overlap_;
|
||||
double opacity_;
|
||||
bool wrap_before_;
|
||||
horizontal_alignment_e halign_;
|
||||
justify_alignment_e jalign_;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -458,7 +458,21 @@ namespace mapnik
|
|||
proj_transform const& prj_trans)
|
||||
{
|
||||
typedef coord_transform2<CoordTransform,geometry2d> path_type;
|
||||
UnicodeString text = feature[sym.get_name()].to_unicode();
|
||||
|
||||
UnicodeString text;
|
||||
if( sym.get_no_text() )
|
||||
text = UnicodeString( " " ); // use 'space' as the text to render
|
||||
else
|
||||
text = feature[sym.get_name()].to_unicode(); // use text from feature to render
|
||||
|
||||
if ( sym.get_text_convert() == TOUPPER)
|
||||
{
|
||||
text = text.toUpper();
|
||||
}
|
||||
else if ( sym.get_text_convert() == TOLOWER)
|
||||
{
|
||||
text = text.toLower();
|
||||
}
|
||||
boost::shared_ptr<ImageData32> const& data = sym.get_image();
|
||||
if (text.length() > 0 && data)
|
||||
{
|
||||
|
@ -500,40 +514,67 @@ namespace mapnik
|
|||
{
|
||||
path_type path(t_,geom,prj_trans);
|
||||
|
||||
if (sym.get_label_placement() == POINT_PLACEMENT)
|
||||
label_placement_enum how_placed = sym.get_label_placement();
|
||||
if (how_placed == POINT_PLACEMENT || how_placed == VERTEX_PLACEMENT)
|
||||
{
|
||||
// for every vertex, try and place a shield/text
|
||||
geom.rewind(0);
|
||||
for( unsigned jj = 0; jj < geom.num_points(); jj++ )
|
||||
{
|
||||
double label_x;
|
||||
double label_y;
|
||||
double z=0.0;
|
||||
placement text_placement(info, sym, false);
|
||||
text_placement.avoid_edges = sym.get_avoid_edges();
|
||||
geom.label_position(&label_x, &label_y);
|
||||
text_placement.allow_overlap = sym.get_allow_overlap();
|
||||
if( how_placed == VERTEX_PLACEMENT )
|
||||
geom.vertex(&label_x,&label_y); // by vertex
|
||||
else
|
||||
geom.label_position(&label_x, &label_y); // by middle of line or by point
|
||||
prj_trans.backward(label_x,label_y, z);
|
||||
t_.forward(&label_x,&label_y);
|
||||
finder.find_point_placement(text_placement,label_x,label_y);
|
||||
|
||||
for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ ii)
|
||||
finder.find_point_placement( text_placement,label_x,label_y,sym.get_vertical_alignment(),sym.get_line_spacing(),
|
||||
sym.get_character_spacing(),sym.get_horizontal_alignment(),sym.get_justify_alignment() );
|
||||
|
||||
// check to see if image overlaps anything too, there is only ever 1 placement found for points and verticies
|
||||
if( text_placement.placements.size() > 0)
|
||||
{
|
||||
double x = text_placement.placements[ii].starting_x;
|
||||
double y = text_placement.placements[ii].starting_y;
|
||||
double x = text_placement.placements[0].starting_x;
|
||||
double y = text_placement.placements[0].starting_y;
|
||||
int px;
|
||||
int py;
|
||||
Envelope<double> label_ext;
|
||||
|
||||
if( !sym.get_unlock_image() )
|
||||
{ // center image at text center position
|
||||
// remove displacement from image label
|
||||
position pos = sym.get_displacement();
|
||||
double lx = x - boost::get<0>(pos);
|
||||
double ly = y - boost::get<1>(pos);
|
||||
int px=int(lx - (0.5 * w)) ;
|
||||
int py=int(ly - (0.5 * h)) ;
|
||||
Envelope<double> label_ext (floor(lx - 0.5 * w), floor(ly - 0.5 * h), ceil (lx + 0.5 * w), ceil (ly + 0.5 * h));
|
||||
px=int(floor(lx - (0.5 * w))) ;
|
||||
py=int(floor(ly - (0.5 * h))) ;
|
||||
label_ext.init( floor(lx - 0.5 * w), floor(ly - 0.5 * h), ceil (lx + 0.5 * w), ceil (ly + 0.5 * h) );
|
||||
}
|
||||
else
|
||||
{ // center image at reference location
|
||||
px=int(floor(label_x - 0.5 * w));
|
||||
py=int(floor(label_y - 0.5 * h));
|
||||
label_ext.init( floor(label_x - 0.5 * w), floor(label_y - 0.5 * h), ceil (label_x + 0.5 * w), ceil (label_y + 0.5 * h));
|
||||
}
|
||||
|
||||
if ( detector_.has_placement(label_ext))
|
||||
if ( sym.get_allow_overlap() || detector_.has_placement(label_ext) )
|
||||
{
|
||||
pixmap_.set_rectangle_alpha(px,py,*data);
|
||||
Envelope<double> dim = ren.prepare_glyphs(&text_placement.placements[ii]);
|
||||
//pixmap_.set_rectangle_alpha(px,py,*data);
|
||||
pixmap_.set_rectangle_alpha2(*data,px,py,sym.get_opacity());
|
||||
Envelope<double> dim = ren.prepare_glyphs(&text_placement.placements[0]);
|
||||
ren.render(x,y);
|
||||
detector_.insert(label_ext);
|
||||
}
|
||||
}
|
||||
finder.update_detector(text_placement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (geom.num_points() > 1 && sym.get_label_placement() == LINE_PLACEMENT)
|
||||
{
|
||||
|
@ -795,6 +836,7 @@ namespace mapnik
|
|||
ren.set_fill(fill);
|
||||
ren.set_halo_fill(sym.get_halo_fill());
|
||||
ren.set_halo_radius(sym.get_halo_radius());
|
||||
ren.set_opacity(sym.get_opacity());
|
||||
|
||||
placement_finder<label_collision_detector4> finder(detector_);
|
||||
|
||||
|
@ -816,7 +858,8 @@ namespace mapnik
|
|||
geom.label_position(&label_x, &label_y);
|
||||
prj_trans.backward(label_x,label_y, z);
|
||||
t_.forward(&label_x,&label_y);
|
||||
finder.find_point_placement(text_placement,label_x,label_y,sym.get_vertical_alignment(),sym.get_line_spacing(),sym.get_character_spacing());
|
||||
finder.find_point_placement(text_placement,label_x,label_y,sym.get_vertical_alignment(),sym.get_line_spacing(),
|
||||
sym.get_character_spacing(),sym.get_horizontal_alignment(),sym.get_justify_alignment());
|
||||
finder.update_detector(text_placement);
|
||||
}
|
||||
else if ( geom.num_points() > 1 && sym.get_label_placement() == LINE_PLACEMENT)
|
||||
|
|
114
src/load_map.cpp
114
src/load_map.cpp
|
@ -859,8 +859,8 @@ namespace mapnik
|
|||
text_symbol.set_label_placement( placement );
|
||||
// vertical alignment
|
||||
vertical_alignment_e valign = get_attr<vertical_alignment_e>(sym, "vertical_alignment", BOTTOM);
|
||||
|
||||
text_symbol.set_vertical_alignment(valign);
|
||||
|
||||
// halo fill and radius
|
||||
optional<color> halo_fill = get_opt_attr<color>(sym, "halo_fill");
|
||||
if (halo_fill)
|
||||
|
@ -877,6 +877,10 @@ namespace mapnik
|
|||
// text ratio and wrap width
|
||||
optional<unsigned> text_ratio =
|
||||
get_opt_attr<unsigned>(sym, "text_ratio");
|
||||
if (text_ratio)
|
||||
{
|
||||
text_symbol.set_text_ratio(*text_ratio);
|
||||
}
|
||||
|
||||
optional<unsigned> wrap_width =
|
||||
get_opt_attr<unsigned>(sym, "wrap_width");
|
||||
|
@ -885,6 +889,13 @@ namespace mapnik
|
|||
text_symbol.set_wrap_width(*wrap_width);
|
||||
}
|
||||
|
||||
optional<boolean> wrap_before =
|
||||
get_opt_attr<boolean>(sym, "wrap_before");
|
||||
if (wrap_before)
|
||||
{
|
||||
text_symbol.set_wrap_before(*wrap_before);
|
||||
}
|
||||
|
||||
// character used to break long strings
|
||||
optional<std::string> wrap_char =
|
||||
get_opt_attr<std::string>(sym, "wrap_character");
|
||||
|
@ -943,6 +954,14 @@ namespace mapnik
|
|||
text_symbol.set_allow_overlap( * allow_overlap );
|
||||
}
|
||||
|
||||
// opacity
|
||||
optional<double> opacity =
|
||||
get_opt_attr<double>(sym, "opacity");
|
||||
if (opacity)
|
||||
{
|
||||
text_symbol.set_opacity( * opacity );
|
||||
}
|
||||
|
||||
// max_char_angle_delta
|
||||
optional<double> max_char_angle_delta =
|
||||
get_opt_attr<double>(sym, "max_char_angle_delta");
|
||||
|
@ -951,6 +970,14 @@ namespace mapnik
|
|||
text_symbol.set_max_char_angle_delta( * max_char_angle_delta);
|
||||
}
|
||||
|
||||
// horizontal alignment
|
||||
horizontal_alignment_e halign = get_attr<horizontal_alignment_e>(sym, "horizontal_alignment", H_MIDDLE);
|
||||
text_symbol.set_horizontal_alignment(halign);
|
||||
|
||||
// justify alignment
|
||||
justify_alignment_e jalign = get_attr<justify_alignment_e>(sym, "justify_alignment", J_MIDDLE);
|
||||
text_symbol.set_justify_alignment(jalign);
|
||||
|
||||
rule.append(text_symbol);
|
||||
}
|
||||
catch (const config_error & ex)
|
||||
|
@ -1078,6 +1105,91 @@ namespace mapnik
|
|||
shield_symbol.set_label_spacing(*spacing);
|
||||
}
|
||||
|
||||
// allow_overlap
|
||||
optional<boolean> allow_overlap =
|
||||
get_opt_attr<boolean>(sym, "allow_overlap");
|
||||
if (allow_overlap)
|
||||
{
|
||||
shield_symbol.set_allow_overlap( * allow_overlap );
|
||||
}
|
||||
|
||||
// vertical alignment
|
||||
vertical_alignment_e valign = get_attr<vertical_alignment_e>(sym, "vertical_alignment", BOTTOM);
|
||||
shield_symbol.set_vertical_alignment(valign);
|
||||
|
||||
// horizontal alignment
|
||||
horizontal_alignment_e halign = get_attr<horizontal_alignment_e>(sym, "horizontal_alignment", H_MIDDLE);
|
||||
shield_symbol.set_horizontal_alignment(halign);
|
||||
|
||||
// justify alignment
|
||||
justify_alignment_e jalign = get_attr<justify_alignment_e>(sym, "justify_alignment", J_MIDDLE);
|
||||
shield_symbol.set_justify_alignment(jalign);
|
||||
|
||||
optional<unsigned> wrap_width =
|
||||
get_opt_attr<unsigned>(sym, "wrap_width");
|
||||
if (wrap_width)
|
||||
{
|
||||
shield_symbol.set_wrap_width(*wrap_width);
|
||||
}
|
||||
|
||||
optional<boolean> wrap_before =
|
||||
get_opt_attr<boolean>(sym, "wrap_before");
|
||||
if (wrap_before)
|
||||
{
|
||||
shield_symbol.set_wrap_before(*wrap_before);
|
||||
}
|
||||
|
||||
// character used to break long strings
|
||||
optional<std::string> wrap_char =
|
||||
get_opt_attr<std::string>(sym, "wrap_character");
|
||||
if (wrap_char && (*wrap_char).size() > 0)
|
||||
{
|
||||
shield_symbol.set_wrap_char((*wrap_char)[0]);
|
||||
}
|
||||
|
||||
// text conversion before rendering
|
||||
text_convert_e tconvert =
|
||||
get_attr<text_convert_e>(sym, "text_convert", NONE);
|
||||
shield_symbol.set_text_convert(tconvert);
|
||||
|
||||
// spacing between text lines
|
||||
optional<unsigned> line_spacing = get_opt_attr<unsigned>(sym, "line_spacing");
|
||||
if (line_spacing)
|
||||
{
|
||||
shield_symbol.set_line_spacing(*line_spacing);
|
||||
}
|
||||
|
||||
// spacing between characters in text
|
||||
optional<unsigned> character_spacing = get_opt_attr<unsigned>(sym, "character_spacing");
|
||||
if (character_spacing)
|
||||
{
|
||||
shield_symbol.set_character_spacing(*character_spacing);
|
||||
}
|
||||
|
||||
// opacity
|
||||
optional<double> opacity =
|
||||
get_opt_attr<double>(sym, "opacity");
|
||||
if (opacity)
|
||||
{
|
||||
shield_symbol.set_opacity( * opacity );
|
||||
}
|
||||
|
||||
// unlock_image
|
||||
optional<boolean> unlock_image =
|
||||
get_opt_attr<boolean>(sym, "unlock_image");
|
||||
if (unlock_image)
|
||||
{
|
||||
shield_symbol.set_unlock_image( * unlock_image );
|
||||
}
|
||||
|
||||
// no text
|
||||
optional<boolean> no_text =
|
||||
get_opt_attr<boolean>(sym, "no_text");
|
||||
if (no_text)
|
||||
{
|
||||
shield_symbol.set_no_text( * no_text );
|
||||
}
|
||||
|
||||
rule.append(shield_symbol);
|
||||
}
|
||||
catch (ImageReaderException const & ex )
|
||||
|
|
|
@ -55,6 +55,8 @@ namespace mapnik
|
|||
displacement_(sym.get_displacement()),
|
||||
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(sym.get_label_spacing()),
|
||||
label_position_tolerance(sym.get_label_position_tolerance()),
|
||||
|
@ -65,7 +67,8 @@ namespace mapnik
|
|||
has_dimensions(has_dimensions_),
|
||||
allow_overlap(false),
|
||||
dimensions(std::make_pair(sym.get_image()->width(),
|
||||
sym.get_image()->height()))
|
||||
sym.get_image()->height())),
|
||||
text_size(sym.get_text_size())
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -75,6 +78,7 @@ namespace mapnik
|
|||
displacement_(sym.get_displacement()),
|
||||
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(sym.get_label_spacing()),
|
||||
|
@ -85,7 +89,8 @@ namespace mapnik
|
|||
avoid_edges(sym.get_avoid_edges()),
|
||||
has_dimensions(false),
|
||||
allow_overlap(sym.get_allow_overlap()),
|
||||
dimensions()
|
||||
dimensions(),
|
||||
text_size(sym.get_text_size())
|
||||
{
|
||||
}
|
||||
placement::~placement()
|
||||
|
@ -217,155 +222,185 @@ namespace mapnik
|
|||
double label_y,
|
||||
vertical_alignment_e valign,
|
||||
unsigned line_spacing,
|
||||
unsigned character_spacing)
|
||||
unsigned character_spacing,
|
||||
horizontal_alignment_e halign,
|
||||
justify_alignment_e jalign)
|
||||
{
|
||||
double x, y;
|
||||
std::auto_ptr<placement_element> current_placement(new placement_element);
|
||||
|
||||
std::pair<double, double> string_dimensions = p.info.get_dimensions();
|
||||
double string_width = string_dimensions.first;
|
||||
double string_width = string_dimensions.first + (character_spacing *(p.info.num_characters()-1));
|
||||
double string_height = string_dimensions.second;
|
||||
|
||||
// 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
|
||||
|
||||
// check if we need to wrap the string
|
||||
double wrap_at = string_width + 1;
|
||||
double wrap_at = string_width + 1.0;
|
||||
if (p.wrap_width && string_width > p.wrap_width)
|
||||
{
|
||||
if (p.text_ratio)
|
||||
for (int i = 1; ((wrap_at = string_width/i)/(string_height*i)) > p.text_ratio && (string_width/i) > p.wrap_width; ++i);
|
||||
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
|
||||
// work out where our line breaks need to be and the resultant width to the 'wrapped' string
|
||||
std::vector<int> line_breaks;
|
||||
std::vector<double> line_widths;
|
||||
std::vector<double> line_heights;
|
||||
|
||||
if (wrap_at < string_width && p.info.num_characters() > 0)
|
||||
{
|
||||
int last_wrap_char = 0;
|
||||
string_width = 0;
|
||||
string_height = 0;
|
||||
double line_width = 0;
|
||||
double line_height = 0;
|
||||
double word_width = 0;
|
||||
double word_height = 0;
|
||||
int last_wrap_char_width = 0;
|
||||
string_width = 0.0;
|
||||
string_height = 0.0;
|
||||
double line_width = 0.0;
|
||||
double word_width = 0.0;
|
||||
|
||||
for (unsigned int ii = 0; ii < p.info.num_characters(); ii++)
|
||||
{
|
||||
character_info ci;
|
||||
ci = p.info.at(ii);
|
||||
|
||||
unsigned cwidth = ci.width + character_spacing;
|
||||
double cwidth = ci.width + character_spacing;
|
||||
|
||||
unsigned c = ci.character;
|
||||
word_width += cwidth;
|
||||
word_height = word_height > (ci.height + line_spacing) ? word_height : (ci.height + line_spacing);
|
||||
|
||||
if (c == p.wrap_char)
|
||||
{
|
||||
last_wrap_char = ii;
|
||||
last_wrap_char_width = cwidth;
|
||||
line_width += word_width;
|
||||
line_height = line_height > word_height ? line_height : word_height;
|
||||
word_width = 0;
|
||||
word_height = 0;
|
||||
word_width = 0.0;
|
||||
}
|
||||
if (line_width > 0 && line_width > wrap_at)
|
||||
|
||||
// wrap text at first wrap_char after (default) the wrap width or immediately before the current word
|
||||
if (line_width > 0 && (((line_width - character_spacing) > wrap_at && !p.wrap_before) ||
|
||||
((line_width + word_width - character_spacing) > wrap_at && p.wrap_before)) )
|
||||
{
|
||||
// Remove width of breaking space character since it is not rendered
|
||||
line_width -= cwidth;
|
||||
// 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 += line_height;
|
||||
string_height += max_character_height;
|
||||
line_breaks.push_back(last_wrap_char);
|
||||
line_widths.push_back(line_width);
|
||||
line_heights.push_back(line_height);
|
||||
ii = last_wrap_char;
|
||||
line_width = 0;
|
||||
line_height = 0;
|
||||
word_width = 0;
|
||||
word_height = 0;
|
||||
line_width = 0.0;
|
||||
word_width = 0.0;
|
||||
}
|
||||
}
|
||||
line_width += word_width;
|
||||
line_height = line_height > word_height ? line_height : word_height;
|
||||
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 += line_height;
|
||||
line_breaks.push_back(p.info.num_characters() + 1);
|
||||
string_height += max_character_height;
|
||||
line_breaks.push_back(p.info.num_characters());
|
||||
line_widths.push_back(line_width);
|
||||
line_heights.push_back(line_height);
|
||||
}
|
||||
if (line_breaks.size() == 0)
|
||||
{
|
||||
line_breaks.push_back(p.info.num_characters() + 1);
|
||||
line_breaks.push_back(p.info.num_characters());
|
||||
line_widths.push_back(string_width);
|
||||
line_heights.push_back(string_height);
|
||||
}
|
||||
int total_lines = line_breaks.size();
|
||||
|
||||
p.info.set_dimensions(string_width, string_height);
|
||||
p.info.set_dimensions( string_width, (string_height + (line_spacing * (total_lines-1))) );
|
||||
|
||||
unsigned int line_number = 0;
|
||||
unsigned int index_to_wrap_at = line_breaks[line_number];
|
||||
double line_width = line_widths[line_number];
|
||||
double line_height = line_heights[line_number];
|
||||
// if needed, adjust for desired vertical alignment
|
||||
current_placement->starting_y = label_y; // no adjustment, default is MIDDLE
|
||||
|
||||
if (valign == 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 (valign == 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 (valign == TOP )
|
||||
current_placement->starting_y -= (p.text_size - max_character_height); // move up by the error
|
||||
|
||||
current_placement->starting_x = label_x;
|
||||
if (valign == BOTTOM)
|
||||
{
|
||||
current_placement->starting_y = label_y;
|
||||
}
|
||||
else if (valign == MIDDLE)
|
||||
{
|
||||
current_placement->starting_y = label_y - 0.5 * (line_heights.size() - 1) * line_height ;
|
||||
}
|
||||
else // TOP
|
||||
{
|
||||
current_placement->starting_y = label_y - line_heights.size() * line_height;
|
||||
}
|
||||
current_placement->starting_y -= ((p.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 (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 += boost::tuples::get<0>(p.displacement_);
|
||||
current_placement->starting_y += boost::tuples::get<1>(p.displacement_);
|
||||
|
||||
x = -line_width/2.0;
|
||||
y = -line_height/2.0;
|
||||
// presets for first line
|
||||
unsigned int line_number = 0;
|
||||
unsigned int index_to_wrap_at = line_breaks[0];
|
||||
double line_width = line_widths[0];
|
||||
|
||||
// set for upper left corner of text envelope for the first line, bottom left of first character
|
||||
x = -(line_width / 2.0);
|
||||
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( jalign == J_LEFT )
|
||||
x = -(string_width / 2.0);
|
||||
|
||||
else if (jalign == J_RIGHT)
|
||||
x = (string_width / 2.0) - line_width;
|
||||
|
||||
// save each character rendering position and build envelope as go thru loop
|
||||
for (unsigned i = 0; i < p.info.num_characters(); i++)
|
||||
{
|
||||
character_info ci;
|
||||
ci = p.info.at(i);
|
||||
|
||||
unsigned cwidth = ci.width + character_spacing;
|
||||
double cwidth = ci.width + character_spacing;
|
||||
|
||||
unsigned c = ci.character;
|
||||
if (i == index_to_wrap_at)
|
||||
{
|
||||
index_to_wrap_at = line_breaks[++line_number];
|
||||
line_width = line_widths[line_number];
|
||||
line_height = line_heights[line_number];
|
||||
y -= line_height;
|
||||
x = -line_width/2.0;
|
||||
|
||||
y -= (max_character_height + line_spacing); // move position down to line start
|
||||
|
||||
// reset to begining of line position
|
||||
x = ((jalign == J_LEFT)? -(string_width / 2.0): ((jalign == J_RIGHT)? ((string_width /2.0) - line_width): -(line_width / 2.0)));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// place the character relative to the center of the string envelope
|
||||
current_placement->add_node(c, x, y, 0.0);
|
||||
|
||||
// compute the Bounding Box for each character and test for:
|
||||
// overlap, minimum distance or edge avoidance - exit if condition occurs
|
||||
Envelope<double> e;
|
||||
if (p.has_dimensions)
|
||||
{
|
||||
e.init(current_placement->starting_x - (p.dimensions.first/2.0),
|
||||
e.init(current_placement->starting_x - (p.dimensions.first/2.0), // Top Left
|
||||
current_placement->starting_y - (p.dimensions.second/2.0),
|
||||
current_placement->starting_x + (p.dimensions.first/2.0),
|
||||
|
||||
current_placement->starting_x + (p.dimensions.first/2.0), // Bottom Right
|
||||
current_placement->starting_y + (p.dimensions.second/2.0));
|
||||
}
|
||||
else
|
||||
{
|
||||
e.init(current_placement->starting_x + x,
|
||||
e.init(current_placement->starting_x + x, // Bottom Left
|
||||
current_placement->starting_y - y,
|
||||
current_placement->starting_x + x + ci.width,
|
||||
current_placement->starting_y - y - ci.height);
|
||||
|
||||
current_placement->starting_x + x + ci.width, // Top Right
|
||||
current_placement->starting_y - y - max_character_height);
|
||||
}
|
||||
|
||||
if (!dimensions_.intersects(e) ||
|
||||
(!p.allow_overlap && !detector_.has_point_placement(e,
|
||||
p.minimum_distance)))
|
||||
(!p.allow_overlap && !detector_.has_point_placement(e,p.minimum_distance)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -374,14 +409,13 @@ p.minimum_distance)))
|
|||
|
||||
p.envelopes.push(e);
|
||||
}
|
||||
x += cwidth;
|
||||
x += cwidth; // move position to next character
|
||||
}
|
||||
p.placements.push_back(current_placement.release());
|
||||
//update_detector(p);
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <typename DetectorT>
|
||||
template <typename PathT>
|
||||
void placement_finder<DetectorT>::find_line_placements(placement & p, PathT & shape_path)
|
||||
|
|
|
@ -157,6 +157,20 @@ namespace mapnik
|
|||
|
||||
add_font_attributes( sym_node, sym);
|
||||
add_image_attributes( sym_node, sym);
|
||||
|
||||
// pseudo-default-construct a shield_symbolizer. It is used
|
||||
// to avoid printing of attributes with default values without
|
||||
// repeating the default values here.
|
||||
// maybe add a real, explicit default-ctor?
|
||||
shield_symbolizer sym_dfl("<no default>", "<no default>", 0, color(0,0,0), "<no default>", "<no default>", 0, 0 );
|
||||
if (sym.get_unlock_image() != sym_dfl.get_unlock_image() || explicit_defaults_ )
|
||||
{
|
||||
set_attr( sym_node, "unlock_image", sym.get_unlock_image() );
|
||||
}
|
||||
if (sym.get_no_text() != sym_dfl.get_no_text() || explicit_defaults_ )
|
||||
{
|
||||
set_attr( sym_node, "no_text", sym.get_no_text() );
|
||||
}
|
||||
}
|
||||
|
||||
void operator () ( const text_symbolizer & sym )
|
||||
|
@ -279,6 +293,10 @@ namespace mapnik
|
|||
{
|
||||
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()) );
|
||||
|
@ -311,6 +329,18 @@ namespace mapnik
|
|||
{
|
||||
set_attr( node, "avoid_edges", sym.get_avoid_edges() );
|
||||
}
|
||||
if (sym.get_opacity() != dfl.get_opacity() || explicit_defaults_ )
|
||||
{
|
||||
set_attr( node, "opacity", sym.get_opacity() );
|
||||
}
|
||||
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() );
|
||||
}
|
||||
}
|
||||
ptree & rule_;
|
||||
bool explicit_defaults_;
|
||||
|
|
|
@ -43,7 +43,9 @@ namespace mapnik
|
|||
std::string const& type,
|
||||
unsigned width,unsigned height)
|
||||
: text_symbolizer(name, face_name, size, fill),
|
||||
symbolizer_with_image( file, type, width, height )
|
||||
symbolizer_with_image( file, type, width, height ),
|
||||
unlock_image_(false),
|
||||
no_text_(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -55,8 +57,30 @@ namespace mapnik
|
|||
std::string const& type,
|
||||
unsigned width,unsigned height)
|
||||
: text_symbolizer(name, size, fill),
|
||||
symbolizer_with_image( file, type, width, height )
|
||||
symbolizer_with_image( file, type, width, height ),
|
||||
unlock_image_(false),
|
||||
no_text_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void shield_symbolizer::set_unlock_image(bool unlock_image)
|
||||
{
|
||||
unlock_image_ = unlock_image;
|
||||
}
|
||||
|
||||
bool shield_symbolizer::get_unlock_image() const
|
||||
{
|
||||
return unlock_image_;
|
||||
}
|
||||
|
||||
void shield_symbolizer::set_no_text(bool no_text)
|
||||
{
|
||||
no_text_ = no_text;
|
||||
}
|
||||
|
||||
bool shield_symbolizer::get_no_text() const
|
||||
{
|
||||
return no_text_;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
static const char * label_placement_strings[] = {
|
||||
"point",
|
||||
"line",
|
||||
"vertex",
|
||||
""
|
||||
};
|
||||
|
||||
|
@ -48,6 +49,26 @@ static const char * vertical_alignment_strings[] = {
|
|||
|
||||
IMPLEMENT_ENUM( mapnik::vertical_alignment_e, vertical_alignment_strings );
|
||||
|
||||
static const char * horizontal_alignment_strings[] = {
|
||||
"left",
|
||||
"middle",
|
||||
"right",
|
||||
""
|
||||
};
|
||||
|
||||
|
||||
IMPLEMENT_ENUM( mapnik::horizontal_alignment_e, horizontal_alignment_strings );
|
||||
|
||||
static const char * justify_alignment_strings[] = {
|
||||
"left",
|
||||
"middle",
|
||||
"right",
|
||||
""
|
||||
};
|
||||
|
||||
|
||||
IMPLEMENT_ENUM( mapnik::justify_alignment_e, justify_alignment_strings );
|
||||
|
||||
static const char * text_convert_strings[] = {
|
||||
"none",
|
||||
"toupper",
|
||||
|
@ -85,7 +106,12 @@ namespace mapnik
|
|||
displacement_(0.0,0.0),
|
||||
avoid_edges_(false),
|
||||
minimum_distance_(0.0),
|
||||
overlap_(false) {}
|
||||
overlap_(false),
|
||||
opacity_(1.0),
|
||||
wrap_before_(false),
|
||||
halign_(H_MIDDLE),
|
||||
jalign_(J_MIDDLE) {}
|
||||
|
||||
text_symbolizer::text_symbolizer(std::string const& name, unsigned size, color const& fill)
|
||||
: name_(name),
|
||||
//face_name_(""),
|
||||
|
@ -110,7 +136,12 @@ namespace mapnik
|
|||
displacement_(0.0,0.0),
|
||||
avoid_edges_(false),
|
||||
minimum_distance_(0.0),
|
||||
overlap_(false) {}
|
||||
overlap_(false),
|
||||
opacity_(1.0),
|
||||
wrap_before_(false),
|
||||
halign_(H_MIDDLE),
|
||||
jalign_(J_MIDDLE) {}
|
||||
|
||||
text_symbolizer::text_symbolizer(text_symbolizer const& rhs)
|
||||
: name_(rhs.name_),
|
||||
face_name_(rhs.face_name_),
|
||||
|
@ -135,7 +166,11 @@ namespace mapnik
|
|||
displacement_(rhs.displacement_),
|
||||
avoid_edges_(rhs.avoid_edges_),
|
||||
minimum_distance_(rhs.minimum_distance_),
|
||||
overlap_(rhs.overlap_) {}
|
||||
overlap_(rhs.overlap_),
|
||||
opacity_(rhs.opacity_),
|
||||
wrap_before_(rhs.wrap_before_),
|
||||
halign_(rhs.halign_),
|
||||
jalign_(rhs.jalign_) {}
|
||||
|
||||
text_symbolizer& text_symbolizer::operator=(text_symbolizer const& other)
|
||||
{
|
||||
|
@ -165,6 +200,10 @@ namespace mapnik
|
|||
avoid_edges_ = other.avoid_edges_;
|
||||
minimum_distance_ = other.minimum_distance_;
|
||||
overlap_ = other.overlap_;
|
||||
opacity_ = other.opacity_;
|
||||
wrap_before_ = other.wrap_before_;
|
||||
halign_ = other.halign_;
|
||||
jalign_ = other.jalign_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -218,6 +257,16 @@ namespace mapnik
|
|||
wrap_width_ = width;
|
||||
}
|
||||
|
||||
bool text_symbolizer::get_wrap_before() const
|
||||
{
|
||||
return wrap_before_;
|
||||
}
|
||||
|
||||
void text_symbolizer::set_wrap_before(bool wrap_before)
|
||||
{
|
||||
wrap_before_ = wrap_before;
|
||||
}
|
||||
|
||||
unsigned char text_symbolizer::get_wrap_char() const
|
||||
{
|
||||
return wrap_char_;
|
||||
|
@ -417,4 +466,34 @@ namespace mapnik
|
|||
{
|
||||
return overlap_;
|
||||
}
|
||||
|
||||
void text_symbolizer::set_opacity(double opacity)
|
||||
{
|
||||
opacity_ = opacity;
|
||||
}
|
||||
|
||||
double text_symbolizer::get_opacity() const
|
||||
{
|
||||
return opacity_;
|
||||
}
|
||||
|
||||
void text_symbolizer::set_horizontal_alignment(horizontal_alignment_e halign)
|
||||
{
|
||||
halign_ = halign;
|
||||
}
|
||||
|
||||
horizontal_alignment_e text_symbolizer::get_horizontal_alignment() const
|
||||
{
|
||||
return halign_;
|
||||
}
|
||||
|
||||
void text_symbolizer::set_justify_alignment(justify_alignment_e jalign)
|
||||
{
|
||||
jalign_ = jalign;
|
||||
}
|
||||
|
||||
justify_alignment_e text_symbolizer::get_justify_alignment() const
|
||||
{
|
||||
return jalign_;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue