Merge remote-tracking branch 'origin/master'

This commit is contained in:
artemp 2017-03-14 11:35:20 +00:00
commit b10d2a3dff
15 changed files with 374 additions and 118 deletions

2
.gitmodules vendored
View file

@ -5,7 +5,7 @@
[submodule "test/data-visual"] [submodule "test/data-visual"]
path = test/data-visual path = test/data-visual
url = https://github.com/mapnik/test-data-visual.git url = https://github.com/mapnik/test-data-visual.git
branch = master branch = harfbuzz-shaper
[submodule "deps/mapbox/variant"] [submodule "deps/mapbox/variant"]
path = deps/mapbox/variant path = deps/mapbox/variant
url = https://github.com/mapbox/variant.git url = https://github.com/mapbox/variant.git

View file

@ -1787,7 +1787,7 @@ if not preconfigured:
# Common flags for g++/clang++ CXX compiler. # Common flags for g++/clang++ CXX compiler.
# TODO: clean up code more to make -Wextra -Wsign-compare -Wsign-conversion -Wconversion viable # TODO: clean up code more to make -Wextra -Wsign-compare -Wsign-conversion -Wconversion viable
# -Wfloat-equal -Wold-style-cast -Wexit-time-destructors -Wglobal-constructors -Wreserved-id-macro -Wheader-hygiene -Wmissing-noreturn # -Wfloat-equal -Wold-style-cast -Wexit-time-destructors -Wglobal-constructors -Wreserved-id-macro -Wheader-hygiene -Wmissing-noreturn
common_cxx_flags = '-fvisibility=hidden -fvisibility-inlines-hidden -Wall %s %s -ftemplate-depth-300 -Wsign-compare -Wshadow ' % (env['WARNING_CXXFLAGS'], pthread) common_cxx_flags = '-fvisibility=hidden -fvisibility-inlines-hidden -Wall %s %s -ftemplate-depth-300 -Wsign-compare ' % (env['WARNING_CXXFLAGS'], pthread)
if 'clang++' in env['CXX']: if 'clang++' in env['CXX']:
common_cxx_flags += ' -Wno-unsequenced -Wtautological-compare -Wheader-hygiene -Wc++14-extensions ' common_cxx_flags += ' -Wno-unsequenced -Wtautological-compare -Wheader-hygiene -Wc++14-extensions '
@ -1799,7 +1799,7 @@ if not preconfigured:
env.Append(CXXFLAGS = '-fsanitize=undefined-trap -fsanitize-undefined-trap-on-error -ftrapv -fwrapv') env.Append(CXXFLAGS = '-fsanitize=undefined-trap -fsanitize-undefined-trap-on-error -ftrapv -fwrapv')
if env['DEBUG_SANITIZE']: if env['DEBUG_SANITIZE']:
env.Append(CXXFLAGS = ['-fsanitize=address']) env.Append(CXXFLAGS = ['-fsanitize=address','-fno-omit-frame-pointer'])
env.Append(LINKFLAGS = ['-fsanitize=address']) env.Append(LINKFLAGS = ['-fsanitize=address'])

View file

@ -24,6 +24,7 @@
#define MAPNIK_EXPRESSIONS_GRAMMAR_X3_DEF_HPP #define MAPNIK_EXPRESSIONS_GRAMMAR_X3_DEF_HPP
#include <mapnik/expression_grammar_x3.hpp> #include <mapnik/expression_grammar_x3.hpp>
#include <mapnik/json/unicode_string_grammar_x3_def.hpp>
#include <mapnik/expression_node.hpp> #include <mapnik/expression_node.hpp>
#include <mapnik/function_call.hpp> #include <mapnik/function_call.hpp>
#include <mapnik/unicode.hpp> #include <mapnik/unicode.hpp>
@ -65,6 +66,15 @@ namespace mapnik { namespace grammar {
using x3::alnum; using x3::alnum;
x3::uint_parser<char, 16, 2, 2> const hex2 {}; x3::uint_parser<char, 16, 2, 2> const hex2 {};
namespace {
auto const& escaped_unicode = json::grammar::escaped_unicode;
}
auto append = [](auto const& ctx)
{
_val(ctx) += _attr(ctx);
};
auto do_assign = [] (auto const& ctx) auto do_assign = [] (auto const& ctx)
{ {
_val(ctx) = std::move(_attr(ctx)); _val(ctx) = std::move(_attr(ctx));
@ -302,8 +312,20 @@ namespace mapnik { namespace grammar {
x3::rule<class regex_replace_expression, std::pair<std::string,std::string> > const regex_replace_expression("regex replace expression"); x3::rule<class regex_replace_expression, std::pair<std::string,std::string> > const regex_replace_expression("regex replace expression");
// strings // strings
auto const single_quoted_string = x3::rule<class single_quoted_string, std::string> {} = lit('\'') >> no_skip[*(unesc_char | ("\\x" > hex2) | (char_ - '\''))] > '\''; auto const single_quoted_string = x3::rule<class single_quoted_string, std::string> {} = lit('\'')
auto const double_quoted_string = x3::rule<class double_quoted_string, std::string> {} = lit('"') >> no_skip[*(unesc_char | ("\\x" > hex2) | (char_ - '"'))] > '"'; >> no_skip[*(unesc_char[append]
|
//(lit('\\') > escaped_unicode[append]) // FIXME (!)
//|
(~char_('\''))[append])] > lit('\'');
auto const double_quoted_string = x3::rule<class double_quoted_string, std::string> {} = lit('"')
>> no_skip[*(unesc_char[append]
|
(lit('\\') > escaped_unicode[append])
|
(~char_('"'))[append])] > lit('"');
auto const quoted_string = x3::rule<class quoted_string, std::string> {} = single_quoted_string | double_quoted_string; auto const quoted_string = x3::rule<class quoted_string, std::string> {} = single_quoted_string | double_quoted_string;
auto const unquoted_ustring = x3::rule<class ustring, std::string> {} = no_skip[alpha > *alnum] - lit("not"); auto const unquoted_ustring = x3::rule<class ustring, std::string> {} = no_skip[alpha > *alnum] - lit("not");

View file

@ -24,8 +24,9 @@
#define MAPNIK_JSON_UNICODE_STRING_GRAMMAR_X3_DEF_HPP #define MAPNIK_JSON_UNICODE_STRING_GRAMMAR_X3_DEF_HPP
#include <mapnik/json/unicode_string_grammar_x3.hpp> #include <mapnik/json/unicode_string_grammar_x3.hpp>
#include <iostream> // boost
#include <boost/regex/pending/unicode_iterator.hpp>
//
namespace mapnik { namespace json { namespace grammar { namespace mapnik { namespace json { namespace grammar {
namespace x3 = boost::spirit::x3; namespace x3 = boost::spirit::x3;
@ -52,6 +53,25 @@ auto push_char = [](auto const& ctx) { _val(ctx).push_back(_attr(ctx));};
auto push_utf8 = [](auto const& ctx) { detail::push_utf8_impl(_val(ctx), _attr(ctx));}; auto push_utf8 = [](auto const& ctx) { detail::push_utf8_impl(_val(ctx), _attr(ctx));};
auto push_utf16 = [](auto const& ctx)
{
using iterator_type = std::vector<std::uint16_t>::const_iterator;
auto const& utf16 = _attr(ctx);
try
{
boost::u16_to_u32_iterator<iterator_type> itr(utf16.begin());
boost::u16_to_u32_iterator<iterator_type> end(utf16.end());
for (; itr != end; ++itr)
{
detail::push_utf8_impl(_val(ctx), *itr);
}
}
catch( ... )
{
// caught
}
};
auto push_esc = [] (auto const& ctx) auto push_esc = [] (auto const& ctx)
{ {
std::string & utf8 = _val(ctx); std::string & utf8 = _val(ctx);
@ -85,29 +105,35 @@ using x3::eol;
using x3::no_skip; using x3::no_skip;
x3::uint_parser<char, 16, 2, 2> const hex2 {}; x3::uint_parser<char, 16, 2, 2> const hex2 {};
x3::uint_parser<uchar, 16, 4, 4> const hex4 {}; x3::uint_parser<std::uint16_t, 16, 4, 4> const hex4 {};
x3::uint_parser<uchar, 16, 8, 8> const hex8 {}; x3::uint_parser<uchar, 16, 8, 8> const hex8 {};
// start rule // start rule
unicode_string_grammar_type const unicode_string("Unicode String"); unicode_string_grammar_type const unicode_string("Unicode String");
// rules // rules
x3::rule<class double_quoted_tag, std::string> const double_quoted("Double-quoted string"); x3::rule<class double_quoted_tag, std::string> const double_quoted("Double-quoted string");
x3::rule<class escaped_tag, std::string> const escaped("Escaped Characted"); x3::rule<class escaped_tag, std::string> const escaped("Escaped Character");
x3::rule<class escaped_unicode_tag, std::string> const escaped_unicode("Escaped Unicode code point(s)");
x3::rule<class utf16_string_tag, std::vector<std::uint16_t>> const utf16_string("UTF16 encoded string");
auto unicode_string_def = double_quoted auto unicode_string_def = double_quoted
; ;
auto const escaped_def = lit('\\') > auto utf16_string_def = lit('u') > hex4 > *(lit("\\u") > hex4)
((lit('x') > hex2[push_char]) ;
auto escaped_unicode_def =
(lit('x') > hex2[push_char])
| |
(lit('u') > hex4[push_utf8]) utf16_string[push_utf16]
| |
(lit('U') > hex8[push_utf8]) (lit('U') > hex8[push_utf8])
;
auto const escaped_def = lit('\\') >
(escaped_unicode[append]
| |
char_("0abtnvfre\"/\\N_LP \t")[push_esc] char_("0abtnvfre\"/\\N_LP \t")[push_esc]
| |
eol) // continue to next line eol) // continue to next line
; ;
auto const double_quoted_def = lit('"') > no_skip[*(escaped[append] | (~char_('"'))[append])] > lit('"'); auto const double_quoted_def = lit('"') > no_skip[*(escaped[append] | (~char_('"'))[append])] > lit('"');
#pragma GCC diagnostic push #pragma GCC diagnostic push
@ -116,7 +142,9 @@ auto const double_quoted_def = lit('"') > no_skip[*(escaped[append] | (~char_('"
BOOST_SPIRIT_DEFINE( BOOST_SPIRIT_DEFINE(
unicode_string, unicode_string,
double_quoted, double_quoted,
escaped escaped,
escaped_unicode,
utf16_string
); );
#pragma GCC diagnostic pop #pragma GCC diagnostic pop

View file

@ -72,10 +72,13 @@ public:
bool glyph_dimensions(glyph_info &glyph) const; bool glyph_dimensions(glyph_info &glyph) const;
inline bool is_color() const { return color_font_;}
~font_face(); ~font_face();
private: private:
FT_Face face_; FT_Face face_;
bool color_font_ = false;
}; };
using face_ptr = std::shared_ptr<font_face>; using face_ptr = std::shared_ptr<font_face>;

View file

@ -80,17 +80,16 @@ static void shape_text(text_line & line,
line.reserve(length); line.reserve(length);
auto hb_buffer_deleter = [](hb_buffer_t * buffer) { hb_buffer_destroy(buffer);}; auto hb_buffer_deleter = [](hb_buffer_t * buffer) { hb_buffer_destroy(buffer);};
const std::unique_ptr<hb_buffer_t, decltype(hb_buffer_deleter)> buffer(hb_buffer_create(),hb_buffer_deleter); const std::unique_ptr<hb_buffer_t, decltype(hb_buffer_deleter)> buffer(hb_buffer_create(), hb_buffer_deleter);
hb_buffer_pre_allocate(buffer.get(), safe_cast<int>(length)); hb_buffer_pre_allocate(buffer.get(), safe_cast<int>(length));
mapnik::value_unicode_string const& text = itemizer.text(); mapnik::value_unicode_string const& text = itemizer.text();
for (auto const& text_item : list) 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); face_set_ptr face_set = font_manager.get_face_set(text_item.format_->face_name, text_item.format_->fontset);
double size = text_item.format_->text_size * scale_factor; double size = text_item.format_->text_size * scale_factor;
face_set->set_unscaled_character_sizes(); face_set->set_unscaled_character_sizes();
std::size_t num_faces = face_set->size(); std::size_t num_faces = face_set->size();
std::size_t pos = 0;
font_feature_settings const& ff_settings = text_item.format_->ff_settings; font_feature_settings const& ff_settings = text_item.format_->ff_settings;
int ff_count = safe_cast<int>(ff_settings.count()); int ff_count = safe_cast<int>(ff_settings.count());
@ -101,85 +100,117 @@ static void shape_text(text_line & line,
hb_glyph_info_t glyph; hb_glyph_info_t glyph;
hb_glyph_position_t position; hb_glyph_position_t position;
}; };
// this table is filled with information for rendering each glyph, so that // this table is filled with information for rendering each glyph, so that
// several font faces can be used in a single text_item // several font faces can be used in a single text_item
std::vector<glyph_face_info> glyphinfos; std::size_t pos = 0;
unsigned valid_glyphs = 0; std::vector<std::vector<glyph_face_info>> glyphinfos;
glyphinfos.resize(text.length());
for (auto const& face : *face_set) for (auto const& face : *face_set)
{ {
++pos; ++pos;
hb_buffer_clear_contents(buffer.get()); hb_buffer_clear_contents(buffer.get());
hb_buffer_add_utf16(buffer.get(), uchar_to_utf16(text.getBuffer()), text.length(), text_item.start, static_cast<int>(text_item.end - text_item.start)); hb_buffer_add_utf16(buffer.get(), uchar_to_utf16(text.getBuffer()), text.length(), text_item.start, static_cast<int>(text_item.end - text_item.start));
hb_buffer_set_direction(buffer.get(), (text_item.dir == UBIDI_RTL)?HB_DIRECTION_RTL:HB_DIRECTION_LTR); hb_buffer_set_direction(buffer.get(), (text_item.dir == UBIDI_RTL) ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
hb_buffer_set_script(buffer.get(), _icu_script_to_script(text_item.script)); 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_font_t *font(hb_ft_font_create(face->get_face(), nullptr));
// https://github.com/mapnik/test-data-visual/pull/25 // https://github.com/mapnik/test-data-visual/pull/25
#if HB_VERSION_MAJOR > 0
#if HB_VERSION_ATLEAST(1, 0 , 5) #if (HB_VERSION_MAJOR > 0 && HB_VERSION_ATLEAST(1, 0, 5))
hb_ft_font_set_load_flags(font,FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING); hb_ft_font_set_load_flags(font,FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING);
#endif #endif
#endif
hb_shape(font, buffer.get(), ff_settings.get_features(), ff_count); hb_shape(font, buffer.get(), ff_settings.get_features(), ff_count);
hb_font_destroy(font); hb_font_destroy(font);
unsigned num_glyphs = hb_buffer_get_length(buffer.get()); unsigned num_glyphs = hb_buffer_get_length(buffer.get());
hb_glyph_info_t *glyphs = hb_buffer_get_glyph_infos(buffer.get(), &num_glyphs);
hb_glyph_position_t *positions = hb_buffer_get_glyph_positions(buffer.get(), &num_glyphs);
// if the number of rendered glyphs has increased, we need to resize the table unsigned cluster = 0;
if (num_glyphs > glyphinfos.size()) bool in_cluster = false;
{ std::vector<unsigned> clusters;
glyphinfos.resize(num_glyphs);
}
hb_glyph_info_t *glyphs = hb_buffer_get_glyph_infos(buffer.get(), nullptr); for (unsigned i = 0; i < num_glyphs; ++i)
hb_glyph_position_t *positions = hb_buffer_get_glyph_positions(buffer.get(), nullptr);
// Check if all glyphs are valid.
for (unsigned i=0; i<num_glyphs; ++i)
{ {
// if we have a valid codepoint, save rendering info. if (i == 0)
if (glyphs[i].codepoint)
{ {
if (!glyphinfos[i].glyph.codepoint) cluster = glyphs[0].cluster;
{ clusters.push_back(cluster);
++valid_glyphs;
} }
glyphinfos[i] = { face, glyphs[i], positions[i] }; if (cluster != glyphs[i].cluster)
{
cluster = glyphs[i].cluster;
clusters.push_back(cluster);
in_cluster = false;
}
else if (i != 0)
{
in_cluster = true;
}
if (glyphinfos.size() <= cluster)
{
glyphinfos.resize(cluster + 1);
}
auto & c = glyphinfos[cluster];
if (c.empty())
{
c.push_back({face, glyphs[i], positions[i]});
}
else if (c.front().glyph.codepoint == 0)
{
c.front() = { face, glyphs[i], positions[i] };
}
else if (in_cluster)
{
c.push_back({ face, glyphs[i], positions[i] });
} }
} }
if (valid_glyphs < num_glyphs && (pos < num_faces)) bool all_set = true;
for (auto c_id : clusters)
{
auto const& c = glyphinfos[c_id];
if (c.empty() || c.front().glyph.codepoint == 0)
{
all_set = false;
break;
}
}
if (!all_set && (pos < num_faces))
{ {
//Try next font in fontset //Try next font in fontset
continue; continue;
} }
double max_glyph_height = 0; double max_glyph_height = 0;
for (unsigned i=0; i<num_glyphs; ++i) for (auto const& c_id : clusters)
{ {
auto& gpos = positions[i]; auto const& c = glyphinfos[c_id];
auto& glyph = glyphs[i]; for (auto const& info : c)
face_ptr theface = face;
if (glyphinfos[i].glyph.codepoint)
{ {
gpos = glyphinfos[i].position; auto const& gpos = info.position;
glyph = glyphinfos[i].glyph; auto const& glyph = info.glyph;
theface = glyphinfos[i].face;
}
unsigned char_index = glyph.cluster; unsigned char_index = glyph.cluster;
glyph_info g(glyph.codepoint,char_index,text_item.format_); glyph_info g(glyph.codepoint,char_index,text_item.format_);
if (theface->glyph_dimensions(g)) if (info.glyph.codepoint != 0) g.face = info.face;
else g.face = face;
if (g.face->glyph_dimensions(g))
{ {
g.face = theface; g.scale_multiplier = g.face->get_face()->units_per_EM > 0 ?
g.scale_multiplier = size / theface->get_face()->units_per_EM; (size / g.face->get_face()->units_per_EM) : (size / 2048.0) ;
//Overwrite default advance with better value provided by HarfBuzz //Overwrite default advance with better value provided by HarfBuzz
g.unscaled_advance = gpos.x_advance; g.unscaled_advance = gpos.x_advance;
g.offset.set(gpos.x_offset * g.scale_multiplier, gpos.y_offset * g.scale_multiplier); g.offset.set(gpos.x_offset * g.scale_multiplier, gpos.y_offset * g.scale_multiplier);
double tmp_height = g.height(); double tmp_height = g.height();
if (g.face->is_color())
{
tmp_height = size;
}
if (tmp_height > max_glyph_height) max_glyph_height = tmp_height; if (tmp_height > max_glyph_height) max_glyph_height = tmp_height;
width_map[char_index] += g.advance(); width_map[char_index] += g.advance();
line.add_glyph(std::move(g), scale_factor); line.add_glyph(std::move(g), scale_factor);
} }
} }
}
line.update_max_char_height(max_glyph_height); line.update_max_char_height(max_glyph_height);
break; //When we reach this point the current font had all glyphs. break; //When we reach this point the current font had all glyphs.
} }

View file

@ -28,6 +28,7 @@
#include <mapnik/image_compositing.hpp> #include <mapnik/image_compositing.hpp>
#include <mapnik/symbolizer_enumerations.hpp> #include <mapnik/symbolizer_enumerations.hpp>
#include <mapnik/util/noncopyable.hpp> #include <mapnik/util/noncopyable.hpp>
#include <mapnik/pixel_position.hpp>
#pragma GCC diagnostic push #pragma GCC diagnostic push
#include <mapnik/warning_ignore.hpp> #include <mapnik/warning_ignore.hpp>
@ -51,8 +52,13 @@ struct glyph_t
{ {
FT_Glyph image; FT_Glyph image;
detail::evaluated_format_properties const& properties; detail::evaluated_format_properties const& properties;
glyph_t(FT_Glyph image_, detail::evaluated_format_properties const& properties_) pixel_position pos;
: image(image_), properties(properties_) {} double size;
glyph_t(FT_Glyph image_, detail::evaluated_format_properties const& properties_, pixel_position const& pos_, double size_)
: image(image_),
properties(properties_),
pos(pos_),
size(size_) {}
}; };
class text_renderer : private util::noncopyable class text_renderer : private util::noncopyable

View file

@ -143,8 +143,8 @@ private:
// note: this probably isn't the best solution. it would be better to have an object for each cluster, but // note: this probably isn't the best solution. it would be better to have an object for each cluster, but
// it needs to be implemented with no overhead. // it needs to be implemented with no overhead.
std::map<unsigned, double> width_map_; std::map<unsigned, double> width_map_;
double width_; double width_ = 0.0;
double height_; double height_ = 0.0;
unsigned glyphs_count_; unsigned glyphs_count_;
// output // output
@ -173,7 +173,7 @@ private:
bool rotate_displacement_ = false; bool rotate_displacement_ = false;
double text_ratio_ = 0.0; double text_ratio_ = 0.0;
pixel_position displacement_ = {0,0}; pixel_position displacement_ = {0,0};
box2d<double> bounds_; box2d<double> bounds_ = {0, 0, 0, 0};
// children // children
text_layout_vector child_layout_list_; text_layout_vector child_layout_list_;

View file

@ -26,10 +26,11 @@
#include <mapnik/wkt/wkt_grammar_x3.hpp> #include <mapnik/wkt/wkt_grammar_x3.hpp>
#include <mapnik/geometry/fusion_adapted.hpp> #include <mapnik/geometry/fusion_adapted.hpp>
#if defined(__GNUC__) && BOOST_VERSION < 106300 #if defined(__GNUC__)
// instantiate `is_substitute` for reference T and reference Attribute // instantiate `is_substitute` for reference T and reference Attribute
// fixes gcc6 compilation issue with boost 1_61 and boost_1_62 // fixes gcc6 compilation issue
namespace boost { namespace spirit { namespace x3 { namespace traits { namespace boost { namespace spirit { namespace x3 { namespace traits {
template <typename T, typename Attribute, typename Enable> template <typename T, typename Attribute, typename Enable>
struct is_substitute<T&, Attribute&, Enable> struct is_substitute<T&, Attribute&, Enable>
: is_substitute<T, Attribute, Enable> {}; : is_substitute<T, Attribute, Enable> {};

View file

@ -29,6 +29,7 @@
extern "C" extern "C"
{ {
#include FT_GLYPH_H #include FT_GLYPH_H
#include FT_TRUETYPE_TABLES_H
} }
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
@ -37,16 +38,23 @@ namespace mapnik
{ {
font_face::font_face(FT_Face face) font_face::font_face(FT_Face face)
: face_(face) {} : face_(face)
{
static const uint32_t tag = FT_MAKE_TAG('C', 'B', 'D', 'T');
unsigned long length = 0;
FT_Load_Sfnt_Table(face_, tag, 0, nullptr, &length);
if (length) color_font_ = true;
}
bool font_face::set_character_sizes(double size) bool font_face::set_character_sizes(double size)
{ {
return (FT_Set_Char_Size(face_,0,static_cast<FT_F26Dot6>(size * (1<<6)),0,0) == 0); return (FT_Set_Char_Size(face_, 0, static_cast<FT_F26Dot6>(size * (1 << 6)), 0, 0) == 0);
} }
bool font_face::set_unscaled_character_sizes() bool font_face::set_unscaled_character_sizes()
{ {
return (FT_Set_Char_Size(face_,0,face_->units_per_EM,0,0) == 0); FT_F26Dot6 char_height = face_->units_per_EM > 0 ? face_->units_per_EM : 2048.0;
return (FT_Set_Char_Size(face_, 0, char_height, 0, 0) == 0);
} }
bool font_face::glyph_dimensions(glyph_info & glyph) const bool font_face::glyph_dimensions(glyph_info & glyph) const
@ -54,11 +62,14 @@ bool font_face::glyph_dimensions(glyph_info & glyph) const
FT_Vector pen; FT_Vector pen;
pen.x = 0; pen.x = 0;
pen.y = 0; pen.y = 0;
if (color_font_) FT_Select_Size(face_, 0);
FT_Set_Transform(face_, 0, &pen); FT_Set_Transform(face_, 0, &pen);
FT_Int32 load_flags = FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING;
if (FT_Load_Glyph(face_, glyph.glyph_index, FT_LOAD_NO_HINTING)) if (color_font_) load_flags |= FT_LOAD_COLOR ;
if (FT_Load_Glyph(face_, glyph.glyph_index, load_flags))
{ {
MAPNIK_LOG_ERROR(font_face) << "FT_Load_Glyph failed"; MAPNIK_LOG_ERROR(font_face) << "FT_Load_Glyph failed :( index=" << glyph.glyph_index << " " << load_flags
<< " " << face_->family_name << " " << face_->style_name ;
return false; return false;
} }
FT_Glyph image; FT_Glyph image;

View file

@ -25,6 +25,8 @@
#include <mapnik/grid/grid.hpp> #include <mapnik/grid/grid.hpp>
#include <mapnik/text/text_properties.hpp> #include <mapnik/text/text_properties.hpp>
#include <mapnik/font_engine_freetype.hpp> #include <mapnik/font_engine_freetype.hpp>
#include <mapnik/image_compositing.hpp>
#include <mapnik/image_scaling.hpp>
#include <mapnik/text/face.hpp> #include <mapnik/text/face.hpp>
#include <mapnik/image_util.hpp> #include <mapnik/image_util.hpp>
#include <mapnik/image_any.hpp> #include <mapnik/image_any.hpp>
@ -66,8 +68,35 @@ void text_renderer::prepare_glyphs(glyph_positions const& positions)
for (auto const& glyph_pos : positions) for (auto const& glyph_pos : positions)
{ {
glyph_info const& glyph = glyph_pos.glyph; glyph_info const& glyph = glyph_pos.glyph;
glyph.face->set_character_sizes(glyph.format->text_size * scale_factor_); //TODO: Optimize this? FT_Int32 load_flags = FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING;
FT_Face face = glyph.face->get_face();
if (glyph.face->is_color())
{
load_flags |= FT_LOAD_COLOR ;
if (face->num_fixed_sizes > 0)
{
int scaled_size = static_cast<int>(glyph.format->text_size * scale_factor_);
int best_match = 0;
int diff = std::abs(scaled_size - face->available_sizes[0].width);
for (int i = 1; i < face->num_fixed_sizes; ++i)
{
int ndiff = std::abs(scaled_size - face->available_sizes[i].height);
if (ndiff < diff)
{
best_match = i;
diff = ndiff;
}
}
error = FT_Select_Size(face, best_match);
}
}
else
{
glyph.face->set_character_sizes(glyph.format->text_size * scale_factor_);
}
double size = glyph.format->text_size * scale_factor_;
matrix.xx = static_cast<FT_Fixed>( glyph_pos.rot.cos * 0x10000L); matrix.xx = static_cast<FT_Fixed>( glyph_pos.rot.cos * 0x10000L);
matrix.xy = static_cast<FT_Fixed>(-glyph_pos.rot.sin * 0x10000L); matrix.xy = static_cast<FT_Fixed>(-glyph_pos.rot.sin * 0x10000L);
matrix.yx = static_cast<FT_Fixed>( glyph_pos.rot.sin * 0x10000L); matrix.yx = static_cast<FT_Fixed>( glyph_pos.rot.sin * 0x10000L);
@ -77,17 +106,13 @@ void text_renderer::prepare_glyphs(glyph_positions const& positions)
pen.x = static_cast<FT_Pos>(pos.x * 64); pen.x = static_cast<FT_Pos>(pos.x * 64);
pen.y = static_cast<FT_Pos>(pos.y * 64); pen.y = static_cast<FT_Pos>(pos.y * 64);
FT_Face face = glyph.face->get_face();
FT_Set_Transform(face, &matrix, &pen); FT_Set_Transform(face, &matrix, &pen);
error = FT_Load_Glyph(face, glyph.glyph_index, load_flags);
error = FT_Load_Glyph(face, glyph.glyph_index, FT_LOAD_NO_HINTING);
if (error) continue; if (error) continue;
FT_Glyph image; FT_Glyph image;
error = FT_Get_Glyph(face->glyph, &image); error = FT_Get_Glyph(face->glyph, &image);
if (error) continue; if (error) continue;
glyphs_.emplace_back(image, *glyph.format, pos, size);
glyphs_.emplace_back(image, *glyph.format);
} }
} }
@ -101,7 +126,7 @@ void composite_bitmap(T & pixmap, FT_Bitmap *bitmap, unsigned rgba, int x, int y
{ {
for (int j = y, q = 0; j < y_max; ++j, ++q) for (int j = y, q = 0; j < y_max; ++j, ++q)
{ {
unsigned gray=bitmap->buffer[q*bitmap->width+p]; unsigned gray = bitmap->buffer[q * bitmap->width + p];
if (gray) if (gray)
{ {
mapnik::composite_pixel(pixmap, comp_op, i, j, rgba, gray, opacity); mapnik::composite_pixel(pixmap, comp_op, i, j, rgba, gray, opacity);
@ -110,6 +135,31 @@ void composite_bitmap(T & pixmap, FT_Bitmap *bitmap, unsigned rgba, int x, int y
} }
} }
template <typename T>
void composite_color_bitmap(T & pixmap, FT_Bitmap *bitmap, int x, int y, double size, double opacity, composite_mode_e comp_op)
{
image_rgba8 image(bitmap->width, bitmap->rows);
for (unsigned i = 0, p = 0; i < bitmap->width; ++i, p += 4)
{
for (unsigned j = 0, q = 0; j < bitmap->rows; ++j, ++q)
{
std::uint8_t b = bitmap->buffer[q * bitmap->width * 4 + p];
std::uint8_t g = bitmap->buffer[q * bitmap->width * 4 + p + 1];
std::uint8_t r = bitmap->buffer[q * bitmap->width * 4 + p + 2];
std::uint8_t a = bitmap->buffer[q * bitmap->width * 4 + p + 3];
unsigned c = static_cast<unsigned>((a << 24) | (b << 16) | (g << 8) | (r));
image(i, j) = c;
}
}
double scale = size/image.height();
int scaled_width = bitmap->width * scale;
int scaled_height = bitmap->rows * scale;
image_rgba8 scaled_image(scaled_width, scaled_height);
scale_image_agg(scaled_image, image , SCALING_BILINEAR , scale, scale, 0.0, 0.0, 1.0, 0);
composite(pixmap, scaled_image, comp_op, opacity, x, y);
}
template <typename T> template <typename T>
agg_text_renderer<T>::agg_text_renderer (pixmap_type & pixmap, agg_text_renderer<T>::agg_text_renderer (pixmap_type & pixmap,
halo_rasterizer_e rasterizer, halo_rasterizer_e rasterizer,
@ -211,11 +261,30 @@ void agg_text_renderer<T>::render(glyph_positions const& pos)
{ {
fill = glyph.properties.fill.rgba(); fill = glyph.properties.fill.rgba();
text_opacity = glyph.properties.text_opacity; text_opacity = glyph.properties.text_opacity;
FT_Glyph_Transform(glyph.image, &matrix, &start); FT_Glyph_Transform(glyph.image, &matrix, &start);
error = 0;
if ( glyph.image->format != FT_GLYPH_FORMAT_BITMAP )
{
error = FT_Glyph_To_Bitmap(&glyph.image ,FT_RENDER_MODE_NORMAL, 0, 1); error = FT_Glyph_To_Bitmap(&glyph.image ,FT_RENDER_MODE_NORMAL, 0, 1);
if (!error) }
if (error == 0)
{ {
FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(glyph.image); FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(glyph.image);
int pixel_mode = bit->bitmap.pixel_mode;
if (pixel_mode == 7)
{
int x = (start.x >> 6) + glyph.pos.x;
int y = height - (start.y >> 6) + glyph.pos.y;
composite_color_bitmap(pixmap_,
&bit->bitmap,
x,y, glyph.size,
text_opacity,
comp_op_);
}
else
{
composite_bitmap(pixmap_, composite_bitmap(pixmap_,
&bit->bitmap, &bit->bitmap,
fill, fill,
@ -224,6 +293,7 @@ void agg_text_renderer<T>::render(glyph_positions const& pos)
text_opacity, text_opacity,
comp_op_); comp_op_);
} }
}
FT_Done_Glyph(glyph.image); FT_Done_Glyph(glyph.image);
} }
@ -257,7 +327,6 @@ void grid_text_renderer<T>::render(glyph_positions const& pos, value_integer fea
error = FT_Glyph_To_Bitmap(&glyph.image, FT_RENDER_MODE_NORMAL, 0, 1); error = FT_Glyph_To_Bitmap(&glyph.image, FT_RENDER_MODE_NORMAL, 0, 1);
if (!error) if (!error)
{ {
FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(glyph.image); FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(glyph.image);
render_halo_id(&bit->bitmap, render_halo_id(&bit->bitmap,
feature_id, feature_id,

View file

@ -64,7 +64,7 @@ void text_line::add_glyph(glyph_info && glyph, double scale_factor_)
// Only add character spacing if the character is not a zero-width part of a cluster. // Only add character spacing if the character is not a zero-width part of a cluster.
width_ += advance + glyphs_.back().format->character_spacing * scale_factor_; width_ += advance + glyphs_.back().format->character_spacing * scale_factor_;
glyphs_width_ += advance; glyphs_width_ += advance;
space_count_++; ++space_count_;
} }
glyphs_.emplace_back(std::move(glyph)); glyphs_.emplace_back(std::move(glyph));
} }

View file

@ -57,7 +57,7 @@ SECTION("registration") {
mapnik::load_map_string(m3,"<Map font-directory=\"test/data/fonts/Noto/\"></Map>"); mapnik::load_map_string(m3,"<Map font-directory=\"test/data/fonts/Noto/\"></Map>");
REQUIRE( m3.get_font_memory_cache().size() == 0 ); REQUIRE( m3.get_font_memory_cache().size() == 0 );
REQUIRE( m3.load_fonts() ); REQUIRE( m3.load_fonts() );
REQUIRE( m3.get_font_memory_cache().size() == 1 ); REQUIRE( m3.get_font_memory_cache().size() == 2 );
std::vector<std::string> face_names; std::vector<std::string> face_names;
std::string foo("foo"); std::string foo("foo");

View file

@ -183,7 +183,16 @@ TEST_CASE("expressions")
auto val1 = eval("'♜♞♝♛♚♝♞♜'.replace('♞','♘')"); // ==> expected ♜♘♝♛♚♝♘♜ auto val1 = eval("'♜♞♝♛♚♝♞♜'.replace('♞','♘')"); // ==> expected ♜♘♝♛♚♝♘♜
TRY_CHECK(val0 == val1); TRY_CHECK(val0 == val1);
TRY_CHECK(val0.to_string() == val1.to_string()); // UTF-8 TRY_CHECK(val0.to_string() == val1.to_string()); // UTF-8
TRY_CHECK(val0.to_unicode() == val1.to_unicode()); // Unicode (UTF-16) TRY_CHECK(val0.to_unicode() == val1.to_unicode()); // Unicode
// \u+NNNN \U+NNNNNNNN \xNN\xNN
auto val3 = eval(u8"'\u262f\xF0\x9F\x8D\xB7'");
auto val4 = eval(u8"'\U0000262f\U0001F377'");
// UTF16 surrogate pairs work also ;)
// \ud83d\udd7a\ud83c\udffc => \U0001F57A\U0001F3FC works also
// TODO: find a way to enter UTF16 pairs
TRY_CHECK(val3 == val4);
TRY_CHECK(val3.to_string() == val4.to_string()); // UTF-8
TRY_CHECK(val3.to_unicode() == val4.to_unicode()); // Unicode
// following test will fail if boost_regex is built without ICU support (unpaired surrogates in output) // following test will fail if boost_regex is built without ICU support (unpaired surrogates in output)
TRY_CHECK(eval("[name].replace('(\\B)|( )',' ') ") == tr.transcode("Q u é b e c")); TRY_CHECK(eval("[name].replace('(\\B)|( )',' ') ") == tr.transcode("Q u é b e c"));

View file

@ -1,25 +1,101 @@
#include "catch.hpp" #include "catch.hpp"
#include <mapnik/text/icu_shaper.hpp> #include <mapnik/text/icu_shaper.hpp>
#include <mapnik/text/harfbuzz_shaper.hpp> #include <mapnik/text/harfbuzz_shaper.hpp>
#include <mapnik/text/font_library.hpp> #include <mapnik/text/font_library.hpp>
#include <mapnik/unicode.hpp>
TEST_CASE("shapers compile") { namespace {
mapnik::text_line line(0,0); void test_shaping( mapnik::font_set const& fontset, mapnik::face_manager& fm,
mapnik::text_itemizer itemizer; std::vector<std::pair<unsigned, unsigned>> const& expected, char const* str, bool debug = false)
{
mapnik::transcoder tr("utf8");
std::map<unsigned,double> width_map; std::map<unsigned,double> width_map;
mapnik::text_itemizer itemizer;
auto props = std::make_unique<mapnik::detail::evaluated_format_properties>();
props->fontset = fontset;
props->text_size = 32;
double scale_factor = 1; double scale_factor = 1;
auto ustr = tr.transcode(str);
auto length = ustr.length();
itemizer.add_text(ustr, props);
mapnik::text_line line(0, length);
mapnik::harfbuzz_shaper::shape_text(line, itemizer,
width_map,
fm,
scale_factor);
std::size_t index = 0;
for (auto const& g : line)
{
if (debug)
{
if (index++ > 0) std::cerr << ",";
std::cerr << "{" << g.glyph_index << ", " << g.char_index
//<< ", " << g.face->family_name() << ":" << g.face->style_name()
<< "}";
}
else
{
unsigned glyph_index, char_index;
CHECK(index < expected.size());
std::tie(glyph_index, char_index) = expected[index++];
REQUIRE(glyph_index == g.glyph_index);
REQUIRE(char_index == g.char_index);
}
}
}
}
TEST_CASE("shaping")
{
mapnik::freetype_engine::register_font("test/data/fonts/NotoSans-Regular.ttc");
mapnik::freetype_engine::register_fonts("test/data/fonts/Noto");
mapnik::font_set fontset("fontset");
for (auto const& name : mapnik::freetype_engine::face_names())
{
fontset.add_face_name(name);
}
mapnik::font_library fl; mapnik::font_library fl;
mapnik::freetype_engine::font_file_mapping_type font_file_mapping; mapnik::freetype_engine::font_file_mapping_type font_file_mapping;
mapnik::freetype_engine::font_memory_cache_type font_memory_cache; mapnik::freetype_engine::font_memory_cache_type font_memory_cache;
mapnik::face_manager fm(fl,font_file_mapping,font_memory_cache); mapnik::face_manager fm(fl, font_file_mapping, font_memory_cache);
mapnik::harfbuzz_shaper::shape_text(line,itemizer,
width_map, {
fm, std::vector<std::pair<unsigned, unsigned>> expected =
scale_factor); {{977, 0}, {1094, 3}, {1038, 4}, {1168, 4}, {9, 7}, {3, 8}, {11, 9}, {68, 10}, {69, 11}, {70, 12}, {12, 13}};
mapnik::icu_shaper::shape_text(line,itemizer, test_shaping(fontset, fm, expected, u8"སྤུ་ཧྲེང (abc)");
width_map, }
fm,
scale_factor); {
std::vector<std::pair<unsigned, unsigned>> expected =
{{977, 0}, {1094, 3}, {1038, 4}, {1168, 4}, {9, 7}, {3, 8}, {11, 9}, {0, 10}, {0, 11}, {0, 12}, {12, 13}};
test_shaping(fontset, fm, expected, u8"སྤུ་ཧྲེང (普兰镇)");
}
{
std::vector<std::pair<unsigned, unsigned>> expected =
{{68, 0}, {69, 1}, {70, 2}, {3, 3}, {11, 4}, {0, 5}, {0, 6}, {0, 7}, {12, 8}};
test_shaping(fontset, fm, expected, u8"abc (普兰镇)");
}
{
std::vector<std::pair<unsigned, unsigned>> expected =
{{68, 0}, {69, 1}, {70, 2}, {3, 3}, {11, 4}, {68, 5}, {69, 6}, {70, 7}, {12, 8}};
test_shaping(fontset, fm, expected, "abc (abc)");
}
{
// "ⵃⴰⵢ ⵚⵉⵏⴰⵄⵉ الحي الصناعي"
std::vector<std::pair<unsigned, unsigned>> expected =
{{0, 0}, {0, 1}, {0, 2}, {3, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7},
{0, 8}, {0, 9}, {3, 10}, {509, 22}, {481, 21}, {438, 20}, {503, 19},
{470, 18}, {496, 17}, {43, 16}, {3, 15}, {509, 14}, {454, 13}, {496, 12}, {43, 11}};
test_shaping(fontset, fm, expected, u8"ⵃⴰⵢ ⵚⵉⵏⴰⵄⵉ الحي الصناعي");
}
} }