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_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).
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_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
# 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
mono_font_path_bold_italic (Bold and italic monospace font path) filepath fonts/Cousine-BoldItalic.ttf
# Path of the fallback font.
# If “freetype” setting is enabled: Must be a TrueType font.

@ -1085,18 +1085,6 @@ msgstr ""
msgid "Invalid gamespec."
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
msgid "Shutting down..."
msgstr ""

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

@ -56,7 +56,7 @@ FontEngine::FontEngine(gui::IGUIEnvironment* env) :
readSettings();
if (m_currentMode == FM_Standard) {
if (m_currentMode != FM_Simple) {
g_settings->registerChangedCallback("font_size", font_setting_changed, NULL);
g_settings->registerChangedCallback("font_bold", 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_shadow", 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_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);
@ -101,6 +96,11 @@ void FontEngine::cleanCache()
/******************************************************************************/
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) {
spec.mode = m_currentMode;
@ -112,6 +112,10 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec)
// Support for those could be added, but who cares?
spec.bold = 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
@ -130,6 +134,13 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec)
else
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;
return font;
@ -205,20 +216,9 @@ void FontEngine::readSettings()
{
if (USE_FREETYPE && g_settings->getBool("freetype")) {
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");
/*~ 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_italic = g_settings->getBool("font_italic");
@ -271,18 +271,8 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
assert(spec.size != FONT_SIZE_UNSPECIFIED);
std::string setting_prefix = "";
switch (spec.mode) {
case FM_Fallback:
setting_prefix = "fallback_";
break;
case FM_Mono:
case FM_SimpleMono:
if (spec.mode == FM_Mono)
setting_prefix = "mono_";
break;
default:
break;
}
std::string setting_suffix = "";
if (spec.bold)
@ -305,38 +295,41 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
g_settings->getU16NoEx(setting_prefix + "font_shadow_alpha",
font_shadow_alpha);
std::string wanted_font_path;
wanted_font_path = g_settings->get(setting_prefix + "font_path" + setting_suffix);
std::string path_setting;
if (spec.mode == _FM_Fallback)
path_setting = "fallback_font_path";
else
path_setting = setting_prefix + "font_path" + setting_suffix;
std::string fallback_settings[] = {
wanted_font_path,
g_settings->get("fallback_font_path"),
Settings::getLayer(SL_DEFAULTS)->get(setting_prefix + "font_path")
g_settings->get(path_setting),
Settings::getLayer(SL_DEFAULTS)->get(path_setting)
};
#if USE_FREETYPE
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_shadow_alpha);
if (font)
return font;
if (!font) {
errorstream << "FontEngine: Cannot load '" << font_path <<
"'. Trying to fall back to another path." << std::endl;
continue;
}
// give up
errorstream << "minetest can not continue without a valid font. "
"Please correct the 'font_path' setting or install the font "
"file in the proper location" << std::endl;
if (spec.mode != _FM_Fallback) {
FontSpec spec2(spec);
spec2.mode = _FM_Fallback;
font->setFallback(getFont(spec2, true));
}
return font;
}
#else
errorstream << "FontEngine: Tried to load freetype fonts but Minetest was"
" not compiled with that library." << std::endl;
errorstream << "FontEngine: Tried to load TTF font but Minetest was"
" compiled without Freetype." << std::endl;
#endif
abort();
return nullptr;
}
/** initialize a font without freetype */

@ -33,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
enum FontMode : u8 {
FM_Standard = 0,
FM_Mono,
FM_Fallback,
_FM_Fallback, // do not use directly
FM_Simple,
FM_SimpleMono,
FM_MaxMode,
@ -47,7 +47,7 @@ struct FontSpec {
bold(bold),
italic(italic) {}
u16 getHash()
u16 getHash() const
{
return (mode << 2) | (static_cast<u8>(bold) << 1) | static_cast<u8>(italic);
}
@ -132,10 +132,12 @@ public:
void readSettings();
private:
irr::gui::IGUIFont *getFont(FontSpec spec, bool may_fail);
/** update content of font cache in case of a setting change made it invalid */
void updateFontCache();
/** initialize a new font */
/** initialize a new TTF font */
gui::IGUIFont *initFont(const FontSpec &spec);
/** 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("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);
settings->setDefault("fallback_font_size", font_size_str);
#else
settings->setDefault("freetype", "false");
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.
CGUITTFont::CGUITTFont(IGUIEnvironment *env)
: 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
setDebugName("CGUITTFont");
@ -640,7 +641,30 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& positio
if (current_color < colors.size())
applied_colors.push_back(colors[current_color]);
}
if (n > 0)
{
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;
++iter;
@ -766,6 +790,12 @@ inline u32 CGUITTFont::getWidthFromCharacter(uchar32_t c) const
int w = Glyphs[n-1].advance.x / 64;
return w;
}
if (fallback != 0)
{
wchar_t s[] = { (wchar_t) c, 0 };
return fallback->getDimension(s).Width;
}
if (c >= 0x2000)
return (font_metrics.ascender / 64);
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();
return height;
}
if (fallback != 0)
{
wchar_t s[] = { (wchar_t) c, 0 };
return fallback->getDimension(s).Height;
}
if (c >= 0x2000)
return (font_metrics.ascender / 64);
else return (font_metrics.ascender / 64) / 2;
@ -804,9 +840,9 @@ u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const
// Get the glyph.
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)
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 (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);
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 (!FT_HAS_KERNING(tt_face))
return ret;
// Get the kerning information.
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 (FT_IS_SCALABLE(tt_face))
@ -960,6 +1009,9 @@ void CGUITTFont::setInvisibleCharacters(const core::ustring& s)
video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch)
{
u32 n = getGlyphIndexByChar(ch);
if (n == 0)
n = getGlyphIndexByChar((uchar32_t) core::unicode::UTF_REPLACEMENT_CHARACTER);
const SGUITTGlyph& glyph = Glyphs[n-1];
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);
}
offset.X += getWidthFromCharacter(current_char);
// Note that fallback font handling is missing here (Minetest never uses this)
previous_char = current_char;
++text;
}

@ -269,7 +269,7 @@ namespace gui
video::SColor color, bool hcenter=false, bool vcenter=false,
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,
const core::rect<s32>* clip=0);
@ -313,6 +313,9 @@ namespace gui
//! Get the last glyph page's index.
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,
//! so you can use this data just like any ordinary video::IImage.
//! \param ch The character you need
@ -387,6 +390,8 @@ namespace gui
core::ustring Invisible;
u32 shadow_offset;
u32 shadow_alpha;
gui::IGUIFont* fallback;
};
} // end namespace gui