supertuxkart-serenity/src/font/font_manager.cpp
2023-06-12 16:31:15 +02:00

918 lines
33 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "font/font_manager.hpp"
#include "io/file_manager.hpp"
#include "font/bold_face.hpp"
#include "font/digit_face.hpp"
#include "font/face_ttf.hpp"
#include "font/regular_face.hpp"
#include "guiengine/engine.hpp"
#include "guiengine/skin.hpp"
#include "states_screens/state_manager.hpp"
#include "utils/string_utils.hpp"
#include "utils/translation.hpp"
#ifndef SERVER_ONLY
#include <harfbuzz/hb-ft.h>
extern "C"
{
#include <SheenBidi.h>
}
#endif
FontManager *font_manager = NULL;
// ----------------------------------------------------------------------------
/** Constructor. It will initialize the \ref m_ft_library.
*/
FontManager::FontManager()
{
#ifndef SERVER_ONLY
m_has_color_emoji = false;
m_ft_library = NULL;
m_digit_face = NULL;
m_shaping_dpi = 128;
m_hb_buffer = NULL;
if (GUIEngine::isNoGraphics())
return;
m_hb_buffer = hb_buffer_create();
checkFTError(FT_Init_FreeType(&m_ft_library), "loading freetype library");
#endif
} // FontManager
// ----------------------------------------------------------------------------
/** Destructor. Clears all fonts and related stuff.
*/
FontManager::~FontManager()
{
for (unsigned int i = 0; i < m_fonts.size(); i++)
delete m_fonts[i];
m_fonts.clear();
#ifndef SERVER_ONLY
if (GUIEngine::isNoGraphics())
return;
hb_buffer_destroy(m_hb_buffer);
for (hb_font_t* font : m_hb_fonts)
hb_font_destroy(font);
for (unsigned int i = 0; i < m_faces.size(); i++)
checkFTError(FT_Done_Face(m_faces[i]), "removing faces for shaping");
if (m_digit_face != NULL)
checkFTError(FT_Done_Face(m_digit_face), "removing digit face");
checkFTError(FT_Done_FreeType(m_ft_library), "removing freetype library");
#endif
} // ~FontManager
#ifndef SERVER_ONLY
// ----------------------------------------------------------------------------
/** Load all TTFs from a list to m_faces.
* \param ttf_list List of TTFs to be loaded.
*/
std::vector<FT_Face>
FontManager::loadTTF(const std::vector<std::string>& ttf_list)
{
std::vector <FT_Face> ret;
if (GUIEngine::isNoGraphics())
return ret;
for (const std::string& font : ttf_list)
{
FT_Face face = NULL;
font_manager->checkFTError(FT_New_Face(
m_ft_library, font.c_str(), 0, &face), font + " is loaded");
ret.push_back(face);
}
return ret;
} // loadTTF
// ============================================================================
namespace LineBreakingRules
{
// Here a list of characters that don't start or end a line for
// chinese/japanese/korean. Only commonly use and full width characters are
// included. You should use full width characters when writing CJK,
// like using "。"instead of a ".", you can add more characters if needed.
// For full list please visit:
// http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/kinsoku.html
bool noStartingLine(char32_t c)
{
switch (c)
{
//
case 8217:
return true;
// ”
case 8221:
return true;
// 々
case 12293:
return true;
// 〉
case 12297:
return true;
// 》
case 12299:
return true;
// 」
case 12301:
return true;
//
case 65373:
return true;
//
case 12309:
return true;
//
case 65289:
return true;
// 』
case 12303:
return true;
// 】
case 12305:
return true;
// 〗
case 12311:
return true;
//
case 65281:
return true;
//
case 65285:
return true;
//
case 65311:
return true;
//
case 65344:
return true;
//
case 65292:
return true;
//
case 65306:
return true;
//
case 65307:
return true;
//
case 65294:
return true;
// 。
case 12290:
return true;
// 、
case 12289:
return true;
default:
return false;
}
} // noStartingLine
//-------------------------------------------------------------------------
bool noEndingLine(char32_t c)
{
switch (c)
{
//
case 8216:
return true;
// “
case 8220:
return true;
// 〈
case 12296:
return true;
// 《
case 12298:
return true;
// 「
case 12300:
return true;
//
case 65371:
return true;
//
case 12308:
return true;
//
case 65288:
return true;
// 『
case 12302:
return true;
// 【
case 12304:
return true;
// 〖
case 12310:
return true;
default:
return false;
}
} // noEndingLine
//-------------------------------------------------------------------------
// Helper function
bool breakable(char32_t c)
{
if ((c > 12287 && c < 40960) || // Common CJK words
(c > 44031 && c < 55204) || // Hangul
(c > 63743 && c < 64256) || // More Chinese
c == 173 || c == 32 || // Soft hyphen and white space
c == 47 || c == 92 || // Slash and blackslash
c == 8203) // Zero-width space
return true;
return false;
} // breakable
//-------------------------------------------------------------------------
void insertBreakMark(const std::u32string& str, std::vector<bool>& result)
{
assert(str.size() == result.size());
for (unsigned i = 0; i < result.size(); i++)
{
char32_t c = str[i];
char32_t nextline_char = 20;
if (i < result.size() - 1)
nextline_char = str[i + 1];
if (breakable(c) && !noEndingLine(c) &&
!noStartingLine(nextline_char))
{
result[i] = true;
}
}
} // insertBreakMark
} // namespace LineBreakingRules
// ----------------------------------------------------------------------------
/* Turn text into glyph layout for rendering by libraqm. */
void FontManager::shape(const std::u32string& text,
std::vector<irr::gui::GlyphLayout>& gls,
u32 shape_flag)
{
return;
// Helper struct
struct ShapeGlyph
{
unsigned int index;
int x_advance;
int y_advance;
int x_offset;
int y_offset;
uint32_t cluster;
FT_Face ftface;
};
auto fill_shape_glyph = [](std::vector<ShapeGlyph>& shape_glyphs,
hb_buffer_t* hb_buffer, int offset, FT_Face ftface)
{
size_t len = hb_buffer_get_length(hb_buffer);
hb_glyph_info_t* info = hb_buffer_get_glyph_infos(hb_buffer, NULL);
hb_glyph_position_t* position =
hb_buffer_get_glyph_positions(hb_buffer, NULL);
for (size_t i = 0; i < len; i++)
{
shape_glyphs.push_back({info[i].codepoint, position[i].x_advance,
position[i].y_advance, position[i].x_offset,
position[i].y_offset, info[i].cluster + offset, ftface});
}
};
// m_faces can be empty in null device
if (text.empty() || m_faces.empty())
return;
auto lines = StringUtils::split(text, U'\n');
// If the text end with and newline, it will miss a newline height, so we
// it back here
if (text.back() == U'\n')
lines.push_back(U"");
// URL marker
std::vector<std::pair<int, int> > http_pos;
auto fix_end_pos = [](const std::u32string& url, size_t start_pos,
size_t pos)->size_t
{
// https:// has 8 characters, shortest URL has 3 characters (like t.me)
// so 8 is valid for http:// too
size_t next_forward_slash = url.find(U'/', start_pos + 8);
if (next_forward_slash > pos)
next_forward_slash = std::string::npos;
// Tested in gnome terminal, URL ends with 0-9, aA-zZ, /- or ~:_=#$%&'+@*]) only
// ~:_=#$%&'+@*]) will not be highlighted unless it's after / (forward slash)
// We assume the URL is valid so we only test ]) instead of ([ blah ])
std::u32string valid_end_characters = U"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/-";
std::u32string valid_end_characters_extra = U"~:_=#$%&'+@*])";
if (next_forward_slash != std::string::npos)
valid_end_characters += valid_end_characters_extra;
while (pos > 1)
{
char32_t url_char = url[pos - 1];
for (char32_t valid_char : valid_end_characters)
{
if (valid_char == url_char)
return pos;
}
pos--;
}
return 0;
};
// Auto URL highlighting for http:// or https://
size_t pos = text.find(U"http://", 0);
while (pos != std::u32string::npos)
{
// Find nearest newline or whitespace
size_t newline_pos = text.find(U'\n', pos + 1);
size_t space_pos = text.find(U' ', pos + 1);
size_t end_pos = std::u32string::npos;
if (newline_pos != std::u32string::npos ||
space_pos != std::u32string::npos)
{
if (space_pos > newline_pos)
end_pos = newline_pos;
else
end_pos = space_pos;
}
else
end_pos = text.size();
end_pos = fix_end_pos(text, pos, end_pos);
http_pos.emplace_back((int)pos, (int)end_pos);
pos = text.find(U"http://", pos + 1);
}
pos = text.find(U"https://", 0);
while (pos != std::u32string::npos)
{
size_t newline_pos = text.find(U'\n', pos + 1);
size_t space_pos = text.find(U' ', pos + 1);
size_t end_pos = std::u32string::npos;
if (newline_pos != std::u32string::npos ||
space_pos != std::u32string::npos)
{
if (space_pos > newline_pos)
end_pos = newline_pos;
else
end_pos = space_pos;
}
else
end_pos = text.size();
end_pos = fix_end_pos(text, pos, end_pos);
http_pos.emplace_back((int)pos, (int)end_pos);
pos = text.find(U"https://", pos + 1);
}
bool save_orig_string = (shape_flag & gui::SF_ENABLE_CLUSTER_TEST) != 0;
if (!http_pos.empty())
save_orig_string = true;
int start = 0;
std::shared_ptr<std::u32string> orig_string =
std::make_shared<std::u32string>(text);
for (unsigned l = 0; l < lines.size(); l++)
{
std::vector<ShapeGlyph> glyphs;
if (l != 0)
{
gui::GlyphLayout gl = { 0 };
gl.flags = gui::GLF_NEWLINE;
gls.push_back(gl);
}
std::u32string& str = lines[l];
if (str.empty())
{
start += 1;
continue;
}
SBCodepointSequence codepoint_sequence;
codepoint_sequence.stringEncoding = SBStringEncodingUTF32;
codepoint_sequence.stringBuffer = (void*)str.c_str();
codepoint_sequence.stringLength = str.size();
// Extract the first bidirectional paragraph
SBAlgorithmRef bidi_algorithm = SBAlgorithmCreate(&codepoint_sequence);
SBParagraphRef first_paragraph = SBAlgorithmCreateParagraph(bidi_algorithm,
0, (int32_t)-1, SBLevelDefaultLTR);
SBUInteger paragraph_length = SBParagraphGetLength(first_paragraph);
// Create a line consisting of whole paragraph and get its runs
SBLineRef paragraph_line = SBParagraphCreateLine(first_paragraph, 0,
paragraph_length);
SBUInteger run_count = SBLineGetRunCount(paragraph_line);
const SBRun* run_array = SBLineGetRunsPtr(paragraph_line);
std::vector<bool> rtl_line, rtl_char;
rtl_line.resize(str.size(), false);
rtl_char.resize(str.size(), false);
for (SBUInteger run = 0; run < run_count; run++)
{
FT_Face prev_face = NULL;
std::vector<std::pair<FT_Face, SBInteger> > face_for_shape;
SBInteger run_length = run_array[run].length;
hb_buffer_flags_t hb_buffer_flags = static_cast<hb_buffer_flags_t>(
HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT);
hb_direction_t direction = run_array[run].level % 2 == 0 ?
HB_DIRECTION_LTR : HB_DIRECTION_RTL;
for (SBInteger l = 0; l < run_length; l++)
{
SBInteger i = l + run_array[run].offset;
rtl_char[i] = direction == HB_DIRECTION_RTL;
// Use the first character in line to determine paragraph
// direction
if (i == 0 && direction == HB_DIRECTION_RTL)
std::fill(rtl_line.begin(), rtl_line.end(), true);
FT_Face cur_face = m_faces.front();
bool override_face = false;
if (prev_face != NULL && i != 0)
{
hb_script_t prev_script = hb_unicode_script(
hb_unicode_funcs_get_default(), str[i - 1]);
hb_script_t cur_script = hb_unicode_script(
hb_unicode_funcs_get_default(), str[i]);
if (cur_script == HB_SCRIPT_INHERITED ||
(prev_script == HB_SCRIPT_ARABIC &&
// Those exists in the default arabic font
(str[i] == U'.' || str[i] == U'!' || str[i] == U':')))
{
// For inherited script (like punctation with arabic or
// join marks), try to use the previous face so it is
// not hb_shape separately
cur_face = prev_face;
override_face = true;
}
}
FT_Face emoji_face = m_faces.size() > 1 ? m_faces[1] : NULL;
if (m_has_color_emoji && !override_face &&
run_length > 1 &&
i < (SBInteger)(run_length + run_array[run].offset - 1) &&
emoji_face != NULL && str[i + 1] == 0xfe0f)
{
// Rule for variation selector-16 (emoji presentation)
// It is used in for example Keycap Digit One
// (U+31, U+FE0F, U+20E3)
cur_face = emoji_face;
override_face = true;
}
if (!override_face)
{
for (unsigned j = 0; j < m_faces.size(); j++)
{
unsigned glyph_index =
FT_Get_Char_Index(m_faces[j], str[i]);
if (glyph_index > 0)
{
cur_face = m_faces[j];
break;
}
}
}
prev_face = cur_face;
if (!FT_HAS_COLOR(cur_face) ||
(FT_HAS_COLOR(cur_face) && cur_face->num_fixed_sizes == 0))
{
// Handle color emoji with CPAL / COLR tables
// (num_fixed_sizes == 0)
checkFTError(FT_Set_Pixel_Sizes(cur_face, 0,
m_shaping_dpi), "setting DPI");
}
face_for_shape.emplace_back(cur_face, i);
}
if (face_for_shape.empty())
continue;
// Compare if different hb_font should be used in this run
auto shape_compare = face_for_shape.front();
auto it = face_for_shape.begin();
// Depend on direction push front or back in this vector
std::vector<ShapeGlyph> run_glyphs;
while (it != face_for_shape.end())
{
const std::pair<FT_Face, SBInteger>& cur_compare = *it;
if (cur_compare.first != shape_compare.first)
{
hb_buffer_reset(m_hb_buffer);
int offset = shape_compare.second;
int length = cur_compare.second - offset;
FT_Face ft_face = shape_compare.first;
hb_buffer_add_utf32(m_hb_buffer,
(uint32_t*)(str.c_str() + offset), length, 0, -1);
hb_buffer_guess_segment_properties(m_hb_buffer);
hb_buffer_set_direction(m_hb_buffer, direction);
hb_buffer_set_flags(m_hb_buffer, hb_buffer_flags);
hb_shape(
m_hb_fonts[m_ft_faces_to_index[ft_face]],
m_hb_buffer, NULL, 0);
std::vector<ShapeGlyph> shaped_glyphs;
fill_shape_glyph(shaped_glyphs, m_hb_buffer, offset,
ft_face);
if (direction == HB_DIRECTION_LTR)
{
run_glyphs.insert(run_glyphs.end(),
shaped_glyphs.begin(), shaped_glyphs.end());
}
else
{
run_glyphs.insert(run_glyphs.begin(),
shaped_glyphs.begin(), shaped_glyphs.end());
}
shape_compare = cur_compare;
it = face_for_shape.erase(face_for_shape.begin(), it);
continue;
}
it++;
}
// Remaining pair if they use the same face
if (!face_for_shape.empty())
{
hb_buffer_reset(m_hb_buffer);
int offset = face_for_shape.front().second;
int length = face_for_shape.back().second - offset + 1;
FT_Face ft_face = face_for_shape.front().first;
hb_buffer_add_utf32(m_hb_buffer,
(uint32_t*)(str.c_str() + offset), length, 0, -1);
hb_buffer_guess_segment_properties(m_hb_buffer);
hb_buffer_set_direction(m_hb_buffer, direction);
hb_buffer_set_flags(m_hb_buffer, hb_buffer_flags);
hb_shape(m_hb_fonts[m_ft_faces_to_index[ft_face]],
m_hb_buffer, NULL, 0);
std::vector<ShapeGlyph> shaped_glyphs;
fill_shape_glyph(shaped_glyphs, m_hb_buffer, offset, ft_face);
if (direction == HB_DIRECTION_LTR)
{
run_glyphs.insert(run_glyphs.end(),
shaped_glyphs.begin(), shaped_glyphs.end());
}
else
{
run_glyphs.insert(run_glyphs.begin(),
shaped_glyphs.begin(), shaped_glyphs.end());
}
}
glyphs.insert(glyphs.end(), run_glyphs.begin(), run_glyphs.end());
}
SBLineRelease(paragraph_line);
SBParagraphRelease(first_paragraph);
SBAlgorithmRelease(bidi_algorithm);
if (!glyphs.empty())
{
std::vector<gui::GlyphLayout> cur_line;
std::vector<bool> breakable;
breakable.resize(str.size(), false);
LineBreakingRules::insertBreakMark(str, breakable);
translations->insertThaiBreakMark(str, breakable);
for (unsigned idx = 0; idx < glyphs.size(); idx++)
{
// Skip some control characters
if (str[glyphs[idx].cluster] == U'\t' ||
str[glyphs[idx].cluster] == U'\r')
continue;
gui::GlyphLayout gl = { 0 };
gl.index = glyphs[idx].index;
gl.cluster.push_back(glyphs[idx].cluster);
gl.x_advance = glyphs[idx].x_advance / BEARING;
gl.x_offset = glyphs[idx].x_offset / BEARING;
gl.y_offset = glyphs[idx].y_offset / BEARING;
gl.face_idx = m_ft_faces_to_index.at(glyphs[idx].ftface);
gl.original_index = idx;
if (rtl_line[glyphs[idx].cluster])
gl.flags |= gui::GLF_RTL_LINE;
if (rtl_char[glyphs[idx].cluster])
gl.flags |= gui::GLF_RTL_CHAR;
if (FT_HAS_COLOR(glyphs[idx].ftface))
gl.flags |= gui::GLF_COLORED;
if (save_orig_string)
gl.orig_string = orig_string;
cur_line.push_back(gl);
}
// Sort glyphs in logical order
std::sort(cur_line.begin(), cur_line.end(), []
(const gui::GlyphLayout& a_gi, const gui::GlyphLayout& b_gi)
{
return a_gi.cluster.front() < b_gi.cluster.front();
});
for (unsigned l = 0; l < cur_line.size(); l++)
{
const int cur_cluster = cur_line[l].cluster.front();
// Last cluster handling, add the remaining clusters if missing
if (l == cur_line.size() - 1)
{
for (int extra_cluster = cur_cluster + 1;
extra_cluster <= (int)str.size() - 1; extra_cluster++)
cur_line[l].cluster.push_back(extra_cluster);
}
else
{
// Make sure there is every cluster values appear in the
// list at least once, it will be used for cursor
// positioning later
const int next_cluster = cur_line[l + 1].cluster.front();
for (int extra_cluster = cur_cluster + 1;
extra_cluster <= next_cluster - 1; extra_cluster++)
cur_line[l].cluster.push_back(extra_cluster);
}
cur_line[l].draw_flags.resize(cur_line[l].cluster.size(),
gui::GLD_NONE);
}
// Sort glyphs in visual order
std::sort(cur_line.begin(), cur_line.end(), []
(const gui::GlyphLayout& a_gi,
const gui::GlyphLayout& b_gi)
{
return a_gi.original_index < b_gi.original_index;
});
// Use last cluster to determine link breaking, so ligatures can be
// handled
for (gui::GlyphLayout& gl : cur_line)
{
int last_cluster = gl.cluster.back();
if (breakable[last_cluster])
gl.flags |= gui::GLF_BREAKABLE;
// Add start offset to clusters
for (int& each_cluster : gl.cluster)
{
each_cluster += start;
for (auto& p : http_pos)
{
if (each_cluster >= p.first &&
each_cluster < p.second)
gl.flags |= gui::GLF_URL;
}
}
}
gls.insert(gls.end(), cur_line.begin(), cur_line.end());
}
// Next str will have a newline
start += str.size() + 1;
}
} // shape
// ----------------------------------------------------------------------------
/* Return the cached glyph layouts for writing, it will clear all layouts if
* not in-game and when the cached sized exceed a certain number. */
std::vector<irr::gui::GlyphLayout>&
FontManager::getCachedLayouts(const irr::core::stringw& str)
{
const size_t MAX_LAYOUTS = 600;
if (StateManager::get()->getGameState() != GUIEngine::GAME &&
m_cached_gls.size() > MAX_LAYOUTS)
{
Log::debug("FontManager",
"Clearing cached glyph layouts because too many.");
clearCachedLayouts();
}
return m_cached_gls[str];
} // getCachedLayouts
// ----------------------------------------------------------------------------
/** Convert text to glyph layouts for fast rendering with (optional) caching
* enabled.
*/
void FontManager::initGlyphLayouts(const core::stringw& text,
std::vector<irr::gui::GlyphLayout>& gls,
u32 shape_flag)
{
if (GUIEngine::isNoGraphics() || text.empty())
return;
if ((shape_flag & gui::SF_DISABLE_CACHE) != 0)
{
shape(StringUtils::wideToUtf32(text), gls, shape_flag);
return;
}
auto& cached_gls = getCachedLayouts(text);
if (cached_gls.empty())
shape(StringUtils::wideToUtf32(text), cached_gls, shape_flag);
gls = cached_gls;
} // initGlyphLayouts
// ----------------------------------------------------------------------------
FT_Face FontManager::loadColorEmoji()
{
if (GUIEngine::getSkin()->getColorEmojiTTF().empty())
return NULL;
FT_Face face = NULL;
FT_Error err = FT_New_Face(m_ft_library,
GUIEngine::getSkin()->getColorEmojiTTF().c_str(), 0, &face);
if (err > 0)
{
Log::error("FontManager", "Something wrong when loading color emoji! "
"The error code was %d.", err);
return NULL;
}
if (!FT_HAS_COLOR(face))
{
Log::error("FontManager", "Bad %s color emoji, ignored.",
GUIEngine::getSkin()->getColorEmojiTTF().c_str());
checkFTError(FT_Done_Face(face), "removing faces for emoji");
return NULL;
}
if (face->num_fixed_sizes != 0)
{
// 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");
}
uint32_t smiley = 0x1f603;
uint32_t glyph_index = FT_Get_Char_Index(face, smiley);
if (glyph_index == 0)
{
Log::error("FontManager", "%s doesn't make 0x1f603 smiley, ignored.",
GUIEngine::getSkin()->getColorEmojiTTF().c_str());
checkFTError(FT_Done_Face(face), "removing faces for emoji");
return NULL;
}
FT_GlyphSlot slot = face->glyph;
if (FT_HAS_COLOR(face) && face->num_fixed_sizes != 0)
{
checkFTError(FT_Load_Glyph(face, glyph_index,
FT_LOAD_DEFAULT | FT_LOAD_COLOR), "loading a glyph");
}
else
{
checkFTError(FT_Set_Pixel_Sizes(face, 0, 16), "setting DPI");
checkFTError(FT_Load_Glyph(face, glyph_index,
FT_LOAD_DEFAULT | FT_LOAD_COLOR), "loading a glyph");
checkFTError(FT_Render_Glyph(slot,
FT_RENDER_MODE_NORMAL), "rendering a glyph to bitmap");
}
FT_Bitmap* bits = &(slot->bitmap);
if (!bits || bits->pixel_mode != FT_PIXEL_MODE_BGRA)
{
Log::error("FontManager", "%s doesn't have color, ignored.",
GUIEngine::getSkin()->getColorEmojiTTF().c_str());
checkFTError(FT_Done_Face(face), "removing faces for emoji");
return NULL;
}
m_has_color_emoji = true;
return face;
} // loadColorEmoji
#endif
// ----------------------------------------------------------------------------
/** Initialize all \ref FaceTTF and \ref FontWithFace members.
*/
void FontManager::loadFonts()
{
#ifndef SERVER_ONLY
// First load the TTF files required by each font
std::vector<FT_Face> normal_ttf = loadTTF(
GUIEngine::getSkin()->getNormalTTF());
std::vector<FT_Face> bold_ttf = normal_ttf;
FT_Face color_emoji = NULL;
if (!GUIEngine::isNoGraphics())
{
assert(!normal_ttf.empty());
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(GUIEngine::getSkin()->getDigitTTF());
if (!digit_ttf.empty())
m_digit_face = digit_ttf.front();
#endif
// Now load fonts with settings of ttf file
unsigned int font_loaded = 0;
RegularFace* regular = new RegularFace();
#ifndef SERVER_ONLY
regular->getFaceTTF()->loadTTF(normal_ttf);
#endif
regular->init();
#ifndef SERVER_ONLY
if (color_emoji && color_emoji->num_fixed_sizes == 0)
{
// This color emoji has CPAL / COLR tables so it's scalable
m_shaping_dpi = regular->getDPI();
// Update inverse shaping from m_shaping_dpi
regular->setDPI();
}
for (FT_Face face : normal_ttf)
{
if (!FT_HAS_COLOR(face) ||
(FT_HAS_COLOR(face) && face->num_fixed_sizes == 0))
{
checkFTError(FT_Set_Pixel_Sizes(face, 0, m_shaping_dpi),
"setting DPI");
}
//m_hb_fonts.push_back(hb_ft_font_create(face, NULL));
}
#endif
m_fonts.push_back(regular);
m_font_type_map[std::type_index(typeid(RegularFace))] = font_loaded++;
BoldFace* bold = new BoldFace();
#ifndef SERVER_ONLY
bold->getFaceTTF()->loadTTF(bold_ttf);
#endif
bold->init();
m_fonts.push_back(bold);
m_font_type_map[std::type_index(typeid(BoldFace))] = font_loaded++;
DigitFace* digit = new DigitFace();
#ifndef SERVER_ONLY
digit->getFaceTTF()->loadTTF(digit_ttf);
#endif
digit->init();
m_fonts.push_back(digit);
m_font_type_map[std::type_index(typeid(DigitFace))] = font_loaded++;
#ifndef SERVER_ONLY
m_faces.insert(m_faces.end(), normal_ttf.begin(), normal_ttf.end());
#endif
} // loadFonts
// ----------------------------------------------------------------------------
/** Unit testing that will try to load all translations in STK, and discover if
* there is any characters required by it are not supported in \ref
* m_normal_ttf.
*/
void FontManager::unitTesting()
{
#ifndef SERVER_ONLY
std::vector<std::string> list = *(translations->getLanguageList());
const int cur_log_level = Log::getLogLevel();
for (const std::string& lang : list)
{
// Hide gettext warning
Log::setLogLevel(5);
delete translations;
#ifdef WIN32
std::string s=std::string("LANGUAGE=") + lang.c_str();
_putenv(s.c_str());
#else
setenv("LANGUAGE", lang.c_str(), 1);
#endif
translations = new Translations();
Log::setLogLevel(cur_log_level);
std::set<unsigned int> used_chars = translations->getCurrentAllChar();
// First FontWithFace is RegularFace
FaceTTF* ttf = m_fonts.front()->getFaceTTF();
for (const unsigned int& c : used_chars)
{
// Skip non-printing characters
if (c < 32) continue;
unsigned int font_number = 0;
unsigned int glyph_index = 0;
if (ttf->getFontAndGlyphFromChar(c, &font_number, &glyph_index))
{
Log::debug("UnitTest", "Character %s in language %s"
" use face %s",
StringUtils::wideToUtf8(core::stringw(&c, 1)).c_str(),
lang.c_str(),
ttf->getFace(font_number)->family_name);
}
else
{
Log::warn("UnitTest", "Character %s in language %s"
" is not supported by all fonts!",
StringUtils::wideToUtf8(core::stringw(&c, 1)).c_str(),
lang.c_str());
}
}
}
#endif
} // unitTesting