diff --git a/include/mapnik/text/placement_finder_impl.hpp b/include/mapnik/text/placement_finder_impl.hpp index 6d7146b03..c851c3f79 100644 --- a/include/mapnik/text/placement_finder_impl.hpp +++ b/include/mapnik/text/placement_finder_impl.hpp @@ -76,11 +76,11 @@ bool placement_finder::find_line_placements(T & path, bool points) // halign == H_LEFT -> don't move if (horizontal_alignment_ == H_MIDDLE || horizontal_alignment_ == H_AUTO) { - pp.forward(spacing/2.0); + if (!pp.forward(spacing / 2.0)) continue; } else if (horizontal_alignment_ == H_RIGHT) { - pp.forward(pp.length()); + if (!pp.forward(pp.length())) continue; } if (move_dx_ != 0.0) path_move_dx(pp, move_dx_); diff --git a/include/mapnik/text/vertex_cache.hpp b/include/mapnik/text/vertex_cache.hpp index 532e74f62..bd4920f7f 100644 --- a/include/mapnik/text/vertex_cache.hpp +++ b/include/mapnik/text/vertex_cache.hpp @@ -110,7 +110,7 @@ public: // Skip a certain amount of space. - // This function automatically calculates new points if the position is not exactly + // This functions automatically calculate new points if the position is not exactly // on a point on the path. bool forward(double length); @@ -118,6 +118,8 @@ public: bool backward(double length); // Move in any direction (based on sign of length). Returns false if it reaches either end of the path. bool move(double length); + // Move to given distance. + bool move_to_distance(double distance); // Work on next subpath. Returns false if the is no next subpath. bool next_subpath(); @@ -139,6 +141,10 @@ private: bool next_segment(); bool previous_segment(); double current_segment_angle(); + void find_line_circle_intersection( + double cx, double cy, double radius, + double x1, double y1, double x2, double y2, + double & ix, double & iy) const; // Position as calculated by last move/forward/next call. pixel_position current_position_; // First pixel of current segment. diff --git a/src/text/placement_finder.cpp b/src/text/placement_finder.cpp index 0c7431872..0a4f2de8e 100644 --- a/src/text/placement_finder.cpp +++ b/src/text/placement_finder.cpp @@ -214,7 +214,7 @@ bool placement_finder::single_line_placement(vertex_cache &pp, text_upright_e or { if (current_cluster != static_cast(glyph.char_index)) { - if (!off_pp.move(sign * (layout.cluster_width(current_cluster) + last_glyph_spacing))) + if (!off_pp.move_to_distance(sign * (layout.cluster_width(current_cluster) + last_glyph_spacing))) { return false; } diff --git a/src/text/vertex_cache.cpp b/src/text/vertex_cache.cpp index e8ceb2c72..e0ddeb07f 100644 --- a/src/text/vertex_cache.cpp +++ b/src/text/vertex_cache.cpp @@ -236,6 +236,8 @@ bool vertex_cache::backward(double length) bool vertex_cache::move(double length) { + if (current_segment_ == current_subpath_->vector.end()) return false; + position_ += length; length += position_in_segment_; while (length >= current_segment_->length) @@ -254,6 +256,67 @@ bool vertex_cache::move(double length) return true; } +bool vertex_cache::move_to_distance(double distance) +{ + if (current_segment_ == current_subpath_->vector.end()) return false; + + double position_in_segment = position_in_segment_ + distance; + if (position_in_segment < .0 || position_in_segment >= current_segment_->length) + { + // If there isn't enough distance left on this segment + // then we need to search until we find the line segment that ends further than distance away + double abs_distance = std::abs(distance); + double new_abs_distance = .0; + pixel_position inner_pos; // Inside circle. + pixel_position outer_pos; // Outside circle. + + position_ -= position_in_segment_; + + if (distance > .0) + { + do + { + position_ += current_segment_->length; + if (!next_segment()) return false; + new_abs_distance = (current_position_ - current_segment_->pos).length(); + } + while (new_abs_distance < abs_distance); + + inner_pos = segment_starting_point_; + outer_pos = current_segment_->pos; + } + else + { + do + { + if (!previous_segment()) return false; + position_ -= current_segment_->length; + new_abs_distance = (current_position_ - segment_starting_point_).length(); + } + while (new_abs_distance < abs_distance); + + inner_pos = current_segment_->pos; + outer_pos = segment_starting_point_; + } + + find_line_circle_intersection(current_position_.x, current_position_.y, abs_distance, + inner_pos.x, inner_pos.y, outer_pos.x, outer_pos.y, + current_position_.x, current_position_.y); + + position_in_segment_ = (current_position_ - segment_starting_point_).length(); + position_ += position_in_segment_; + } + else + { + position_ += distance; + distance += position_in_segment_; + double factor = distance / current_segment_->length; + position_in_segment_ = distance; + current_position_ = segment_starting_point_ + (current_segment_->pos - segment_starting_point_) * factor; + } + return true; +} + void vertex_cache::rewind(unsigned) { vertex_subpath_ = subpaths_.begin(); @@ -297,4 +360,50 @@ void vertex_cache::restore_state(state const& s) angle_valid_ = false; } +void vertex_cache::find_line_circle_intersection( + double cx, double cy, double radius, + double x1, double y1, double x2, double y2, + double & ix, double & iy) const +{ + double dx = x2 - x1; + double dy = y2 - y1; + + double A = dx * dx + dy * dy; + double B = 2 * (dx * (x1 - cx) + dy * (y1 - cy)); + double C = (x1 - cx) * (x1 - cx) + (y1 - cy) * (y1 - cy) - radius * radius; + + double det = B * B - 4 * A * C; + if (A <= 1.0e-7 || det < 0) + { + // Should never happen. + // No real solutions. + return; + } + else if (det == 0) + { + // Could potentially happen.... + // One solution. + double t = -B / (2 * A); + ix = x1 + t * dx; + iy = y1 + t * dy; + return; + } + else + { + // Two solutions. + + // Always use the 1st one + // We only really have one solution here, as we know the line segment will start in the circle and end outside + double t = (-B + std::sqrt(det)) / (2 * A); + ix = x1 + t * dx; + iy = y1 + t * dy; + + //t = (-B - std::sqrt(det)) / (2 * A); + //ix = x1 + t * dx; + //iy = y1 + t * dy; + + return; + } +} + } //ns mapnik