/***************************************************************************** * * 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 * *****************************************************************************/ //mapnik #include #include #include #include #include #include #include // agg #include "agg_path_length.h" // boost #include #include #include #include //stl #include #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif namespace mapnik { template std::pair get_position_at_distance(double target_distance, T & shape_path) { double x1 = 0.0; double y1 = 0.0; double x2 = 0.0; double y2 = 0.0; double distance = 0.0; bool first = true; unsigned cmd; double x = 0.0; double y = 0.0; shape_path.rewind(0); while (!agg::is_stop(cmd = shape_path.vertex(&x2,&y2))) { if (first || agg::is_move_to(cmd)) { first = false; } else { double dx = x2-x1; double dy = y2-y1; double segment_length = std::sqrt(dx*dx + dy*dy); distance +=segment_length; if (distance > target_distance) { x = x2 - dx * (distance - target_distance)/segment_length; y = y2 - dy * (distance - target_distance)/segment_length; break; } } x1 = x2; y1 = y2; } return std::pair(x, y); } template double get_total_distance(T & shape_path) { return agg::path_length(shape_path); } template placement_finder::placement_finder(feature_impl const& feature, text_placement_info const& placement_info, string_info const& info, DetectorT & detector, box2d const& extent) : detector_(detector), 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_(), collect_extents_(false) { init_string_size(); init_alignment(); } template template void placement_finder::find_point_placements(T & shape_path) { unsigned cmd; double new_x = 0.0; double new_y = 0.0; double old_x = 0.0; double old_y = 0.0; bool first = true; double total_distance = get_total_distance(shape_path); shape_path.rewind(0); if (total_distance == 0) //Point data, not a line { double x, y; shape_path.vertex(&x,&y); find_point_placement(x, y); return; } int num_labels = 1; if (p.label_spacing > 0) num_labels = static_cast (floor(total_distance / pi.get_actual_label_spacing())); if (p.force_odd_labels && num_labels % 2 == 0) num_labels--; if (num_labels <= 0) num_labels = 1; double distance = 0.0; // distance from last label double spacing = total_distance / num_labels; double target_distance = spacing / 2; // first label should be placed at half the spacing while (!agg::is_stop(cmd = shape_path.vertex(&new_x,&new_y))) //For each node in the shape { if (first || agg::is_move_to(cmd)) //Don't do any processing if it is the first node { first = false; } else { //Add the length of this segment to the total we have saved up double segment_length = std::sqrt(std::pow(old_x-new_x,2) + std::pow(old_y-new_y,2)); //Pythagoras distance += segment_length; //While we have enough distance to place text in while (distance > target_distance) { //Try place at the specified place double new_weight = (segment_length - (distance - target_distance))/segment_length; 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. } } old_x = new_x; old_y = new_y; } } template void placement_finder::init_string_size() { // Get total string size 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 string_height_ -= first_line_space_; //First line is a bit smaller } template void placement_finder::find_line_breaks() { if (!line_sizes_.empty()) return; bool first_line = true; // check if we need to wrap the string 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) ; } else { wrap_at = p.wrap_width; } } // work out where our line breaks need to be and the resultant width to the 'wrapped' string if ((wrap_at < string_width_) || info_.has_line_breaks()) { 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 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 and word_width include char width + spacing, but not the spacing after the last char for (unsigned int ii = 0; ii < info_.num_characters(); ii++) { char_info const& ci = info_.at(ii); unsigned c = ci.c; if ((c == ci.format->wrap_char) || (c == '\n')) { 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; //Current one is included in last_wrap_char_width 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); //TODO: I think this calculation could be wrong if height changes for the first word in the second line 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 > wrap_at && !ci.format->wrap_before) || ((line_width + last_wrap_char_width + word_width) > wrap_at && ci.format->wrap_before)) ) ) { add_line(line_width, line_height, first_line); line_breaks_.push_back(last_wrap_char_pos); line_width = 0.0; line_height = 0.0; last_wrap_char_width = 0; //Wrap char supressed first_line = false; } } line_width += last_wrap_char_width + word_width; line_height = std::max(line_height, word_height); add_line(line_width, line_height, first_line); } else { //No linebreaks line_sizes_.push_back(std::make_pair(string_width_, string_height_)); } line_breaks_.push_back(info_.num_characters()); } template void placement_finder::add_line(double width, double height, bool first_line) { if (first_line) height -= first_line_space_; string_width_ = std::max(string_width_, width); //Total width is the longest line string_height_ += height; line_sizes_.push_back(std::make_pair(width, height)); } template void placement_finder::init_alignment() { valign_ = p.valign; if (valign_ == V_AUTO) { if (p.displacement.second > 0.0) { valign_ = V_BOTTOM; } else if (p.displacement.second < 0.0) { valign_ = V_TOP; } else { valign_ = V_MIDDLE; } } halign_ = p.halign; if (halign_ == H_AUTO) { if (p.displacement.first > 0.0) { halign_ = H_RIGHT; } else if (p.displacement.first < 0.0) { halign_ = H_LEFT; } else { halign_ = H_MIDDLE; } } jalign_ = p.jalign; if (jalign_ == J_AUTO) { if (p.displacement.first > 0.0) { jalign_ = J_LEFT; } else if (p.displacement.first < 0.0) { jalign_ = J_RIGHT; } else { jalign_ = J_MIDDLE; } } } template void placement_finder::adjust_position(text_path *current_placement) { // if needed, adjust for desired vertical alignment if (valign_ == V_TOP) { current_placement->center.y -= 0.5 * string_height_; // move center up by 1/2 the total height } else if (valign_ == V_BOTTOM) { current_placement->center.y += 0.5 * string_height_; // move center down by the 1/2 the total height } // set horizontal position to middle of text if (halign_ == H_LEFT) { current_placement->center.x -= 0.5 * string_width_; // move center left by 1/2 the string width } else if (halign_ == H_RIGHT) { current_placement->center.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->center.x += pi.get_scale_factor() * p.displacement.first; current_placement->center.y += pi.get_scale_factor() * p.displacement.second; } template void placement_finder::find_point_placement(double label_x, double label_y, double angle) { find_line_breaks(); double rad = M_PI * angle/180.0; double cosa = std::cos(rad); double sina = std::sin(rad); double x, y; std::unique_ptr current_placement(new text_path(label_x, label_y)); adjust_position(current_placement.get()); // presets for first line unsigned int line_number = 0; unsigned int index_to_wrap_at = line_breaks_[0]; double line_width = line_sizes_[0].first; double line_height = line_sizes_[0].second; /* 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) */ // set for upper left corner of text envelope for the first line, bottom left of first character y = string_height_ / 2.0 - line_height; // RTL text is converted to a mirrored representation in get_string_info() // so we have to fix line break order here if (info_.get_rtl()) y = -y; // adjust for desired justification if (jalign_ == J_LEFT) x = -(string_width_ / 2.0); else if (jalign_ == J_RIGHT) x = (string_width_ / 2.0) - line_width; else /* J_MIDDLE */ 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 < info_.num_characters(); i++) { char_info const& ci = info_.at(i); double cwidth = ci.width + ci.format->character_spacing; if (i == index_to_wrap_at) { index_to_wrap_at = line_breaks_[++line_number]; line_width = line_sizes_[line_number].first; line_height= line_sizes_[line_number].second; if (info_.get_rtl()) { y += line_height; } else { y -= line_height; // move position down to line start } // reset to begining of line position if (jalign_ == J_LEFT) x = -(string_width_ / 2.0); else if (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 dx = x * cosa - y*sina; double dy = x * sina + y*cosa; current_placement->add_node(&ci, dx, dy, rad); // compute the Bounding Box for each character and test for: // overlap, minimum distance or edge avoidance - exit if condition occurs box2d e; /*x axis: left to right, y axis: top to bottom (negative values higher)*/ e.init(current_placement->center.x + dx, // Bottom Left current_placement->center.y - dy - ci.ymin, // ymin usually <0 current_placement->center.x + dx + ci.width, // Top Right current_placement->center.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, pi.get_actual_minimum_distance()))) { return; } // if avoid_edges test dimensions contains e if (p.avoid_edges && !dimensions_.contains(e)) { return; } if (p.minimum_padding > 0) { box2d epad = e; epad.pad(pi.get_actual_minimum_padding()); if (!dimensions_.contains(epad)) { return; } } c_envelopes.push(e); // add character's envelope to temp storage } x += cwidth; // move position to next character } // check the placement of any additional envelopes if (!p.allow_overlap && !additional_boxes_.empty()) { BOOST_FOREACH(box2d const& box, additional_boxes_) { box2d pt(box.minx() + current_placement->center.x, box.miny() + current_placement->center.y, box.maxx() + current_placement->center.x, box.maxy() + current_placement->center.y); // abort the whole placement if the additional envelopes can't be placed. if (!detector_.has_point_placement(pt, p.minimum_distance)) return; c_envelopes.push(pt); } } // since there was no early exit, add the character envelopes to the placements' envelopes while (!c_envelopes.empty()) { envelopes_.push(c_envelopes.front()); c_envelopes.pop(); } placements_.push_back(current_placement.release()); } template template void placement_finder::find_line_placements(PathT & shape_path) { #ifdef MAPNIK_LOG if (! line_sizes_.empty()) { MAPNIK_LOG_WARN(placement_finder) << "Internal error. Text contains line breaks, but line placement is used. Please file a bug report!"; } #endif unsigned cmd; double new_x = 0.0; double new_y = 0.0; double old_x = 0.0; double old_y = 0.0; bool first = true; //Pre-Cache all the path_positions and path_distances //This stops the PathT from having to do multiple re-projections if we need to reposition ourself // and lets us know how many points are in the shape. std::vector path_positions; std::vector path_distances; // distance from node x-1 to node x double total_distance = 0; shape_path.rewind(0); while (!agg::is_stop(cmd = shape_path.vertex(&new_x,&new_y))) //For each node in the shape { if (!first && agg::is_line_to(cmd)) { double dx = old_x - new_x; double dy = old_y - new_y; double distance = std::sqrt(dx*dx + dy*dy); total_distance += distance; path_distances.push_back(distance); } else { path_distances.push_back(0); } first = false; path_positions.push_back(vertex2d(new_x, new_y, cmd)); old_x = new_x; old_y = new_y; } //Now path_positions is full and total_distance is correct //shape_path shouldn't be used from here // Ensure lines have a minimum length. if (total_distance < p.minimum_path_length) return; double distance = 0.0; double displacement = p.displacement.second; // displace by dy //Calculate a target_distance that will place the labels centered evenly rather than offset from the start of the linestring if (total_distance < string_width_) //Can't place any strings 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 / (pi.get_actual_label_spacing() + string_width_))); 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 //Calculate or read out the tolerance double tolerance_delta, tolerance; if (p.label_position_tolerance > 0) { tolerance = p.label_position_tolerance; tolerance_delta = std::max ( 1.0, p.label_position_tolerance/100.0 ); } else { tolerance = spacing/2.0; tolerance_delta = std::max ( 1.0, spacing/100.0 ); } first = true; for (unsigned index = 0; index < path_positions.size(); index++) //For each node in the shape { cmd = path_positions[index].cmd; new_x = path_positions[index].x; new_y = path_positions[index].y; if (first || agg::is_move_to(cmd)) //Don't do any processing if it is the first node { first = false; } else { //Add the length of this segment to the total we have saved up double segment_length = path_distances[index]; distance += segment_length; //While we have enough distance to place text in while (distance > target_distance) { for (double diff = 0; diff < tolerance; diff += tolerance_delta) { for(int dir = -1; dir < 2; dir+=2) //-1, +1 { //Record details for the start of the string placement int orientation = 0; std::unique_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() == nullptr) continue; //Apply displacement //NOTE: The text is centered on the line in get_placement_offset, so we are offsetting from there if (displacement != 0) { //Average the angle of all characters and then offset them all by that angle double anglesum = 0; for (unsigned i = 0; i < current_placement->nodes_.size(); i++) { double angle = current_placement->nodes_[i].angle; //Normalize angle in range -PI ... PI while (angle > M_PI) { angle -= 2*M_PI; } anglesum += angle; } anglesum /= current_placement->nodes_.size(); //Now it is angle average double cosa = orientation * std::cos(anglesum); double sina = orientation * std::sin(anglesum); //Offset all the characters by this angle for (unsigned i = 0; i < current_placement->nodes_.size(); i++) { current_placement->nodes_[i].pos.x -= pi.get_scale_factor() * displacement * sina; current_placement->nodes_[i].pos.y += pi.get_scale_factor() * displacement * cosa; } } bool status = test_placement(current_placement, orientation); if (status) //We have successfully placed one { placements_.push_back(current_placement.release()); update_detector(); //Totally break out of the loops diff = tolerance; break; } else { //If we've failed to place, remove all the envelopes we've added up while (!envelopes_.empty()) envelopes_.pop(); } //Don't need to loop twice when diff = 0 if (diff == 0) break; } } 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. } } old_x = new_x; old_y = new_y; } } template std::unique_ptr placement_finder::get_placement_offset(std::vector const& path_positions, std::vector const& 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) { index--; distance += path_distances[index]; } if (index <= 1 && distance < 0) //We've gone off the start, fail out return std::unique_ptr(nullptr); //Same thing, checking if we go off the end while (index < path_distances.size() && distance > path_distances[index]) { distance -= path_distances[index]; index++; } if (index >= path_distances.size()) return std::unique_ptr(nullptr); //Keep track of the initial index,distance incase we need to re-call get_placement_offset const unsigned initial_index = index; const double initial_distance = distance; double old_x = path_positions[index-1].x; double old_y = path_positions[index-1].y; double new_x = path_positions[index].x; double new_y = path_positions[index].y; double dx = new_x - old_x; double dy = new_y - old_y; double segment_length = path_distances[index]; if (segment_length == 0) { // Not allowed to place across on 0 length segments or discontinuities return std::unique_ptr(nullptr); } std::unique_ptr current_placement( new text_path((old_x + dx*distance/segment_length), (old_y + dy*distance/segment_length) ) ); double angle = atan2(-dy, dx); bool orientation_forced = (orientation != 0); // Whether the orientation was set by the caller if (!orientation_forced) orientation = (angle > 0.55*M_PI || angle < -0.45*M_PI) ? -1 : 1; unsigned upside_down_char_count = 0; //Count of characters that are placed upside down. for (unsigned i = 0; i < info_.num_characters(); ++i) { // grab the next character according to the orientation char_info const &ci = orientation > 0 ? info_.at(i) : info_.at(info_.num_characters() - i - 1); double cwidth = ci.width + ci.format->character_spacing; double last_character_angle = angle; //Coordinates this character will start at if (segment_length == 0) { // Not allowed to place across on 0 length segments or discontinuities return std::unique_ptr(nullptr); } double start_x = old_x + dx*distance/segment_length; double start_y = old_y + dy*distance/segment_length; //Coordinates this character ends at, calculated below double end_x = 0; double end_y = 0; if (segment_length - distance >= cwidth) { //if the distance remaining in this segment is enough, we just go further along the segment distance += cwidth; end_x = old_x + dx*distance/segment_length; end_y = old_y + dy*distance/segment_length; } else { //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 ci.width away do { old_x = new_x; old_y = new_y; index++; if (index >= path_positions.size()) //Bail out if we run off the end of the shape { //MAPNIK_LOG_ERROR(placement_finder) << "FAIL: Out of space"; return std::unique_ptr(nullptr); } new_x = path_positions[index].x; new_y = path_positions[index].y; dx = new_x - old_x; dy = new_y - old_y; segment_length = path_distances[index]; } while (std::sqrt(std::pow(start_x - new_x, 2) + std::pow(start_y - new_y, 2)) < cwidth); //Distance from start_ to new_ //Calculate the position to place the end of the character on find_line_circle_intersection( start_x, start_y, cwidth, old_x, old_y, new_x, new_y, end_x, end_y); //results are stored in end_x, end_y //Need to calculate distance on the new segment distance = std::sqrt(std::pow(old_x - end_x, 2) + std::pow(old_y - end_y, 2)); } //Calculate angle from the start of the character to the end based on start_/end_ position angle = fast_atan2(start_y-end_y, end_x-start_x); //Test last_character_angle vs angle // since our rendering angle has changed then check against our // max allowable angle change. double angle_delta = last_character_angle - angle; // normalise between -180 and 180 while (angle_delta > M_PI) angle_delta -= 2*M_PI; while (angle_delta < -M_PI) angle_delta += 2*M_PI; if (p.max_char_angle_delta > 0 && std::fabs(angle_delta) > p.max_char_angle_delta) { //MAPNIK_LOG_ERROR(placement_finder) << "FAIL: Too Bendy!"; return std::unique_ptr(nullptr); } double render_angle = angle; double cosa = fast_cos(angle); double sina = fast_sin(angle); double render_x = start_x; double render_y = start_y; //Center the text on the line double char_height = ci.avg_height; render_x += char_height/2.0*sina; render_y += char_height/2.0*cosa; if (orientation < 0) { // rotate in place render_x += cwidth*cosa - char_height*sina; render_y -= cwidth*sina + char_height*cosa; render_angle += M_PI; } current_placement->add_node(&ci, render_x - current_placement->center.x, -render_y + current_placement->center.y, render_angle); //Normalise to 0 <= angle < 2PI while (render_angle >= 2*M_PI) render_angle -= 2*M_PI; while (render_angle < 0) render_angle += 2*M_PI; if (render_angle > M_PI/2 && render_angle < 1.5*M_PI) upside_down_char_count++; } //If we placed too many characters upside down 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(path_positions, path_distances, orientation, initial_index, initial_distance); } else { //Otherwise we have failed to find a placement //MAPNIK_LOG_ERROR(placement_finder) << "FAIL: Double upside-down!"; return std::unique_ptr(nullptr); } } return std::move(current_placement); } template bool placement_finder::test_placement(std::unique_ptr const& current_placement, int orientation) { //Create and test envelopes bool status = true; for (unsigned i = 0; i < info_.num_characters(); ++i) { //TODO: I think this can be simplified by taking the char_info from vertex() but this needs to be carefully tested! // grab the next character according to the orientation char_info const& ci = orientation > 0 ? info_.at(i) : info_.at(info_.num_characters() - i - 1); double cwidth = ci.width + ci.format->character_spacing; char_info_ptr c; double x, y, angle; current_placement->vertex(c, x, y, angle); x = current_placement->center.x + x; y = current_placement->center.y - y; double sina = fast_sin(angle); double cosa = fast_cos(angle); if (orientation < 0) { // rotate in place x += cwidth * cosa - string_height_ * sina; y -= cwidth * sina + string_height_ * cosa; angle += M_PI; //sin(x+PI) = -sin(x) sina = -sina; cosa = -cosa; } box2d e(x, y, x + cwidth*cosa, y - cwidth*sina); // put four corners of the letter into envelope e.expand_to_include(x - ci.height()*sina, y - ci.height()*cosa); e.expand_to_include(x + (cwidth*cosa - ci.height()*sina), y - (cwidth*sina + ci.height()*cosa)); if (!detector_.extent().intersects(e) || (!p.allow_overlap && !detector_.has_placement(e, info_.get_string(), pi.get_actual_minimum_distance()) ) ) { //MAPNIK_LOG_ERROR(placement_finder) << "No Intersects:" << !dimensions_.intersects(e) << ": " << e << " @ " << dimensions_; //MAPNIK_LOG_ERROR(placement_finder) << "No Placements:" << !detector_.has_placement(e, info.get_string(), p.minimum_distance); status = false; break; } if (p.avoid_edges && !dimensions_.contains(e)) { //MAPNIK_LOG_ERROR(placement_finder) << "Fail avoid edges"; status = false; break; } if (p.minimum_padding > 0) { box2d epad = e; epad.pad(pi.get_actual_minimum_padding()); if (!dimensions_.contains(epad)) { status = false; break; } } envelopes_.push(e); } current_placement->rewind(); return status; } template void placement_finder::find_line_circle_intersection( double cx, double cy, double radius, double x1, double y1, double x2, double y2, double & ix, double & iy) { 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 <= 0.0000001 || 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; } } template void placement_finder::update_detector() { if (collect_extents_) extents_.init(0,0,0,0); // add the bboxes to the detector and remove from the placement while (!envelopes_.empty()) { box2d e = envelopes_.front(); detector_.insert(e, info_.get_string()); envelopes_.pop(); if (collect_extents_) { extents_.expand_to_include(e); } } } template void placement_finder::clear_placements() { placements_.clear(); while (!envelopes_.empty()) envelopes_.pop(); } template class placement_finder; template void placement_finder::find_point_placements(ClippedPathType &); template void placement_finder::find_line_placements(ClippedPathType &); template void placement_finder::find_point_placements(PathType &); template void placement_finder::find_line_placements(PathType &); } // namespace