Parse and support placement of multiple text layouts within a single text symbolizer.

This commit is contained in:
Jordan Hollinger 2014-01-30 06:31:47 -05:00
parent 269b038147
commit 6aa25090c0
9 changed files with 624 additions and 258 deletions

View File

@ -0,0 +1,59 @@
/*****************************************************************************
*
* 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
*
*****************************************************************************/
#ifndef FORMATTING_OFFSET_HPP
#define FORMATTING_OFFSET_HPP
#include <mapnik/text/formatting/base.hpp>
#include <mapnik/text/text_properties.hpp>
#include <boost/optional.hpp>
namespace mapnik {
namespace formatting {
class MAPNIK_DECL layout_node: public node {
public:
void to_xml(boost::property_tree::ptree &xml) const;
static node_ptr from_xml(xml_node const& xml);
virtual void apply(char_properties_ptr p, feature_impl const& feature, text_layout &output) const;
virtual void add_expressions(expression_set &output) const;
void set_child(node_ptr child);
node_ptr get_child() const;
boost::optional<double> dx;
boost::optional<double> dy;
boost::optional<horizontal_alignment_e> halign;
boost::optional<vertical_alignment_e> valign;
boost::optional<justify_alignment_e> jalign;
boost::optional<double> text_ratio;
boost::optional<double> wrap_width;
boost::optional<bool> wrap_before;
boost::optional<bool> rotate_displacement;
boost::optional<expression_ptr> orientation;
private:
node_ptr child_;
};
} //ns formatting
} //ns mapnik
#endif // FORMATTING_OFFSET_HPP

View File

@ -30,6 +30,7 @@
#include <mapnik/text/harfbuzz_shaper.hpp>
#include <mapnik/text/icu_shaper.hpp>
#include <mapnik/text/dummy_shaper.hpp>
#include <mapnik/text/rotation.hpp>
//stl
#include <vector>
@ -38,13 +39,17 @@
namespace mapnik
{
typedef std::shared_ptr<text_layout> text_layout_ptr;
typedef std::vector<text_layout_ptr> text_layout_vector;
class text_layout
{
public:
typedef std::vector<text_line> line_vector;
typedef line_vector::const_iterator const_iterator;
typedef text_layout_vector::const_iterator child_iterator;
typedef harfbuzz_shaper shaper_type;
text_layout(face_manager_freetype & font_manager, double scale_factor);
text_layout(face_manager_freetype & font_manager, double scale_factor, text_layout_properties_ptr properties);
/** Adds a new text part. Call this function repeatedly to build the complete text. */
void add_text(mapnik::value_unicode_string const& str, char_properties_ptr format);
@ -53,7 +58,7 @@ public:
mapnik::value_unicode_string const& text() const;
/** Processes the text into a list of glyphs, performing RTL/LTR handling, shaping and line breaking. */
void layout(double wrap_width, unsigned text_ratio, bool wrap_before);
void layout();
/** Clear all data stored in this object. The object's state is the same as directly after construction. */
void clear();
@ -81,11 +86,29 @@ public:
// Returns the number of glyphs so memory can be preallocated.
inline unsigned glyphs_count() const { return glyphs_count_;}
void add_child(text_layout_ptr child_layout);
inline const text_layout_vector &get_child_layouts() const { return child_layout_list_; }
inline face_manager<freetype_engine> &get_font_manager() const { return font_manager_; }
inline double get_scale_factor() const { return scale_factor_; }
inline text_layout_properties_ptr get_layout_properties() const { return properties_; }
inline rotation const& orientation() const { return orientation_; }
inline pixel_position const& displacement() const { return displacement_; }
inline box2d<double> const& bounds() const { return bounds_; }
pixel_position alignment_offset() const;
double jalign_offset(double line_width) const;
void init_orientation(feature_impl const& feature);
private:
void break_line(text_line & line, double wrap_width, unsigned text_ratio, bool wrap_before);
void shape_text(text_line & line);
void add_line(text_line & line);
void clear_cluster_widths(unsigned first, unsigned last);
void init_alignment();
//input
face_manager_freetype &font_manager_;
@ -103,7 +126,60 @@ private:
//output
line_vector lines_;
//text layout properties
text_layout_properties_ptr properties_;
//alignments
vertical_alignment_e valign_;
horizontal_alignment_e halign_;
justify_alignment_e jalign_;
// Precalculated values for maximum performance
rotation orientation_;
pixel_position displacement_;
box2d<double> bounds_;
//children
text_layout_vector child_layout_list_;
};
class layout_container
{
public:
layout_container() : glyphs_count_(0), line_count_(0) {}
void add(text_layout_ptr layout);
void clear();
void layout();
inline size_t size() const { return layouts_.size(); }
inline text_layout_vector::const_iterator begin() const { return layouts_.begin(); }
inline text_layout_vector::const_iterator end() const { return layouts_.end(); }
inline mapnik::value_unicode_string const& text() const { return text_; }
inline unsigned glyphs_count() const { return glyphs_count_; }
inline unsigned line_count() const { return line_count_; }
inline box2d<double> const& bounds() const { return bounds_; }
inline double width() const { return bounds_.width(); }
inline double height() const { return bounds_.height(); }
private:
text_layout_vector layouts_;
mapnik::value_unicode_string text_;
unsigned glyphs_count_;
unsigned line_count_;
box2d<double> bounds_;
};
}
#endif // TEXT_LAYOUT_HPP

View File

@ -29,6 +29,7 @@
#include <mapnik/text/placements/base.hpp>
#include <mapnik/text/placements_list.hpp>
#include <mapnik/text/rotation.hpp>
#include <mapnik/text/vertex_cache.hpp>
#include <mapnik/noncopyable.hpp>
namespace mapnik
@ -62,11 +63,12 @@ public:
void set_marker(marker_info_ptr m, box2d<double> box, bool marker_unlocked, pixel_position const& marker_displacement);
private:
void init_alignment();
pixel_position alignment_offset() const;
double jalign_offset(double line_width) const;
//bool find_point_placement(pixel_position const& pos, bool add_marker, text_layout const& layout, placements_list &placements, std::vector<box2d<double> > &bboxes);
bool single_line_placement(vertex_cache &pp, text_upright_e orientation);
//bool single_line_placement(vertex_cache &pp, text_upright_e real_orientation, text_layout const& layout,
// placements_list &placements, std::vector<box2d<double> > &bboxes,
// int &glyph_count, int &upside_down_glyph_count);
bool line_layout_placement();
/** Moves dx pixels but makes sure not to fall of the end. */
void path_move_dx(vertex_cache &pp);
/** Normalize angle in range [-pi, +pi]. */
@ -80,23 +82,16 @@ private:
/** Maps upright==auto, left_only and right_only to left,right to simplify processing.
angle = angle of at start of line (to estimate best option for upright==auto) */
text_upright_e simplify_upright(text_upright_e upright, double angle) const;
box2d<double> get_bbox(glyph_info const& glyph, pixel_position const& pos, rotation const& rot);
box2d<double> get_bbox(text_layout const& layout, glyph_info const& glyph, pixel_position const& pos, rotation const& rot);
feature_impl const& feature_;
DetectorType &detector_;
box2d<double> const& extent_;
// Precalculated values for maximum performance
rotation orientation_;
text_layout layout_;
text_placement_info_ptr info_;
layout_container layouts_;
bool valid_;
vertical_alignment_e valign_;
/** Horizontal alignment for point placements. */
horizontal_alignment_e halign_point_;
/** Horizontal alignment for line placements. */
horizontal_alignment_e halign_line_;
justify_alignment_e jalign_;
double scale_factor_;
face_manager_freetype &font_manager_;
placements_list placements_;

View File

@ -15,8 +15,8 @@ struct rotation
void init(double angle) { sin = std::sin(angle); cos = std::cos(angle); }
double sin;
double cos;
rotation operator~() { return rotation(sin, -cos); }
rotation operator!() { return rotation(-sin, cos); }
rotation operator~() const { return rotation(sin, -cos); }
rotation operator!() const { return rotation(-sin, cos); }
};
}

View File

@ -227,6 +227,7 @@ source = Split(
text/formatting/list.cpp
text/formatting/text.cpp
text/formatting/format.cpp
text/formatting/layout.cpp
text/formatting/registry.cpp
text/placements/registry.cpp
text/placements/base.cpp

View File

@ -0,0 +1,126 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2012 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
// mapnik
#include <mapnik/text/text_properties.hpp>
#include <mapnik/text/layout.hpp>
#include <mapnik/debug.hpp>
#include <mapnik/feature.hpp>
#include <mapnik/ptree_helpers.hpp>
#include <mapnik/expression_string.hpp>
#include <mapnik/text/formatting/layout.hpp>
#include <mapnik/xml_node.hpp>
#include <mapnik/config_error.hpp>
#include <mapnik/symbolizer.hpp>
// boost
#include <boost/property_tree/ptree.hpp>
namespace mapnik {
namespace formatting {
using boost::property_tree::ptree;
void layout_node::to_xml(ptree &xml) const
{
ptree &new_node = xml.push_back(ptree::value_type("Layout", ptree()))->second;
if (dx) set_attr(new_node, "dx", *dx);
if (dy) set_attr(new_node, "dy", *dy);
if (halign) set_attr(new_node, "horizontal-alignment", *halign);
if (valign) set_attr(new_node, "vertical-alignment", *valign);
if (jalign) set_attr(new_node, "justify-alignment", *jalign);
if (text_ratio) set_attr(new_node, "text-ratio", *text_ratio);
if (wrap_width) set_attr(new_node, "wrap-width", *wrap_width);
if (wrap_before) set_attr(new_node, "wrap-before", *wrap_before);
if (rotate_displacement) set_attr(new_node, "rotate-displacement", *rotate_displacement);
if (orientation) set_attr(new_node, "orientation", to_expression_string(**orientation));
if (child_) child_->to_xml(new_node);
}
node_ptr layout_node::from_xml(xml_node const& xml)
{
layout_node *n = new layout_node();
node_ptr np(n);
node_ptr child = node::from_xml(xml);
n->set_child(child);
n->dx = xml.get_opt_attr<double>("dx");
n->dy = xml.get_opt_attr<double>("dy");
n->halign = xml.get_opt_attr<horizontal_alignment_e>("horizontal-alignment");
n->valign = xml.get_opt_attr<vertical_alignment_e>("vertical-alignment");
n->jalign = xml.get_opt_attr<justify_alignment_e>("justify-alignment");
n->text_ratio = xml.get_opt_attr<double>("text-ratio");
n->wrap_width = xml.get_opt_attr<double>("wrap-width");
n->wrap_before = xml.get_opt_attr<boolean>("wrap-before");
n->rotate_displacement = xml.get_opt_attr<boolean>("rotate-displacement");
n->orientation = xml.get_opt_attr<expression_ptr>("orientation");
return np;
}
void layout_node::apply(char_properties_ptr p, feature_impl const& feature, text_layout &output) const
{
text_layout_properties_ptr new_properties = std::make_shared<text_layout_properties>(*output.get_layout_properties());
if (dx) new_properties->displacement.x = *dx;
if (dy) new_properties->displacement.y = *dy;
if (halign) new_properties->halign = *halign;
if (valign) new_properties->valign = *valign;
if (jalign) new_properties->jalign = *jalign;
if (text_ratio) new_properties->text_ratio = *text_ratio;
if (wrap_width) new_properties->wrap_width = *wrap_width;
if (wrap_before) new_properties->wrap_before = *wrap_before;
if (rotate_displacement) new_properties->rotate_displacement = *rotate_displacement;
if (orientation) new_properties->orientation = *orientation;
// starting a new offset child with the new displacement value
text_layout_ptr child_layout = std::make_shared<text_layout>(output.get_font_manager(), output.get_scale_factor(), new_properties);
child_layout->init_orientation(feature);
// process contained format tree into the child node
if (child_) {
child_->apply(p, feature, *child_layout);
} else {
MAPNIK_LOG_WARN(format) << "Useless layout node: Contains no text";
}
output.add_child(child_layout);
}
void layout_node::set_child(node_ptr child)
{
child_ = child;
}
node_ptr layout_node::get_child() const
{
return child_;
}
void layout_node::add_expressions(expression_set &output) const
{
if (child_) child_->add_expressions(output);
}
} //ns formatting
} //ns mapnik

View File

@ -24,6 +24,7 @@
#include <mapnik/text/formatting/text.hpp>
#include <mapnik/text/formatting/format.hpp>
#include <mapnik/text/formatting/expression_format.hpp>
#include <mapnik/text/formatting/layout.hpp>
#include <mapnik/xml_node.hpp>
#include <mapnik/config_error.hpp>
@ -37,6 +38,7 @@ registry::registry()
register_name("<xmltext>", &text_node::from_xml);
register_name("Format", &format_node::from_xml);
register_name("ExpressionFormat", &expression_format::from_xml);
register_name("Layout", &layout_node::from_xml);
}
void registry::register_name(std::string const& name, from_xml_function_ptr ptr, bool overwrite)

View File

@ -22,6 +22,7 @@
#include <mapnik/text/layout.hpp>
#include <mapnik/text/text_properties.hpp>
#include <mapnik/expression_evaluator.hpp>
#include <mapnik/debug.hpp>
// ICU
@ -30,7 +31,29 @@
namespace mapnik
{
text_layout::text_layout(face_manager_freetype & font_manager, double scale_factor)
// Output is centered around (0,0)
static void rotated_box2d(box2d<double> & box, rotation const& rot, pixel_position const& center, double width, double height)
{
double half_width, half_height;
if (rot.sin == 0 && rot.cos == 1.)
{
half_width = width / 2.;
half_height = height / 2.;
}
else
{
half_width = (width * rot.cos + height * rot.sin) /2.;
half_height = (width * rot.sin + height * rot.cos) /2.;
}
box.init(center.x - half_width, center.y - half_height, center.x + half_width, center.y + half_height);
}
pixel_position pixel_position::rotate(rotation const& rot) const
{
return pixel_position(x * rot.cos - y * rot.sin, x * rot.sin + y * rot.cos);
}
text_layout::text_layout(face_manager_freetype & font_manager, double scale_factor, text_layout_properties_ptr properties)
: font_manager_(font_manager),
scale_factor_(scale_factor),
itemizer_(),
@ -38,7 +61,8 @@ text_layout::text_layout(face_manager_freetype & font_manager, double scale_fact
width_(0.0),
height_(0.0),
glyphs_count_(0),
lines_()
lines_(),
properties_(properties)
{
}
@ -47,20 +71,34 @@ void text_layout::add_text(mapnik::value_unicode_string const& str, char_propert
itemizer_.add_text(str, format);
}
void text_layout::add_child(text_layout_ptr child_layout)
{
child_layout_list_.push_back(child_layout);
}
mapnik::value_unicode_string const& text_layout::text() const
{
return itemizer_.text();
}
void text_layout::layout(double wrap_width, unsigned text_ratio, bool wrap_before)
void text_layout::layout()
{
unsigned num_lines = itemizer_.num_lines();
for (unsigned i = 0; i < num_lines; ++i)
{
std::pair<unsigned, unsigned> line_limits = itemizer_.line(i);
text_line line(line_limits.first, line_limits.second);
break_line(line, wrap_width, text_ratio, wrap_before); //Break line if neccessary
//Break line if neccessary
break_line(line, properties_->wrap_width * scale_factor_, properties_->text_ratio, properties_->wrap_before);
}
init_alignment();
/* Find text origin. */
displacement_ = scale_factor_ * properties_->displacement + alignment_offset();
if (properties_->rotate_displacement) displacement_ = displacement_.rotate(!orientation_);
/* Find layout bounds, expanded for rotation */
rotated_box2d(bounds_, orientation_, displacement_, width_, height_);
}
/* In the Unicode string characters are always stored in logical order.
@ -189,6 +227,7 @@ void text_layout::clear()
width_map_.clear();
width_ = 0.;
height_ = 0.;
child_layout_list_.clear();
}
void text_layout::shape_text(text_line & line)
@ -196,5 +235,155 @@ void text_layout::shape_text(text_line & line)
shaper_type::shape_text(line, itemizer_, width_map_, font_manager_, scale_factor_);
}
void text_layout::init_orientation(feature_impl const& feature)
{
if (properties_->orientation)
{
// https://github.com/mapnik/mapnik/issues/1352
mapnik::evaluate<feature_impl, value_type> evaluator(feature);
orientation_.init(
boost::apply_visitor(
evaluator,
*(properties_->orientation)).to_double() * M_PI / 180.0);
}
else
{
orientation_.reset();
}
}
void text_layout::init_alignment()
{
text_layout_properties const& p = *(properties_);
valign_ = p.valign;
if (valign_ == V_AUTO)
{
if (p.displacement.y > 0.0)
{
valign_ = V_BOTTOM;
}
else if (p.displacement.y < 0.0)
{
valign_ = V_TOP;
}
else
{
valign_ = V_MIDDLE;
}
}
halign_ = p.halign;
if (halign_ == H_AUTO)
{
if (p.displacement.x > 0.0)
{
halign_ = H_RIGHT;
}
else if (p.displacement.x < 0.0)
{
halign_ = H_LEFT;
}
else
{
halign_ = 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;
}
}
}
pixel_position text_layout::alignment_offset() const
{
pixel_position result(0,0);
// if needed, adjust for desired vertical alignment
if (valign_ == V_TOP)
{
result.y = -0.5 * height(); // move center up by 1/2 the total height
}
else if (valign_ == V_BOTTOM)
{
result.y = 0.5 * height(); // move center down by the 1/2 the total height
}
// set horizontal position to middle of text
if (halign_ == H_LEFT)
{
result.x = -0.5 * width(); // move center left by 1/2 the string width
}
else if (halign_ == H_RIGHT)
{
result.x = 0.5 * width(); // move center right by 1/2 the string width
}
return result;
}
double text_layout::jalign_offset(double line_width) const
{
if (jalign_ == J_MIDDLE) return -(line_width / 2.0);
if (jalign_ == J_LEFT) return -(width() / 2.0);
if (jalign_ == J_RIGHT) return (width() / 2.0) - line_width;
return 0;
}
void layout_container::add(text_layout_ptr layout)
{
text_ += layout->text();
layouts_.push_back(layout);
for (text_layout_ptr const& child_layout : layout->get_child_layouts())
{
add(child_layout);
}
}
void layout_container::layout()
{
bounds_.init(0,0,0,0);
glyphs_count_ = 0;
line_count_ = 0;
bool first = true;
for (text_layout_ptr const& layout : layouts_)
{
layout->layout();
glyphs_count_ += layout->glyphs_count();
line_count_ += layout->num_lines();
if (first)
{
bounds_ = layout->bounds();
first = false;
}
else
{
bounds_.expand_to_include(layout->bounds());
}
}
}
void layout_container::clear()
{
layouts_.clear();
text_.remove();
bounds_.init(0,0,0,0);
glyphs_count_ = 0;
line_count_ = 0;
}
} //ns mapnik

View File

@ -107,19 +107,6 @@ private:
};
// 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.);
}
pixel_position pixel_position::rotate(rotation const& rot) const
{
return pixel_position(x * rot.cos - y * rot.sin, x * rot.sin + y * rot.cos);
}
placement_finder::placement_finder(feature_impl const& feature,
DetectorType &detector,
box2d<double> const& extent,
@ -129,10 +116,10 @@ placement_finder::placement_finder(feature_impl const& feature,
: feature_(feature),
detector_(detector),
extent_(extent),
layout_(font_manager, scale_factor),
info_(placement_info),
valid_(true),
scale_factor_(scale_factor),
font_manager_(font_manager),
placements_(),
has_marker_(false),
marker_(),
@ -153,173 +140,113 @@ bool placement_finder::next_position()
return false;
}
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);
text_layout_ptr layout = std::make_shared<text_layout>(font_manager_, scale_factor_, info_->properties.layout_defaults);
layout->init_orientation(feature_);
info_->properties.process(*layout, feature_);
layouts_.clear();
layouts_.add(layout);
layouts_.layout();
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()
text_upright_e placement_finder::simplify_upright(text_upright_e upright, double angle) const
{
text_layout_properties const& p = *(info_->properties.layout_defaults);
valign_ = p.valign;
if (valign_ == V_AUTO)
if (upright == UPRIGHT_AUTO)
{
if (p.displacement.y > 0.0)
{
valign_ = V_BOTTOM;
}
else if (p.displacement.y < 0.0)
{
valign_ = V_TOP;
}
else
{
valign_ = V_MIDDLE;
}
return (std::fabs(normalize_angle(angle)) > 0.5*M_PI) ? UPRIGHT_LEFT : UPRIGHT_RIGHT;
}
halign_point_ = p.halign;
halign_line_ = p.halign;
if (halign_point_ == H_AUTO)
if (upright == UPRIGHT_LEFT_ONLY)
{
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;
}
return UPRIGHT_LEFT;
}
jalign_ = p.jalign;
if (jalign_ == J_AUTO)
if (upright == UPRIGHT_RIGHT_ONLY)
{
if (p.displacement.x > 0.0)
{
jalign_ = J_LEFT;
}
else if (p.displacement.x < 0.0)
{
jalign_ = J_RIGHT;
}
else
{
jalign_ = J_MIDDLE;
}
return UPRIGHT_RIGHT;
}
}
pixel_position placement_finder::alignment_offset() const //TODO
{
pixel_position result(0,0);
// 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
}
// 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;
}
double placement_finder::jalign_offset(double line_width) const //TODO
{
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;
return upright;
}
bool placement_finder::find_point_placement(pixel_position const& pos)
{
glyph_positions_ptr glyphs = std::make_shared<glyph_positions>();
std::vector<box2d<double> > bboxes;
/* 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);
glyphs->reserve(layouts_.glyphs_count());
bboxes.reserve(layouts_.size());
/* 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_)
bool base_point_set = false;
for (auto const& layout_ptr : layouts_)
{
y -= line.height(); //Automatically handles first line differently
x = jalign_offset(line.width());
text_layout const& layout = *layout_ptr;
rotation const& orientation = layout.orientation();
for (auto const& glyph : line)
/* Find text origin. */
pixel_position layout_center = pos + layout.displacement();
if (!base_point_set)
{
// 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)
glyphs->set_base_point(layout_center);
base_point_set = true;
}
box2d<double> bbox = layout.bounds();
bbox.re_center(layout_center.x, layout_center.y);
/* For point placements it is faster to just check the bounding box. */
if (collision(bbox)) return false;
if (layout.num_lines()) bboxes.push_back(std::move(bbox));
pixel_position layout_offset = layout_center - glyphs->get_base_point();
layout_offset.y = -layout_offset.y;
/* 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;
for ( auto const& line : layout)
{
y -= line.height(); //Automatically handles first line differently
x = layout.jalign_offset(line.width());
for (auto const& glyph : line)
{
//Only advance if glyph is not part of a multiple glyph sequence
x += glyph.width + glyph.format->character_spacing * scale_factor_;
// place the character relative to the center of the string envelope
glyphs->push_back(glyph, (pixel_position(x, y).rotate(orientation)) + layout_offset, orientation);
if (glyph.width)
{
//Only advance if glyph is not part of a multiple glyph sequence
x += glyph.width + glyph.format->character_spacing * scale_factor_;
}
}
}
}
/* add_marker first checks for collision and then updates the detector.*/
if (has_marker_ && !add_marker(glyphs, pos)) return false;
for (box2d<double> const& bbox : bboxes)
{
detector_.insert(bbox, layouts_.text());
}
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
if (!layouts_.line_count()) return true; //TODO
vertex_cache pp(path);
bool success = false;
@ -339,13 +266,13 @@ bool placement_finder::find_line_placements(T & path, bool points)
||
(pp.length() <= 0.001) /* Clipping removed whole geometry */
||
(pp.length() < layout_.width()))
(pp.length() < layouts_.width()))
{
continue;
}
}
double spacing = get_spacing(pp.length(), points ? 0. : layout_.width());
double spacing = get_spacing(pp.length(), points ? 0. : layouts_.width());
horizontal_alignment_e halign = info_->properties.layout_defaults->halign;
if (halign == H_LEFT)
@ -381,120 +308,111 @@ bool placement_finder::find_line_placements(T & path, bool points)
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);
vertex_cache::scoped_state begin(pp);
text_upright_e real_orientation = simplify_upright(orientation, pp.angle());
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;
glyph_positions_ptr glyphs = std::make_shared<glyph_positions>();
std::vector<box2d<double> > bboxes;
glyphs->reserve(layouts_.glyphs_count());
bboxes.reserve(layouts_.glyphs_count());
text_upright_e real_orientation = simplify_upright(orientation, pp.angle());
unsigned upside_down_glyph_count = 0;
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_)
for (auto const& layout_ptr : layouts_)
{
//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
text_layout const& layout = *layout_ptr;
pixel_position align_offset = layout.alignment_offset();
pixel_position const& layout_displacement = layout.get_layout_properties()->displacement;
double sign = (real_orientation == UPRIGHT_LEFT) ? -1 : 1;
double offset = align_offset.y + layout_displacement.y * scale_factor_ + sign * layout.height()/2.;
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.;
for (auto const& glyph : line)
for (auto const& line : layout)
{
if (current_cluster != static_cast<int>(glyph.char_index))
//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 * layout.jalign_offset(line.width()) - align_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.;
for (auto const& glyph : line)
{
if (!off_pp.move(sign * (layout_.cluster_width(current_cluster) + last_glyph_spacing)))
if (current_cluster != static_cast<int>(glyph.char_index))
{
return false;
if (!off_pp.move(sign * (layout.cluster_width(current_cluster) + last_glyph_spacing)))
{
return false;
}
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;
}
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;
cluster_offset.x += rot.cos * glyph.width;
cluster_offset.y -= rot.sin * glyph.width;
box2d<double> bbox = get_bbox(layout, glyph, pos, rot);
if (collision(bbox)) return false;
bboxes.push_back(std::move(bbox));
glyphs->push_back(glyph, pos, rot);
}
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;
cluster_offset.x += rot.cos * glyph.width;
cluster_offset.y -= rot.sin * glyph.width;
box2d<double> bbox = get_bbox(glyph, pos, rot);
if (collision(bbox)) return false;
bboxes.push_back(bbox);
glyphs->push_back(glyph, pos, rot);
//See comment above
offset -= sign * line.height()/2;
}
//See comment above
offset -= sign * line.height()/2;
}
if (upside_down_glyph_count > (layout_.text().length()/2))
if (upside_down_glyph_count > (layouts_.text().length() / 2))
{
if (orientation == UPRIGHT_AUTO)
{
//Try again with oposite orientation
s.restore();
begin.restore();
return single_line_placement(pp, real_orientation == UPRIGHT_RIGHT ? UPRIGHT_LEFT : UPRIGHT_RIGHT);
}
//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)
else if (orientation == UPRIGHT_LEFT_ONLY || orientation == UPRIGHT_RIGHT_ONLY)
{
return false;
}
}
for (box2d<double> const& bbox : bboxes)
for (box2d<double> const& box : bboxes)
{
detector_.insert(bbox, layout_.text());
detector_.insert(box, layouts_.text());
}
placements_.push_back(glyphs);
return true;
return true;
}
void placement_finder::path_move_dx(vertex_cache &pp)
@ -579,7 +497,7 @@ bool placement_finder::add_marker(glyph_positions_ptr glyphs, pixel_position con
return true;
}
box2d<double> placement_finder::get_bbox(glyph_info const& glyph, pixel_position const& pos, rotation const& rot)
box2d<double> placement_finder::get_bbox(text_layout const& layout, glyph_info const& glyph, pixel_position const& pos, rotation const& rot)
{
/*
@ -592,7 +510,7 @@ box2d<double> placement_finder::get_bbox(glyph_info const& glyph, pixel_position
(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);
double width = layout.cluster_width(glyph.char_index);
if (glyph.width <= 0) width = -width;
pixel_position tmp, tmp2;
tmp.set(0, glyph.ymax);