Merge pull request #3636 from mapnik/color-emoji

Color emoji + updated harfbuzz_shaper and unicode_string_grammar
This commit is contained in:
Artem Pavlenko 2017-03-14 11:22:57 +00:00 committed by GitHub
commit a3d29ae52f
18 changed files with 377 additions and 121 deletions

2
.gitmodules vendored
View file

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

View file

@ -1787,7 +1787,7 @@ if not preconfigured:
# Common flags for g++/clang++ CXX compiler.
# 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
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']:
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')
if env['DEBUG_SANITIZE']:
env.Append(CXXFLAGS = ['-fsanitize=address'])
env.Append(CXXFLAGS = ['-fsanitize=address','-fno-omit-frame-pointer'])
env.Append(LINKFLAGS = ['-fsanitize=address'])

2
deps/mapbox/variant vendored

@ -1 +1 @@
Subproject commit d2588a8f1d6b5d480d228e6d8a906ce634bdea9a
Subproject commit 916139a2e51e125816efce6e19d428385601273f

View file

@ -24,6 +24,7 @@
#define MAPNIK_EXPRESSIONS_GRAMMAR_X3_DEF_HPP
#include <mapnik/expression_grammar_x3.hpp>
#include <mapnik/json/unicode_string_grammar_x3_def.hpp>
#include <mapnik/expression_node.hpp>
#include <mapnik/function_call.hpp>
#include <mapnik/unicode.hpp>
@ -65,6 +66,15 @@ namespace mapnik { namespace grammar {
using x3::alnum;
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)
{
_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");
// strings
auto const single_quoted_string = x3::rule<class single_quoted_string, std::string> {} = lit('\'') >> no_skip[*(unesc_char | ("\\x" > hex2) | (char_ - '\''))] > '\'';
auto const double_quoted_string = x3::rule<class double_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('\'')
>> 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 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
#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 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_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)
{
std::string & utf8 = _val(ctx);
@ -85,29 +105,35 @@ using x3::eol;
using x3::no_skip;
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 {};
// start rule
unicode_string_grammar_type const unicode_string("Unicode String");
// rules
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 utf16_string_def = lit('u') > hex4 > *(lit("\\u") > hex4)
;
auto escaped_unicode_def =
(lit('x') > hex2[push_char])
|
utf16_string[push_utf16]
|
(lit('U') > hex8[push_utf8])
;
auto const escaped_def = lit('\\') >
((lit('x') > hex2[push_char])
|
(lit('u') > hex4[push_utf8])
|
(lit('U') > hex8[push_utf8])
(escaped_unicode[append]
|
char_("0abtnvfre\"/\\N_LP \t")[push_esc]
|
eol) // continue to next line
;
auto const double_quoted_def = lit('"') > no_skip[*(escaped[append] | (~char_('"'))[append])] > lit('"');
#pragma GCC diagnostic push
@ -116,7 +142,9 @@ auto const double_quoted_def = lit('"') > no_skip[*(escaped[append] | (~char_('"
BOOST_SPIRIT_DEFINE(
unicode_string,
double_quoted,
escaped
escaped,
escaped_unicode,
utf16_string
);
#pragma GCC diagnostic pop

View file

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

View file

@ -80,17 +80,16 @@ static void shape_text(text_line & line,
line.reserve(length);
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));
mapnik::value_unicode_string const& text = itemizer.text();
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);
double size = text_item.format_->text_size * scale_factor;
face_set->set_unscaled_character_sizes();
std::size_t num_faces = face_set->size();
std::size_t pos = 0;
font_feature_settings const& ff_settings = text_item.format_->ff_settings;
int ff_count = safe_cast<int>(ff_settings.count());
@ -101,83 +100,115 @@ static void shape_text(text_line & line,
hb_glyph_info_t glyph;
hb_glyph_position_t position;
};
// this table is filled with information for rendering each glyph, so that
// several font faces can be used in a single text_item
std::vector<glyph_face_info> glyphinfos;
unsigned valid_glyphs = 0;
std::size_t pos = 0;
std::vector<std::vector<glyph_face_info>> glyphinfos;
glyphinfos.resize(text.length());
for (auto const& face : *face_set)
{
++pos;
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_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_font_t *font(hb_ft_font_create(face->get_face(), nullptr));
// 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);
#endif
#endif
#endif
hb_shape(font, buffer.get(), ff_settings.get_features(), ff_count);
hb_font_destroy(font);
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
if (num_glyphs > glyphinfos.size())
unsigned cluster = 0;
bool in_cluster = false;
std::vector<unsigned> clusters;
for (unsigned i = 0; i < num_glyphs; ++i)
{
glyphinfos.resize(num_glyphs);
}
hb_glyph_info_t *glyphs = hb_buffer_get_glyph_infos(buffer.get(), nullptr);
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 (glyphs[i].codepoint)
if (i == 0)
{
if (!glyphinfos[i].glyph.codepoint)
{
++valid_glyphs;
}
glyphinfos[i] = { face, glyphs[i], positions[i] };
cluster = glyphs[0].cluster;
clusters.push_back(cluster);
}
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
continue;
}
double max_glyph_height = 0;
for (unsigned i=0; i<num_glyphs; ++i)
for (auto const& c_id : clusters)
{
auto& gpos = positions[i];
auto& glyph = glyphs[i];
face_ptr theface = face;
if (glyphinfos[i].glyph.codepoint)
auto const& c = glyphinfos[c_id];
for (auto const& info : c)
{
gpos = glyphinfos[i].position;
glyph = glyphinfos[i].glyph;
theface = glyphinfos[i].face;
}
unsigned char_index = glyph.cluster;
glyph_info g(glyph.codepoint,char_index,text_item.format_);
if (theface->glyph_dimensions(g))
{
g.face = theface;
g.scale_multiplier = size / theface->get_face()->units_per_EM;
//Overwrite default advance with better value provided by HarfBuzz
g.unscaled_advance = gpos.x_advance;
g.offset.set(gpos.x_offset * g.scale_multiplier, gpos.y_offset * g.scale_multiplier);
double tmp_height = g.height();
if (tmp_height > max_glyph_height) max_glyph_height = tmp_height;
width_map[char_index] += g.advance();
line.add_glyph(std::move(g), scale_factor);
auto const& gpos = info.position;
auto const& glyph = info.glyph;
unsigned char_index = glyph.cluster;
glyph_info g(glyph.codepoint,char_index,text_item.format_);
if (info.glyph.codepoint != 0) g.face = info.face;
else g.face = face;
if (g.face->glyph_dimensions(g))
{
g.scale_multiplier = g.face->get_face()->units_per_EM > 0 ?
(size / g.face->get_face()->units_per_EM) : (size / 2048.0) ;
//Overwrite default advance with better value provided by HarfBuzz
g.unscaled_advance = gpos.x_advance;
g.offset.set(gpos.x_offset * g.scale_multiplier, gpos.y_offset * g.scale_multiplier);
double tmp_height = g.height();
if (g.face->is_color())
{
tmp_height = size;
}
if (tmp_height > max_glyph_height) max_glyph_height = tmp_height;
width_map[char_index] += g.advance();
line.add_glyph(std::move(g), scale_factor);
}
}
}
line.update_max_char_height(max_glyph_height);

View file

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

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
// it needs to be implemented with no overhead.
std::map<unsigned, double> width_map_;
double width_;
double height_;
double width_ = 0.0;
double height_ = 0.0;
unsigned glyphs_count_;
// output
@ -173,7 +173,7 @@ private:
bool rotate_displacement_ = false;
double text_ratio_ = 0.0;
pixel_position displacement_ = {0,0};
box2d<double> bounds_;
box2d<double> bounds_ = {0, 0, 0, 0};
// children
text_layout_vector child_layout_list_;

View file

@ -26,10 +26,11 @@
#include <mapnik/wkt/wkt_grammar_x3.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
// fixes gcc6 compilation issue with boost 1_61 and boost_1_62
// fixes gcc6 compilation issue
namespace boost { namespace spirit { namespace x3 { namespace traits {
template <typename T, typename Attribute, typename Enable>
struct is_substitute<T&, Attribute&, Enable>
: is_substitute<T, Attribute, Enable> {};

View file

@ -29,6 +29,7 @@
extern "C"
{
#include FT_GLYPH_H
#include FT_TRUETYPE_TABLES_H
}
#pragma GCC diagnostic pop
@ -37,16 +38,23 @@ namespace mapnik
{
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)
{
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()
{
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
@ -54,11 +62,14 @@ bool font_face::glyph_dimensions(glyph_info & glyph) const
FT_Vector pen;
pen.x = 0;
pen.y = 0;
if (color_font_) FT_Select_Size(face_, 0);
FT_Set_Transform(face_, 0, &pen);
if (FT_Load_Glyph(face_, glyph.glyph_index, FT_LOAD_NO_HINTING))
FT_Int32 load_flags = FT_LOAD_DEFAULT | 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;
}
FT_Glyph image;

View file

@ -25,6 +25,8 @@
#include <mapnik/grid/grid.hpp>
#include <mapnik/text/text_properties.hpp>
#include <mapnik/font_engine_freetype.hpp>
#include <mapnik/image_compositing.hpp>
#include <mapnik/image_scaling.hpp>
#include <mapnik/text/face.hpp>
#include <mapnik/image_util.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)
{
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.xy = 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.y = static_cast<FT_Pos>(pos.y * 64);
FT_Face face = glyph.face->get_face();
FT_Set_Transform(face, &matrix, &pen);
error = FT_Load_Glyph(face, glyph.glyph_index, FT_LOAD_NO_HINTING);
error = FT_Load_Glyph(face, glyph.glyph_index, load_flags);
if (error) continue;
FT_Glyph image;
error = FT_Get_Glyph(face->glyph, &image);
if (error) continue;
glyphs_.emplace_back(image, *glyph.format);
glyphs_.emplace_back(image, *glyph.format, pos, size);
}
}
@ -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)
{
unsigned gray=bitmap->buffer[q*bitmap->width+p];
unsigned gray = bitmap->buffer[q * bitmap->width + p];
if (gray)
{
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>
agg_text_renderer<T>::agg_text_renderer (pixmap_type & pixmap,
halo_rasterizer_e rasterizer,
@ -211,18 +261,38 @@ void agg_text_renderer<T>::render(glyph_positions const& pos)
{
fill = glyph.properties.fill.rgba();
text_opacity = glyph.properties.text_opacity;
FT_Glyph_Transform(glyph.image, &matrix, &start);
error = FT_Glyph_To_Bitmap(&glyph.image ,FT_RENDER_MODE_NORMAL, 0, 1);
if (!error)
error = 0;
if ( glyph.image->format != FT_GLYPH_FORMAT_BITMAP )
{
error = FT_Glyph_To_Bitmap(&glyph.image ,FT_RENDER_MODE_NORMAL, 0, 1);
}
if (error == 0)
{
FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(glyph.image);
composite_bitmap(pixmap_,
&bit->bitmap,
fill,
bit->left,
height - bit->top,
text_opacity,
comp_op_);
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_,
&bit->bitmap,
fill,
bit->left,
height - bit->top,
text_opacity,
comp_op_);
}
}
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);
if (!error)
{
FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(glyph.image);
render_halo_id(&bit->bitmap,
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.
width_ += advance + glyphs_.back().format->character_spacing * scale_factor_;
glyphs_width_ += advance;
space_count_++;
++space_count_;
}
glyphs_.emplace_back(std::move(glyph));
}

@ -1 +1 @@
Subproject commit 5ad14e6bdf2c5e6babd8ac04aa057ed6c67ac617
Subproject commit e61692e688c39e737fb73394839bcffe90f6fe78

@ -1 +1 @@
Subproject commit f9a3c203b3657dd1748d788304f83a26efb6439f
Subproject commit 7acfb4c48d688463c1fa3c3bade27afde9563269

View file

@ -57,7 +57,7 @@ SECTION("registration") {
mapnik::load_map_string(m3,"<Map font-directory=\"test/data/fonts/Noto/\"></Map>");
REQUIRE( m3.get_font_memory_cache().size() == 0 );
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::string foo("foo");

View file

@ -183,7 +183,16 @@ TEST_CASE("expressions")
auto val1 = eval("'♜♞♝♛♚♝♞♜'.replace('♞','♘')"); // ==> expected ♜♘♝♛♚♝♘♜
TRY_CHECK(val0 == val1);
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)
TRY_CHECK(eval("[name].replace('(\\B)|( )',' ') ") == tr.transcode("Q u é b e c"));

View file

@ -1,25 +1,101 @@
#include "catch.hpp"
#include <mapnik/text/icu_shaper.hpp>
#include <mapnik/text/harfbuzz_shaper.hpp>
#include <mapnik/text/font_library.hpp>
#include <mapnik/unicode.hpp>
TEST_CASE("shapers compile") {
namespace {
mapnik::text_line line(0,0);
mapnik::text_itemizer itemizer;
void test_shaping( mapnik::font_set const& fontset, mapnik::face_manager& fm,
std::vector<std::pair<unsigned, unsigned>> const& expected, char const* str, bool debug = false)
{
mapnik::transcoder tr("utf8");
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;
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::freetype_engine::font_file_mapping_type font_file_mapping;
mapnik::freetype_engine::font_memory_cache_type font_memory_cache;
mapnik::face_manager fm(fl,font_file_mapping,font_memory_cache);
mapnik::harfbuzz_shaper::shape_text(line,itemizer,
width_map,
fm,
scale_factor);
mapnik::icu_shaper::shape_text(line,itemizer,
width_map,
fm,
scale_factor);
mapnik::face_manager fm(fl, font_file_mapping, font_memory_cache);
{
std::vector<std::pair<unsigned, unsigned>> expected =
{{977, 0}, {1094, 3}, {1038, 4}, {1168, 4}, {9, 7}, {3, 8}, {11, 9}, {68, 10}, {69, 11}, {70, 12}, {12, 13}};
test_shaping(fontset, fm, expected, u8"སྤུ་ཧྲེང (abc)");
}
{
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"ⵃⴰⵢ ⵚⵉⵏⴰⵄⵉ الحي الصناعي");
}
}