Add color emoji

This commit is contained in:
Benau 2019-06-10 10:35:42 +08:00
parent 89e3bcd11b
commit 28cc2838a8
13 changed files with 205 additions and 81 deletions

View File

@ -544,7 +544,8 @@
using industry standard nowadays...
-->
<fonts-list normal-ttf="Cantarell-Regular.otf wqy-microhei.ttf NotoSansThai-Regular.ttf NotoSansHebrew-Regular.ttf NotoNaskhArabicUI-Regular.ttf"
digit-ttf="SigmarOne.otf" />
digit-ttf="SigmarOne.otf"
color-emoji-ttf="NotoColorEmoji.ttf"/>
<!-- Maximum bones from all animated meshes in each frame to be uploaded for
hardware skinning, For gles 3.0 the specification guarantees at least 2048, for

View File

@ -3,7 +3,7 @@ wqyMicroHei is released under the GPLv3 with font embedding exception and Apache
Copyright (C) 2008-2009 The WenQuanYi Project Board of Trustees
Copyright (C) 2007 Google Corporation
Noto Sans Thai, Noto Sans Hebrew and Noto Naskh Arabic UI are released under SIL open font license 1.1 ( https://www.google.com/get/noto/ )
Noto Sans Thai, Noto Sans Hebrew, Noto Naskh Arabic UI and Noto Color Emoji are released under SIL open font license 1.1 ( https://www.google.com/get/noto/ )
Noto is a trademark of Google Inc. Noto fonts are open source. All Noto fonts are published under the SIL Open Font License, Version 1.1. Language data and some sample texts are from the Unicode CLDR project.

BIN
data/ttf/NotoColorEmoji.ttf Normal file

Binary file not shown.

View File

@ -23,7 +23,8 @@ GLF_RTL_LINE = 1, /* This line from this glyph is RTL. */
GLF_RTL_CHAR = 2, /* This character(s) from this glyph is RTL. */
GLF_BREAKABLE = 4, /* This glyph is breakable when line breaking. */
GLF_QUICK_DRAW = 8, /* This glyph is not created by libraqm, which get x_advance_x directly from font. */
GLF_NEWLINE = 16 /* This glyph will start a newline. */
GLF_NEWLINE = 16, /* This glyph will start a newline. */
GLF_COLORED = 32 /* This glyph is a colored one (for example emoji). */
};
enum GlyphLayoutDraw

View File

@ -505,6 +505,7 @@ void STKConfig::getAllData(const XMLNode * root)
{
fonts_list->get("normal-ttf", &m_normal_ttf);
fonts_list->get("digit-ttf", &m_digit_ttf );
fonts_list->get("color-emoji-ttf", &m_color_emoji_ttf);
}
if (const XMLNode *skinning = root->getNode("skinning"))

View File

@ -209,6 +209,7 @@ public:
/** Lists of TTF files used in STK. */
std::vector<std::string> m_normal_ttf;
std::vector<std::string> m_digit_ttf;
std::string m_color_emoji_ttf;
/** Configurable values used in SmoothNetworkBody class. */
float m_snb_min_adjust_length, m_snb_max_adjust_length,

View File

@ -17,6 +17,8 @@
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "font/bold_face.hpp"
#include "font/font_manager.hpp"
#include "font/regular_face.hpp"
// ----------------------------------------------------------------------------
/** Constructor of BoldFace.
@ -33,10 +35,10 @@ void BoldFace::init()
// Reserve some space for characters added later
m_font_max_height = m_glyph_max_height * 3 / 2;
/* Use FT_Outline_Embolden for now, no more fallback font
setFallbackFont(font_manager->getFont<RegularFace>());
setFallbackFontScale(2.0f);*/
// Fallback font for emoji
RegularFace* rf = font_manager->getFont<RegularFace>();
setFallbackFont(rf);
setFallbackFontScale((float)getDPI() / (float)rf->getDPI());
} // init
// ----------------------------------------------------------------------------

View File

@ -368,8 +368,11 @@ void FontManager::shape(const std::u32string& text,
break;
}
}
if (!FT_HAS_COLOR(cur_face))
{
checkFTError(FT_Set_Pixel_Sizes(cur_face, 0,
m_shaping_dpi), "setting DPI");
}
if (!raqm_set_freetype_face_range(rq, cur_face, i, 1))
{
Log::error("FontManager",
@ -409,6 +412,8 @@ void FontManager::shape(const std::u32string& text,
gl.flags |= gui::GLF_RTL_CHAR;
if (breakable[glyphs[idx].cluster])
gl.flags |= gui::GLF_BREAKABLE;
if (FT_HAS_COLOR(glyphs[idx].ftface))
gl.flags |= gui::GLF_COLORED;
cur_line.push_back(gl);
}
// Sort glyphs in logical order
@ -489,6 +494,33 @@ void FontManager::initGlyphLayouts(const core::stringw& text,
shape(StringUtils::wideToUtf32(text), cached_gls);
gls = cached_gls;
} // initGlyphLayouts
// ----------------------------------------------------------------------------
FT_Face FontManager::loadColorEmoji()
{
if (stk_config->m_color_emoji_ttf.empty())
return NULL;
FT_Face face = NULL;
const std::string loc = file_manager->getAssetChecked(FileManager::TTF,
stk_config->m_color_emoji_ttf.c_str(), true);
font_manager->checkFTError(FT_New_Face(
m_ft_library, loc.c_str(), 0, &face), loc + " is loaded");
if (!FT_HAS_COLOR(face) || face->num_fixed_sizes == 0)
{
Log::error("FontManager", "Bad %s color emoji, ignored.",
stk_config->m_color_emoji_ttf.c_str());
checkFTError(FT_Done_Face(face), "removing faces for emoji");
return NULL;
}
// Use the largest size available, it will be scaled to regular face ttf
// when loading the glyph, so it can reduce the blurring effect
m_shaping_dpi = face->available_sizes[face->num_fixed_sizes - 1].height;
checkFTError(FT_Select_Size(face, face->num_fixed_sizes - 1),
"setting color emoji size");
return face;
} // loadColorEmoji
#endif
// ----------------------------------------------------------------------------
@ -499,11 +531,25 @@ void FontManager::loadFonts()
#ifndef SERVER_ONLY
// First load the TTF files required by each font
std::vector<FT_Face> normal_ttf = loadTTF(stk_config->m_normal_ttf);
std::vector<FT_Face> bold_ttf = normal_ttf;
if (!ProfileWorld::isNoGraphics())
{
assert(!normal_ttf.empty());
FT_Face color_emoji = loadColorEmoji();
if (!normal_ttf.empty() && color_emoji != NULL)
{
// Put color emoji after 1st default font so can use it before wqy
// font
normal_ttf.insert(normal_ttf.begin() + 1, color_emoji);
// We don't use color emoji in bold font
bold_ttf.insert(bold_ttf.begin() + 1, NULL);
}
// We use 16bit face idx in GlyphLayout class
if (normal_ttf.size() > 65535)
normal_ttf.resize(65535);
for (uint16_t i = 0; i < normal_ttf.size(); i++)
m_ft_faces_to_index[normal_ttf[i]] = i;
}
std::vector<FT_Face> digit_ttf = loadTTF(stk_config->m_digit_ttf);
if (!digit_ttf.empty())
@ -522,7 +568,7 @@ void FontManager::loadFonts()
BoldFace* bold = new BoldFace();
#ifndef SERVER_ONLY
bold->getFaceTTF()->loadTTF(normal_ttf);
bold->getFaceTTF()->loadTTF(bold_ttf);
#endif
bold->init();
m_fonts.push_back(bold);

View File

@ -118,6 +118,8 @@ public:
// ------------------------------------------------------------------------
std::vector<FT_Face> loadTTF(const std::vector<std::string>& ttf_list);
// ------------------------------------------------------------------------
FT_Face loadColorEmoji();
// ------------------------------------------------------------------------
unsigned getShapingDPI() const { return m_shaping_dpi; }
// ------------------------------------------------------------------------
void shape(const std::u32string& text,

View File

@ -154,7 +154,7 @@ void FontWithFace::createNewGlyphPage()
return;
uint8_t* data = new uint8_t[getGlyphPageSize() * getGlyphPageSize() *
(CVS->isARBTextureSwizzleUsable() ? 1 : 4)]();
(CVS->isARBTextureSwizzleUsable() && !useColorGlyphPage() ? 1 : 4)]();
#else
uint8_t* data = NULL;
#endif
@ -165,7 +165,7 @@ void FontWithFace::createNewGlyphPage()
StringUtils::toString(m_spritebank->getTextureCount()),
getGlyphPageSize(),
#ifndef SERVER_ONLY
CVS->isARBTextureSwizzleUsable()
CVS->isARBTextureSwizzleUsable() && !useColorGlyphPage()
#else
false
#endif
@ -189,6 +189,13 @@ void FontWithFace::insertGlyph(unsigned font_number, unsigned glyph_index)
FT_Face cur_face = m_face_ttf->getFace(font_number);
FT_GlyphSlot slot = cur_face->glyph;
if (FT_HAS_COLOR(cur_face))
{
font_manager->checkFTError(FT_Load_Glyph(cur_face, glyph_index,
FT_LOAD_DEFAULT | FT_LOAD_COLOR), "loading a glyph");
}
else
{
// Same face may be shared across the different FontWithFace,
// so reset dpi each time
font_manager->checkFTError(FT_Set_Pixel_Sizes(cur_face, 0, getDPI()),
@ -200,12 +207,23 @@ void FontWithFace::insertGlyph(unsigned font_number, unsigned glyph_index)
font_manager->checkFTError(shapeOutline(&(slot->outline)),
"shaping outline");
font_manager->checkFTError(FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL),
"rendering a glyph to bitmap");
font_manager->checkFTError(FT_Render_Glyph(slot,
FT_RENDER_MODE_NORMAL), "rendering a glyph to bitmap");
}
// Convert to an anti-aliased bitmap
FT_Bitmap* bits = &(slot->bitmap);
core::dimension2du texture_size(bits->width + 1, bits->rows + 1);
float scale_ratio = 1.0f;
unsigned cur_glyph_width = bits->width;
unsigned cur_glyph_height = bits->rows;
if (bits->pixel_mode == FT_PIXEL_MODE_BGRA)
{
scale_ratio =
(float)getDPI() / (float)font_manager->getShapingDPI();
cur_glyph_width = (unsigned)(bits->width * scale_ratio);
cur_glyph_height = (unsigned)(bits->rows * scale_ratio);
}
core::dimension2du texture_size(cur_glyph_width + 1, cur_glyph_height + 1);
if ((m_used_width + texture_size.Width > getGlyphPageSize() &&
m_used_height + m_current_height + texture_size.Height >
getGlyphPageSize()) ||
@ -228,8 +246,9 @@ void FontWithFace::insertGlyph(unsigned font_number, unsigned glyph_index)
{
video::ITexture* tex = m_spritebank->getTexture(cur_tex);
glBindTexture(GL_TEXTURE_2D, tex->getOpenGLTextureName());
assert(bits->pixel_mode == FT_PIXEL_MODE_GRAY);
if (CVS->isARBTextureSwizzleUsable())
if (bits->pixel_mode == FT_PIXEL_MODE_GRAY)
{
if (CVS->isARBTextureSwizzleUsable() && !useColorGlyphPage())
{
glTexSubImage2D(GL_TEXTURE_2D, 0, m_used_width, m_used_height,
bits->width, bits->rows, GL_RED, GL_UNSIGNED_BYTE,
@ -247,6 +266,39 @@ void FontWithFace::insertGlyph(unsigned font_number, unsigned glyph_index)
image_data);
delete[] image_data;
}
}
else if (bits->pixel_mode == FT_PIXEL_MODE_BGRA)
{
assert(useColorGlyphPage());
// Scale it to normal font dpi
video::IImage* unscaled = irr_driver->getVideoDriver()
->createImageFromData(video::ECF_A8R8G8B8,
{ bits->width, bits->rows },
bits->buffer, true/*ownForeignMemory*/, false/*deleteMemory*/);
assert(unscaled);
video::IImage* scaled = irr_driver
->getVideoDriver()->createImage(video::ECF_A8R8G8B8,
{ cur_glyph_width , cur_glyph_height});
assert(scaled);
unscaled->copyToScalingBoxFilter(scaled);
uint8_t* scaled_data = (uint8_t*)scaled->lock();
for (unsigned int i = 0; i < cur_glyph_width * cur_glyph_height;
i++)
{
uint8_t tmp_val = scaled_data[i * 4];
scaled_data[i * 4] = scaled_data[i * 4 + 2];
scaled_data[i * 4 + 2] = tmp_val;
}
glTexSubImage2D(GL_TEXTURE_2D, 0, m_used_width, m_used_height,
cur_glyph_width, cur_glyph_height, GL_RGBA, GL_UNSIGNED_BYTE,
scaled_data);
unscaled->drop();
scaled->drop();
}
else
{
assert(false && "Invalid pixel mode");
}
if (tex->hasMipMaps())
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
@ -256,7 +308,7 @@ void FontWithFace::insertGlyph(unsigned font_number, unsigned glyph_index)
gui::SGUISpriteFrame f;
gui::SGUISprite s;
core::rect<s32> rectangle(m_used_width, m_used_height,
m_used_width + bits->width, m_used_height + bits->rows);
m_used_width + cur_glyph_width, m_used_height + cur_glyph_height);
f.rectNumber = m_spritebank->getPositions().size();
f.textureNumber = cur_tex;
@ -268,11 +320,14 @@ void FontWithFace::insertGlyph(unsigned font_number, unsigned glyph_index)
// Save glyph metrics
FontArea a;
a.advance_x = cur_face->glyph->advance.x / BEARING;
a.bearing_x = cur_face->glyph->metrics.horiBearingX / BEARING;
const int cur_height = (cur_face->glyph->metrics.height / BEARING);
a.advance_x = (int)
(cur_face->glyph->advance.x / BEARING * scale_ratio);
a.bearing_x = (int)
(cur_face->glyph->metrics.horiBearingX / BEARING * scale_ratio);
const int cur_height =
(int)(cur_face->glyph->metrics.height / BEARING * scale_ratio);
const int cur_offset_y = cur_height -
(cur_face->glyph->metrics.horiBearingY / BEARING);
(int)(cur_face->glyph->metrics.horiBearingY / BEARING * scale_ratio);
a.offset_y = m_glyph_max_height - cur_height + cur_offset_y;
a.offset_y_bt = -cur_offset_y;
a.spriteno = f.rectNumber;
@ -571,7 +626,7 @@ void FontWithFace::render(const std::vector<gui::GlyphLayout>& gl,
// Collect character locations
const unsigned int text_size = gl.size();
core::array<s32> indices(text_size);
std::vector<std::pair<s32, bool> > indices;
core::array<core::position2d<float>> offsets(text_size);
std::vector<bool> fallback(text_size);
core::array<core::position2d<float>> gld_offsets;
@ -675,7 +730,8 @@ void FontWithFace::render(const std::vector<gui::GlyphLayout>& gl,
offset.Y -= glyph_offset_y;
}
indices.push_back(area->spriteno);
indices.emplace_back(area->spriteno,
(glyph_layout.flags & gui::GLF_COLORED) != 0);
if ((glyph_layout.flags & gui::GLF_QUICK_DRAW) != 0)
{
offset.X += glyph_layout.x_advance * scale;
@ -740,10 +796,10 @@ void FontWithFace::render(const std::vector<gui::GlyphLayout>& gl,
for (int n = 0; n < indice_amount; n++)
{
const int sprite_id = indices[n];
const int sprite_id = indices[n].first;
if (!fallback[n] && (sprite_id < 0 || sprite_id >= sprite_amount))
continue;
if (indices[n] == -1) continue;
if (indices[n].first == -1) continue;
const int tex_id = (fallback[n] ?
(*fallback_sprites)[sprite_id].Frames[0].textureNumber :
@ -783,12 +839,33 @@ void FontWithFace::render(const std::vector<gui::GlyphLayout>& gl,
}
}
std::array<video::SColor, 4> white =
{ {
video::SColor(-1), video::SColor(-1),
video::SColor(-1), video::SColor(-1)
} };
video::SColor top = GUIEngine::getSkin()->getColor("font::top");
video::SColor bottom = GUIEngine::getSkin()->getColor("font::bottom");
top.setAlpha(color.getAlpha());
bottom.setAlpha(color.getAlpha());
std::array<video::SColor, 4> title_colors;
if (CVS->isGLSL())
title_colors = { { top, bottom, top, bottom } };
else
title_colors = { { bottom, top, top, bottom } };
video::SColor text_marked = GUIEngine::getSkin()->getColor(
"text_field::background_marked");
video::SColor text_neutral = GUIEngine::getSkin()->getColor(
"text_field::neutral");
for (int n = 0; n < indice_amount; n++)
{
const int sprite_id = indices[n];
const int sprite_id = indices[n].first;
if (!fallback[n] && (sprite_id < 0 || sprite_id >= sprite_amount))
continue;
if (indices[n] == -1) continue;
if (indices[n].first == -1) continue;
const int tex_id = (fallback[n] ?
(*fallback_sprites)[sprite_id].Frames[0].textureNumber :
@ -810,44 +887,33 @@ void FontWithFace::render(const std::vector<gui::GlyphLayout>& gl,
m_fallback_font->m_spritebank->getTexture(tex_id) :
m_spritebank->getTexture(tex_id));
if (fallback[n] || isBold())
const bool is_colored = indices[n].second;
if (isBold())
{
video::SColor top = GUIEngine::getSkin()->getColor("font::top");
video::SColor bottom = GUIEngine::getSkin()
->getColor("font::bottom");
top.setAlpha(color.getAlpha());
bottom.setAlpha(color.getAlpha());
std::array<video::SColor, 4> title_colors;
if (CVS->isGLSL())
{
title_colors = { { top, bottom, top, bottom } };
}
else
{
title_colors = { { bottom, top, top, bottom } };
}
if (char_collector != NULL)
{
char_collector->collectChar(texture, dest, source,
title_colors.data());
is_colored ? white.data() : title_colors.data());
}
else
{
draw2DImage(texture, dest, source, clip, title_colors.data(),
true);
draw2DImage(texture, dest, source, clip,
is_colored ? white.data() : title_colors.data(), true);
}
}
else
{
if (char_collector != NULL)
{
video::SColor colors[] = {color, color, color, color};
char_collector->collectChar(texture, dest, source, colors);
std::array<video::SColor, 4> single_color =
{ {color, color, color, color} };
char_collector->collectChar(texture, dest, source,
is_colored ? white.data() : single_color.data());
}
else
{
draw2DImage(texture, dest, source, clip, color, true);
draw2DImage(texture, dest, source, clip,
is_colored ? video::SColor(-1) : color, true);
}
}
}
@ -858,18 +924,16 @@ void FontWithFace::render(const std::vector<gui::GlyphLayout>& gl,
core::rect<s32> gld((s32)gld_offsets[i].X, (s32)gld_offsets[i].Y,
(s32)gld_offsets[i + 1].X,
(s32)(gld_offsets[i + 1].Y + m_font_max_height * scale));
GL32_draw2DRectangle(GUIEngine::getSkin()->getColor(
"text_field::background_marked"), gld, clip);
GL32_draw2DRectangle(text_marked, gld, clip);
}
else if (df_used == gui::GLD_COMPOSING)
{
float line1 = m_font_max_height * scale * 0.88f;
float line2 = m_font_max_height * scale * 0.92f;
core::rect<s32> gld((s32)gld_offsets[i].X, (s32)gld_offsets[i].Y + line1,
(s32)gld_offsets[i + 1].X,
core::rect<s32> gld((s32)gld_offsets[i].X,
(s32)gld_offsets[i].Y + line1, (s32)gld_offsets[i + 1].X,
(s32)(gld_offsets[i + 1].Y + line2));
GL32_draw2DRectangle(GUIEngine::getSkin()->getColor(
"text::neutral"), gld, clip);
GL32_draw2DRectangle(text_neutral, gld, clip);
}
}
#endif

View File

@ -305,6 +305,8 @@ public:
virtual bool disableTextShaping() const { return false; }
// ------------------------------------------------------------------------
float getInverseShaping() const { return m_inverse_shaping; }
// ------------------------------------------------------------------------
virtual bool useColorGlyphPage() const { return false; }
}; // FontWithFace
#endif

View File

@ -43,6 +43,8 @@ public:
virtual void init() OVERRIDE;
// ------------------------------------------------------------------------
virtual void reset() OVERRIDE;
// ------------------------------------------------------------------------
virtual bool useColorGlyphPage() const OVERRIDE { return true; }
}; // RegularFace

View File

@ -1031,6 +1031,7 @@ void CGUIEditBox::setText(const wchar_t* text)
updateGlyphLayouts();
m_mark_begin = m_mark_end = m_cursor_pos = getTextCount();
m_scroll_pos = 0;
calculateScrollPos();
#ifdef ANDROID
if (GUIEngine::ScreenKeyboard::shouldUseScreenKeyboard() &&
irr_driver->getDevice()->hasOnScreenKeyboard() &&
@ -1502,6 +1503,7 @@ void CGUIEditBox::fromAndroidEditText(const std::u32string& text, int start,
m_composing_start = composing_start;
m_composing_end = composing_end;
}
calculateScrollPos();
}
void CGUIEditBox::updateGlyphLayouts()