diff --git a/include/mapnik/symbolizer.hpp b/include/mapnik/symbolizer.hpp index 181881281..7985c6e1a 100644 --- a/include/mapnik/symbolizer.hpp +++ b/include/mapnik/symbolizer.hpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -104,7 +105,8 @@ using value_base_type = util::variant; + group_symbolizer_properties_ptr, + font_feature_settings_ptr>; struct strict_value : value_base_type { @@ -173,7 +175,8 @@ enum class property_types : std::uint8_t target_horizontal_alignment, target_justify_alignment, target_vertical_alignment, - target_upright + target_upright, + target_font_feature_settings }; inline bool operator==(symbolizer_base const& lhs, symbolizer_base const& rhs) @@ -415,6 +418,20 @@ struct evaluate_expression_wrapper } }; +// mapnik::font_feature_settings_ptr +template <> +struct evaluate_expression_wrapper +{ + template + mapnik::font_feature_settings_ptr operator() (T1 const& expr, T2 const& feature, T3 const& vars) const + { + mapnik::value_type val = util::apply_visitor(mapnik::evaluate(feature, vars), expr); + // FIXME - throw instead? + if (val.is_null()) return std::make_shared(); + return std::make_shared(val.to_string()); + } +}; + template struct extract_value : public util::static_visitor { diff --git a/include/mapnik/symbolizer_keys.hpp b/include/mapnik/symbolizer_keys.hpp index b4442ed7c..f69f780ff 100644 --- a/include/mapnik/symbolizer_keys.hpp +++ b/include/mapnik/symbolizer_keys.hpp @@ -91,6 +91,7 @@ enum class keys : std::uint8_t vertical_alignment, upright, avoid_edges, + font_feature_settings, MAX_SYMBOLIZER_KEY }; diff --git a/include/mapnik/text/font_feature_settings.hpp b/include/mapnik/text/font_feature_settings.hpp new file mode 100644 index 000000000..167e05a31 --- /dev/null +++ b/include/mapnik/text/font_feature_settings.hpp @@ -0,0 +1,68 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2014 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 MAPNIK_FONT_FEATURE_SETTINGS_HPP +#define MAPNIK_FONT_FEATURE_SETTINGS_HPP + +// stl +#include +#include +#include + +// harfbuzz +#include + +namespace mapnik +{ + +class font_feature_settings +{ +public: + using font_feature = hb_feature_t; + using feature_vector = std::vector; + using feature_iterator = feature_vector::iterator; + + font_feature_settings(std::string const& features = ""); + + void from_string(std::string const& features); + std::string to_string() const; + + void append(std::string const& feature); + void append(font_feature const& feature) { features_.push_back(feature); } + + const font_feature* get_features() const { return features_.data(); } + feature_vector::size_type count() const { return features_.size(); } + +private: + feature_vector features_; +}; + +using font_feature_settings_ptr = std::shared_ptr; + +constexpr unsigned int font_feature_range_global_start = 0u; +constexpr unsigned int font_feature_range_global_end = std::numeric_limits::max(); + +constexpr hb_feature_t font_feature_liga_off = { HB_TAG('l', 'i', 'g', 'a'), 0, font_feature_range_global_start, font_feature_range_global_end }; + +} // mapnik namespace + +#endif // MAPNIK_FONT_FEATURE_SETTINGS_HPP diff --git a/include/mapnik/text/formatting/format.hpp b/include/mapnik/text/formatting/format.hpp index 5dbe21e4e..c52635788 100644 --- a/include/mapnik/text/formatting/format.hpp +++ b/include/mapnik/text/formatting/format.hpp @@ -55,6 +55,7 @@ public: boost::optional fill; boost::optional halo_fill; boost::optional halo_radius; + boost::optional font_feature_settings; private: node_ptr child_; diff --git a/include/mapnik/text/harfbuzz_shaper.hpp b/include/mapnik/text/harfbuzz_shaper.hpp index e9af8e93e..888a0b0ec 100644 --- a/include/mapnik/text/harfbuzz_shaper.hpp +++ b/include/mapnik/text/harfbuzz_shaper.hpp @@ -27,6 +27,8 @@ #include #include #include +#include + // stl #include #include @@ -60,7 +62,7 @@ static void shape_text(text_line & line, text_itemizer & itemizer, std::map & width_map, face_manager_freetype & font_manager, - double scale_factor ) + double scale_factor) { unsigned start = line.first_char(); unsigned end = line.last_char(); @@ -74,6 +76,8 @@ static void shape_text(text_line & line, hb_buffer_pre_allocate(buffer.get(), length); mapnik::value_unicode_string const& text = itemizer.text(); + font_feature_settings_ptr features = list.front().format->font_feature_settings; + for (auto const& text_item : list) { face_set_ptr face_set = font_manager.get_face_set(text_item.format->face_name, text_item.format->fontset); @@ -89,7 +93,7 @@ static void shape_text(text_line & line, hb_buffer_set_direction(buffer.get(), (text_item.rtl == UBIDI_RTL)?HB_DIRECTION_RTL:HB_DIRECTION_LTR); hb_buffer_set_script(buffer.get(), _icu_script_to_script(text_item.script)); hb_font_t *font(hb_ft_font_create(face->get_face(), nullptr)); - hb_shape(font, buffer.get(), nullptr, 0); + hb_shape(font, buffer.get(), features->get_features(), features->count()); hb_font_destroy(font); unsigned num_glyphs = hb_buffer_get_length(buffer.get()); diff --git a/include/mapnik/text/text_properties.hpp b/include/mapnik/text/text_properties.hpp index f094574de..d22d0b536 100644 --- a/include/mapnik/text/text_properties.hpp +++ b/include/mapnik/text/text_properties.hpp @@ -55,7 +55,8 @@ struct evaluated_format_properties text_transform(NONE), fill(0,0,0), halo_fill(0,0,0), - halo_radius(0.0) {} + halo_radius(0.0), + font_feature_settings(std::make_shared()) {} std::string face_name; boost::optional fontset; double text_size; @@ -67,6 +68,7 @@ struct evaluated_format_properties color fill; color halo_fill; double halo_radius; + font_feature_settings_ptr font_feature_settings; }; } @@ -94,6 +96,7 @@ struct MAPNIK_DECL format_properties symbolizer_base::value_type halo_fill; symbolizer_base::value_type halo_radius; symbolizer_base::value_type text_transform; + symbolizer_base::value_type font_feature_settings; }; diff --git a/include/mapnik/xml_attribute_cast.hpp b/include/mapnik/xml_attribute_cast.hpp index 2e4c666b1..95880e03f 100644 --- a/include/mapnik/xml_attribute_cast.hpp +++ b/include/mapnik/xml_attribute_cast.hpp @@ -33,6 +33,7 @@ #include #include #include +#include // boost #include @@ -200,6 +201,16 @@ struct do_xml_attribute_cast } }; +// specialization for mapnik::font_feature_settings_ptr +template <> +struct do_xml_attribute_cast +{ + static inline boost::optional xml_attribute_cast_impl(xml_tree const& tree, std::string const& source) + { + return std::make_shared(source); + } +}; + } // end namespace detail template diff --git a/src/build.py b/src/build.py index 7bd766611..7854f6022 100644 --- a/src/build.py +++ b/src/build.py @@ -211,6 +211,7 @@ source = Split( text/renderer.cpp text/symbolizer_helpers.cpp text/text_properties.cpp + text/font_feature_settings.cpp text/formatting/base.cpp text/formatting/list.cpp text/formatting/text.cpp diff --git a/src/symbolizer_keys.cpp b/src/symbolizer_keys.cpp index bb440e984..6bd5c638b 100644 --- a/src/symbolizer_keys.cpp +++ b/src/symbolizer_keys.cpp @@ -118,6 +118,7 @@ static const property_meta_type key_meta[const_max_key] = {return enumeration(text_upright_enum(e.value)).as_string();}, property_types::target_upright}, property_meta_type{ "avoid-edges",false, nullptr, property_types::target_bool }, + property_meta_type{ "font-feature-settings", nullptr, nullptr, property_types::target_font_feature_settings }, }; diff --git a/src/text/font_feature_settings.cpp b/src/text/font_feature_settings.cpp new file mode 100644 index 000000000..3bed59a2e --- /dev/null +++ b/src/text/font_feature_settings.cpp @@ -0,0 +1,95 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2014 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 + +// boost +#include + +// stl +#include +#include +#include + +namespace mapnik +{ + +font_feature_settings::font_feature_settings(std::string const& features) +{ + from_string(features); +} + +void font_feature_settings::from_string(std::string const& features) +{ + features_.clear(); + + if (std::all_of(features.begin(), features.end(), isspace)) return; + + namespace qi = boost::spirit::qi; + qi::char_type char_; + qi::as_string_type as_string; + + auto app = [&](std::string const& s) { append(s); }; + + if (!qi::parse(features.begin(), features.end(), as_string[+(char_ - ',')][app] % ',')) + { + throw config_error("failed to parse font-features: '" + features + "'"); + } +} + +std::string font_feature_settings::to_string() const +{ + std::ostringstream output; + constexpr size_t buffsize = 128; + char buff[buffsize]; + bool first = true; + + for (auto feature : features_) + { + if (!first) + { + output << ", "; + first = false; + } + hb_feature_to_string(&feature, buff, buffsize); + output << buff; + } + + return output.str(); +} + +void font_feature_settings::append(std::string const& feature) +{ + features_.emplace_back(); + feature_iterator current_feature = features_.end() - 1; + + if (!hb_feature_from_string(feature.c_str(), feature.length(), &*current_feature)) + { + features_.erase(current_feature); + throw config_error("failed to parse font-features: '" + feature + "'"); + } +} + +} + diff --git a/src/text/formatting/format.cpp b/src/text/formatting/format.cpp index 4df00e644..186e7e0b8 100644 --- a/src/text/formatting/format.cpp +++ b/src/text/formatting/format.cpp @@ -50,6 +50,7 @@ void format_node::to_xml(ptree & xml) const if (halo_fill) serialize_property("halo-fill", *halo_fill, new_node); if (halo_radius) serialize_property("halo-radius", *halo_radius, new_node); if (text_transform) serialize_property("text-transform", *text_transform, new_node); + if (font_feature_settings) serialize_property("font-feature-settings", *font_feature_settings, new_node); if (face_name) set_attr(new_node, "face-name", *face_name); if (fontset) set_attr(new_node, "fontset-name", fontset->get_name()); @@ -73,6 +74,7 @@ node_ptr format_node::from_xml(xml_node const& xml, fontset_map const& fontsets) set_property_from_xml(n->fill, "fill", xml); set_property_from_xml(n->halo_fill, "halo-fill", xml); set_property_from_xml(n->text_transform, "text-transform", xml); + set_property_from_xml(n->font_feature_settings, "font-feature-settings", xml); boost::optional face_name = xml.get_opt_attr("face-name"); if (face_name) @@ -112,6 +114,7 @@ void format_node::apply(evaluated_format_properties_ptr p, feature_impl const& f if (fill) new_properties->fill = util::apply_visitor(extract_value(feature,attrs), *fill); if (halo_fill) new_properties->halo_fill = util::apply_visitor(extract_value(feature,attrs), *halo_fill); if (text_transform) new_properties->text_transform = util::apply_visitor(extract_value(feature,attrs), *text_transform); + if (font_feature_settings) new_properties->font_feature_settings = util::apply_visitor(extract_value(feature,attrs), *font_feature_settings); if (fontset) { @@ -155,6 +158,7 @@ void format_node::add_expressions(expression_set & output) const if (fill && is_expression(*fill)) output.insert(util::get(*fill)); if (halo_fill && is_expression(*halo_fill)) output.insert(util::get(*halo_fill)); if (text_transform && is_expression(*text_transform)) output.insert(util::get(*text_transform)); + if (font_feature_settings && is_expression(*font_feature_settings)) output.insert(util::get(*font_feature_settings)); if (child_) child_->add_expressions(output); } diff --git a/src/text/properties_util.cpp b/src/text/properties_util.cpp index d3a87734c..ec13c510b 100644 --- a/src/text/properties_util.cpp +++ b/src/text/properties_util.cpp @@ -22,6 +22,7 @@ #include #include +#include namespace mapnik { namespace detail { @@ -67,6 +68,12 @@ struct property_serializer : public util::static_visitor<> node_.put("." + name_, str); } + void operator() (font_feature_settings_ptr const& val) const + { + std::string str = val->to_string(); + node_.put("." + name_, str); + } + template void operator() (T const& val) const { diff --git a/src/text/text_properties.cpp b/src/text/text_properties.cpp index 719c5a704..80963b88a 100644 --- a/src/text/text_properties.cpp +++ b/src/text/text_properties.cpp @@ -97,6 +97,8 @@ void text_symbolizer_properties::process(text_layout & output, feature_impl cons format->face_name = format_defaults.face_name; format->fontset = format_defaults.fontset; + format->font_feature_settings = util::apply_visitor(extract_value(feature,attrs), format_defaults.font_feature_settings); + tree_->apply(format, feature, attrs, output); } else MAPNIK_LOG_WARN(text_properties) << "text_symbolizer_properties can't produce text: No formatting tree!"; @@ -315,7 +317,8 @@ format_properties::format_properties() fill(color(0,0,0)), halo_fill(color(255,255,255)), halo_radius(0.0), - text_transform(enumeration_wrapper(NONE)) {} + text_transform(enumeration_wrapper(NONE)), + font_feature_settings(std::make_shared()) {} void format_properties::from_xml(xml_node const& node, fontset_map const& fontsets) { @@ -328,6 +331,7 @@ void format_properties::from_xml(xml_node const& node, fontset_map const& fontse set_property_from_xml(fill, "fill", node); set_property_from_xml(halo_fill, "halo-fill", node); set_property_from_xml(text_transform,"text-transform", node); + set_property_from_xml(font_feature_settings, "font-feature-settings", node); optional face_name_ = node.get_opt_attr("face-name"); if (face_name_) face_name = *face_name_; @@ -377,6 +381,7 @@ void format_properties::to_xml(boost::property_tree::ptree & node, bool explicit if (!(fill == dfl.fill) || explicit_defaults) serialize_property("fill", fill, node); if (!(halo_fill == dfl.halo_fill) || explicit_defaults) serialize_property("halo-fill", halo_fill, node); if (!(text_transform == dfl.text_transform) || explicit_defaults) serialize_property("text-transform", text_transform, node); + if (!(font_feature_settings == dfl.font_feature_settings) || explicit_defaults) serialize_property("font-feature-settings", font_feature_settings, node); } void format_properties::add_expressions(expression_set & output) const @@ -390,6 +395,7 @@ void format_properties::add_expressions(expression_set & output) const if (is_expression(fill)) output.insert(util::get(fill)); if (is_expression(halo_fill)) output.insert(util::get(halo_fill)); if (is_expression(text_transform)) output.insert(util::get(text_transform)); + if (is_expression(font_feature_settings)) output.insert(util::get(font_feature_settings)); } diff --git a/src/xml_tree.cpp b/src/xml_tree.cpp index 0e2caa1c4..9d8b80553 100644 --- a/src/xml_tree.cpp +++ b/src/xml_tree.cpp @@ -33,6 +33,7 @@ #include #include #include +#include // stl #include @@ -74,7 +75,8 @@ DEFINE_NAME_TRAIT( mapnik::value_integer, "int" ) #endif DEFINE_NAME_TRAIT( std::string, "string" ) DEFINE_NAME_TRAIT( color, "color" ) -DEFINE_NAME_TRAIT(expression_ptr, "expression_ptr" ) +DEFINE_NAME_TRAIT( expression_ptr, "expression_ptr" ) +DEFINE_NAME_TRAIT( font_feature_settings_ptr, "font-feature-settings" ) template struct name_trait< mapnik::enumeration > @@ -421,6 +423,7 @@ compile_get_opt_attr(justify_alignment_e); compile_get_opt_attr(text_upright_e); compile_get_opt_attr(halo_rasterizer_e); compile_get_opt_attr(expression_ptr); +compile_get_opt_attr(font_feature_settings_ptr); compile_get_attr(std::string); compile_get_attr(filter_mode_e); compile_get_attr(point_placement_e);