Replace fallback font nonsense with automatic per-glyph fallback (#11084)

This commit is contained in:
sfan5 2021-03-29 19:55:24 +02:00 committed by GitHub
parent 5f4c78a77d
commit 8d89f5f0cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 119 additions and 88 deletions

@ -859,7 +859,7 @@ font_path (Regular font path) filepath fonts/Arimo-Regular.ttf
font_path_bold (Bold font path) filepath fonts/Arimo-Bold.ttf font_path_bold (Bold font path) filepath fonts/Arimo-Bold.ttf
font_path_italic (Italic font path) filepath fonts/Arimo-Italic.ttf font_path_italic (Italic font path) filepath fonts/Arimo-Italic.ttf
font_path_bolditalic (Bold and italic font path) filepath fonts/Arimo-BoldItalic.ttf font_path_bold_italic (Bold and italic font path) filepath fonts/Arimo-BoldItalic.ttf
# Font size of the monospace font in point (pt). # Font size of the monospace font in point (pt).
mono_font_size (Monospace font size) int 15 1 mono_font_size (Monospace font size) int 15 1
@ -872,16 +872,7 @@ mono_font_path (Monospace font path) filepath fonts/Cousine-Regular.ttf
mono_font_path_bold (Bold monospace font path) filepath fonts/Cousine-Bold.ttf mono_font_path_bold (Bold monospace font path) filepath fonts/Cousine-Bold.ttf
mono_font_path_italic (Italic monospace font path) filepath fonts/Cousine-Italic.ttf mono_font_path_italic (Italic monospace font path) filepath fonts/Cousine-Italic.ttf
mono_font_path_bolditalic (Bold and italic monospace font path) filepath fonts/Cousine-BoldItalic.ttf mono_font_path_bold_italic (Bold and italic monospace font path) filepath fonts/Cousine-BoldItalic.ttf
# Font size of the fallback font in point (pt).
fallback_font_size (Fallback font size) int 15 1
# Shadow offset (in pixels) of the fallback font. If 0, then shadow will not be drawn.
fallback_font_shadow (Fallback font shadow) int 1
# Opaqueness (alpha) of the shadow behind the fallback font, between 0 and 255.
fallback_font_shadow_alpha (Fallback font shadow alpha) int 128 0 255
# Path of the fallback font. # Path of the fallback font.
# If “freetype” setting is enabled: Must be a TrueType font. # If “freetype” setting is enabled: Must be a TrueType font.

@ -1085,18 +1085,6 @@ msgstr ""
msgid "Invalid gamespec." msgid "Invalid gamespec."
msgstr "" msgstr ""
#. ~ DO NOT TRANSLATE THIS LITERALLY!
#. This is a special string. Put either "no" or "yes"
#. into the translation field (literally).
#. Choose "yes" if the language requires use of the fallback
#. font, "no" otherwise.
#. The fallback font is (normally) required for languages with
#. non-Latin script, like Chinese.
#. When in doubt, test your translation.
#: src/client/fontengine.cpp
msgid "needs_fallback_font"
msgstr ""
#: src/client/game.cpp #: src/client/game.cpp
msgid "Shutting down..." msgid "Shutting down..."
msgstr "" msgstr ""

@ -668,7 +668,10 @@ endif(BUILD_SERVER)
# see issue #4638 # see issue #4638
set(GETTEXT_BLACKLISTED_LOCALES set(GETTEXT_BLACKLISTED_LOCALES
ar ar
dv
he he
hi
kn
ky ky
ms_Arab ms_Arab
th th

@ -56,7 +56,7 @@ FontEngine::FontEngine(gui::IGUIEnvironment* env) :
readSettings(); readSettings();
if (m_currentMode == FM_Standard) { if (m_currentMode != FM_Simple) {
g_settings->registerChangedCallback("font_size", font_setting_changed, NULL); g_settings->registerChangedCallback("font_size", font_setting_changed, NULL);
g_settings->registerChangedCallback("font_bold", font_setting_changed, NULL); g_settings->registerChangedCallback("font_bold", font_setting_changed, NULL);
g_settings->registerChangedCallback("font_italic", font_setting_changed, NULL); g_settings->registerChangedCallback("font_italic", font_setting_changed, NULL);
@ -66,12 +66,7 @@ FontEngine::FontEngine(gui::IGUIEnvironment* env) :
g_settings->registerChangedCallback("font_path_bolditalic", font_setting_changed, NULL); g_settings->registerChangedCallback("font_path_bolditalic", font_setting_changed, NULL);
g_settings->registerChangedCallback("font_shadow", font_setting_changed, NULL); g_settings->registerChangedCallback("font_shadow", font_setting_changed, NULL);
g_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed, NULL); g_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed, NULL);
}
else if (m_currentMode == FM_Fallback) {
g_settings->registerChangedCallback("fallback_font_size", font_setting_changed, NULL);
g_settings->registerChangedCallback("fallback_font_path", font_setting_changed, NULL); g_settings->registerChangedCallback("fallback_font_path", font_setting_changed, NULL);
g_settings->registerChangedCallback("fallback_font_shadow", font_setting_changed, NULL);
g_settings->registerChangedCallback("fallback_font_shadow_alpha", font_setting_changed, NULL);
} }
g_settings->registerChangedCallback("mono_font_path", font_setting_changed, NULL); g_settings->registerChangedCallback("mono_font_path", font_setting_changed, NULL);
@ -101,6 +96,11 @@ void FontEngine::cleanCache()
/******************************************************************************/ /******************************************************************************/
irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec) irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec)
{
return getFont(spec, false);
}
irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec, bool may_fail)
{ {
if (spec.mode == FM_Unspecified) { if (spec.mode == FM_Unspecified) {
spec.mode = m_currentMode; spec.mode = m_currentMode;
@ -112,6 +112,10 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec)
// Support for those could be added, but who cares? // Support for those could be added, but who cares?
spec.bold = false; spec.bold = false;
spec.italic = false; spec.italic = false;
} else if (spec.mode == _FM_Fallback) {
// Fallback font doesn't support these either
spec.bold = false;
spec.italic = false;
} }
// Fallback to default size // Fallback to default size
@ -130,6 +134,13 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec)
else else
font = initFont(spec); font = initFont(spec);
if (!font && !may_fail) {
errorstream << "Minetest cannot continue without a valid font. "
"Please correct the 'font_path' setting or install the font "
"file in the proper location." << std::endl;
abort();
}
m_font_cache[spec.getHash()][spec.size] = font; m_font_cache[spec.getHash()][spec.size] = font;
return font; return font;
@ -205,20 +216,9 @@ void FontEngine::readSettings()
{ {
if (USE_FREETYPE && g_settings->getBool("freetype")) { if (USE_FREETYPE && g_settings->getBool("freetype")) {
m_default_size[FM_Standard] = g_settings->getU16("font_size"); m_default_size[FM_Standard] = g_settings->getU16("font_size");
m_default_size[FM_Fallback] = g_settings->getU16("fallback_font_size"); m_default_size[_FM_Fallback] = g_settings->getU16("font_size");
m_default_size[FM_Mono] = g_settings->getU16("mono_font_size"); m_default_size[FM_Mono] = g_settings->getU16("mono_font_size");
/*~ DO NOT TRANSLATE THIS LITERALLY!
This is a special string. Put either "no" or "yes"
into the translation field (literally).
Choose "yes" if the language requires use of the fallback
font, "no" otherwise.
The fallback font is (normally) required for languages with
non-Latin script, like Chinese.
When in doubt, test your translation. */
m_currentMode = is_yes(gettext("needs_fallback_font")) ?
FM_Fallback : FM_Standard;
m_default_bold = g_settings->getBool("font_bold"); m_default_bold = g_settings->getBool("font_bold");
m_default_italic = g_settings->getBool("font_italic"); m_default_italic = g_settings->getBool("font_italic");
@ -271,18 +271,8 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
assert(spec.size != FONT_SIZE_UNSPECIFIED); assert(spec.size != FONT_SIZE_UNSPECIFIED);
std::string setting_prefix = ""; std::string setting_prefix = "";
if (spec.mode == FM_Mono)
switch (spec.mode) {
case FM_Fallback:
setting_prefix = "fallback_";
break;
case FM_Mono:
case FM_SimpleMono:
setting_prefix = "mono_"; setting_prefix = "mono_";
break;
default:
break;
}
std::string setting_suffix = ""; std::string setting_suffix = "";
if (spec.bold) if (spec.bold)
@ -305,38 +295,41 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
g_settings->getU16NoEx(setting_prefix + "font_shadow_alpha", g_settings->getU16NoEx(setting_prefix + "font_shadow_alpha",
font_shadow_alpha); font_shadow_alpha);
std::string wanted_font_path; std::string path_setting;
wanted_font_path = g_settings->get(setting_prefix + "font_path" + setting_suffix); if (spec.mode == _FM_Fallback)
path_setting = "fallback_font_path";
else
path_setting = setting_prefix + "font_path" + setting_suffix;
std::string fallback_settings[] = { std::string fallback_settings[] = {
wanted_font_path, g_settings->get(path_setting),
g_settings->get("fallback_font_path"), Settings::getLayer(SL_DEFAULTS)->get(path_setting)
Settings::getLayer(SL_DEFAULTS)->get(setting_prefix + "font_path")
}; };
#if USE_FREETYPE #if USE_FREETYPE
for (const std::string &font_path : fallback_settings) { for (const std::string &font_path : fallback_settings) {
irr::gui::IGUIFont *font = gui::CGUITTFont::createTTFont(m_env, gui::CGUITTFont *font = gui::CGUITTFont::createTTFont(m_env,
font_path.c_str(), size, true, true, font_shadow, font_path.c_str(), size, true, true, font_shadow,
font_shadow_alpha); font_shadow_alpha);
if (font) if (!font) {
return font;
errorstream << "FontEngine: Cannot load '" << font_path << errorstream << "FontEngine: Cannot load '" << font_path <<
"'. Trying to fall back to another path." << std::endl; "'. Trying to fall back to another path." << std::endl;
continue;
} }
if (spec.mode != _FM_Fallback) {
// give up FontSpec spec2(spec);
errorstream << "minetest can not continue without a valid font. " spec2.mode = _FM_Fallback;
"Please correct the 'font_path' setting or install the font " font->setFallback(getFont(spec2, true));
"file in the proper location" << std::endl; }
return font;
}
#else #else
errorstream << "FontEngine: Tried to load freetype fonts but Minetest was" errorstream << "FontEngine: Tried to load TTF font but Minetest was"
" not compiled with that library." << std::endl; " compiled without Freetype." << std::endl;
#endif #endif
abort(); return nullptr;
} }
/** initialize a font without freetype */ /** initialize a font without freetype */

@ -33,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
enum FontMode : u8 { enum FontMode : u8 {
FM_Standard = 0, FM_Standard = 0,
FM_Mono, FM_Mono,
FM_Fallback, _FM_Fallback, // do not use directly
FM_Simple, FM_Simple,
FM_SimpleMono, FM_SimpleMono,
FM_MaxMode, FM_MaxMode,
@ -47,7 +47,7 @@ struct FontSpec {
bold(bold), bold(bold),
italic(italic) {} italic(italic) {}
u16 getHash() u16 getHash() const
{ {
return (mode << 2) | (static_cast<u8>(bold) << 1) | static_cast<u8>(italic); return (mode << 2) | (static_cast<u8>(bold) << 1) | static_cast<u8>(italic);
} }
@ -132,10 +132,12 @@ public:
void readSettings(); void readSettings();
private: private:
irr::gui::IGUIFont *getFont(FontSpec spec, bool may_fail);
/** update content of font cache in case of a setting change made it invalid */ /** update content of font cache in case of a setting change made it invalid */
void updateFontCache(); void updateFontCache();
/** initialize a new font */ /** initialize a new TTF font */
gui::IGUIFont *initFont(const FontSpec &spec); gui::IGUIFont *initFont(const FontSpec &spec);
/** initialize a font without freetype */ /** initialize a font without freetype */

@ -304,12 +304,7 @@ void set_default_settings()
settings->setDefault("mono_font_path_bold_italic", porting::getDataPath("fonts" DIR_DELIM "Cousine-BoldItalic.ttf")); settings->setDefault("mono_font_path_bold_italic", porting::getDataPath("fonts" DIR_DELIM "Cousine-BoldItalic.ttf"));
settings->setDefault("fallback_font_path", porting::getDataPath("fonts" DIR_DELIM "DroidSansFallbackFull.ttf")); settings->setDefault("fallback_font_path", porting::getDataPath("fonts" DIR_DELIM "DroidSansFallbackFull.ttf"));
settings->setDefault("fallback_font_shadow", "1");
settings->setDefault("fallback_font_shadow_alpha", "128");
std::string font_size_str = std::to_string(TTF_DEFAULT_FONT_SIZE); std::string font_size_str = std::to_string(TTF_DEFAULT_FONT_SIZE);
settings->setDefault("fallback_font_size", font_size_str);
#else #else
settings->setDefault("freetype", "false"); settings->setDefault("freetype", "false");
settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "mono_dejavu_sans")); settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "mono_dejavu_sans"));

@ -275,7 +275,8 @@ CGUITTFont* CGUITTFont::create(IrrlichtDevice *device, const io::path& filename,
//! Constructor. //! Constructor.
CGUITTFont::CGUITTFont(IGUIEnvironment *env) CGUITTFont::CGUITTFont(IGUIEnvironment *env)
: use_monochrome(false), use_transparency(true), use_hinting(true), use_auto_hinting(true), : use_monochrome(false), use_transparency(true), use_hinting(true), use_auto_hinting(true),
batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0) batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0),
shadow_offset(0), shadow_alpha(0), fallback(0)
{ {
#ifdef _DEBUG #ifdef _DEBUG
setDebugName("CGUITTFont"); setDebugName("CGUITTFont");
@ -640,7 +641,30 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& positio
if (current_color < colors.size()) if (current_color < colors.size())
applied_colors.push_back(colors[current_color]); applied_colors.push_back(colors[current_color]);
} }
if (n > 0)
{
offset.X += getWidthFromCharacter(currentChar); offset.X += getWidthFromCharacter(currentChar);
}
else if (fallback != 0)
{
// Let the fallback font draw it, this isn't super efficient but hopefully that doesn't matter
wchar_t l1[] = { (wchar_t) currentChar, 0 }, l2 = (wchar_t) previousChar;
if (visible)
{
// Apply kerning.
offset.X += fallback->getKerningWidth(l1, &l2);
offset.Y += fallback->getKerningHeight();
u32 current_color = iter.getPos();
fallback->draw(core::stringw(l1),
core::rect<s32>({offset.X-1, offset.Y-1}, position.LowerRightCorner), // ???
current_color < colors.size() ? colors[current_color] : video::SColor(255, 255, 255, 255),
false, false, clip);
}
offset.X += fallback->getDimension(l1).Width;
}
previousChar = currentChar; previousChar = currentChar;
++iter; ++iter;
@ -766,6 +790,12 @@ inline u32 CGUITTFont::getWidthFromCharacter(uchar32_t c) const
int w = Glyphs[n-1].advance.x / 64; int w = Glyphs[n-1].advance.x / 64;
return w; return w;
} }
if (fallback != 0)
{
wchar_t s[] = { (wchar_t) c, 0 };
return fallback->getDimension(s).Width;
}
if (c >= 0x2000) if (c >= 0x2000)
return (font_metrics.ascender / 64); return (font_metrics.ascender / 64);
else return (font_metrics.ascender / 64) / 2; else return (font_metrics.ascender / 64) / 2;
@ -789,6 +819,12 @@ inline u32 CGUITTFont::getHeightFromCharacter(uchar32_t c) const
s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight(); s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight();
return height; return height;
} }
if (fallback != 0)
{
wchar_t s[] = { (wchar_t) c, 0 };
return fallback->getDimension(s).Height;
}
if (c >= 0x2000) if (c >= 0x2000)
return (font_metrics.ascender / 64); return (font_metrics.ascender / 64);
else return (font_metrics.ascender / 64) / 2; else return (font_metrics.ascender / 64) / 2;
@ -804,9 +840,9 @@ u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const
// Get the glyph. // Get the glyph.
u32 glyph = FT_Get_Char_Index(tt_face, c); u32 glyph = FT_Get_Char_Index(tt_face, c);
// Check for a valid glyph. If it is invalid, attempt to use the replacement character. // Check for a valid glyph.
if (glyph == 0) if (glyph == 0)
glyph = FT_Get_Char_Index(tt_face, core::unicode::UTF_REPLACEMENT_CHARACTER); return 0;
// If our glyph is already loaded, don't bother doing any batch loading code. // If our glyph is already loaded, don't bother doing any batch loading code.
if (glyph != 0 && Glyphs[glyph - 1].isLoaded) if (glyph != 0 && Glyphs[glyph - 1].isLoaded)
@ -922,13 +958,26 @@ core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32
core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight); core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight);
u32 n = getGlyphIndexByChar(thisLetter);
// If we don't have this glyph, ask fallback font
if (n == 0)
{
if (fallback != 0) {
wchar_t l1 = (wchar_t) thisLetter, l2 = (wchar_t) previousLetter;
ret.X = fallback->getKerningWidth(&l1, &l2);
ret.Y = fallback->getKerningHeight();
}
return ret;
}
// If we don't have kerning, no point in continuing. // If we don't have kerning, no point in continuing.
if (!FT_HAS_KERNING(tt_face)) if (!FT_HAS_KERNING(tt_face))
return ret; return ret;
// Get the kerning information. // Get the kerning information.
FT_Vector v; FT_Vector v;
FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), getGlyphIndexByChar(thisLetter), FT_KERNING_DEFAULT, &v); FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), n, FT_KERNING_DEFAULT, &v);
// If we have a scalable font, the return value will be in font points. // If we have a scalable font, the return value will be in font points.
if (FT_IS_SCALABLE(tt_face)) if (FT_IS_SCALABLE(tt_face))
@ -960,6 +1009,9 @@ void CGUITTFont::setInvisibleCharacters(const core::ustring& s)
video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch) video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch)
{ {
u32 n = getGlyphIndexByChar(ch); u32 n = getGlyphIndexByChar(ch);
if (n == 0)
n = getGlyphIndexByChar((uchar32_t) core::unicode::UTF_REPLACEMENT_CHARACTER);
const SGUITTGlyph& glyph = Glyphs[n-1]; const SGUITTGlyph& glyph = Glyphs[n-1];
CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page]; CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page];
@ -1147,6 +1199,8 @@ core::array<scene::ISceneNode*> CGUITTFont::addTextSceneNode(const wchar_t* text
container.push_back(current_node); container.push_back(current_node);
} }
offset.X += getWidthFromCharacter(current_char); offset.X += getWidthFromCharacter(current_char);
// Note that fallback font handling is missing here (Minetest never uses this)
previous_char = current_char; previous_char = current_char;
++text; ++text;
} }

@ -269,7 +269,7 @@ namespace gui
video::SColor color, bool hcenter=false, bool vcenter=false, video::SColor color, bool hcenter=false, bool vcenter=false,
const core::rect<s32>* clip=0); const core::rect<s32>* clip=0);
virtual void draw(const EnrichedString& text, const core::rect<s32>& position, void draw(const EnrichedString& text, const core::rect<s32>& position,
video::SColor color, bool hcenter=false, bool vcenter=false, video::SColor color, bool hcenter=false, bool vcenter=false,
const core::rect<s32>* clip=0); const core::rect<s32>* clip=0);
@ -313,6 +313,9 @@ namespace gui
//! Get the last glyph page's index. //! Get the last glyph page's index.
u32 getLastGlyphPageIndex() const { return Glyph_Pages.size() - 1; } u32 getLastGlyphPageIndex() const { return Glyph_Pages.size() - 1; }
//! Set font that should be used for glyphs not present in ours
void setFallback(gui::IGUIFont* font) { fallback = font; }
//! Create corresponding character's software image copy from the font, //! Create corresponding character's software image copy from the font,
//! so you can use this data just like any ordinary video::IImage. //! so you can use this data just like any ordinary video::IImage.
//! \param ch The character you need //! \param ch The character you need
@ -387,6 +390,8 @@ namespace gui
core::ustring Invisible; core::ustring Invisible;
u32 shadow_offset; u32 shadow_offset;
u32 shadow_alpha; u32 shadow_alpha;
gui::IGUIFont* fallback;
}; };
} // end namespace gui } // end namespace gui