Skip to content

Commit

Permalink
Replace fallback font nonsense with automatic per-glyph fallback (#11084
Browse files Browse the repository at this point in the history
)
  • Loading branch information
sfan5 committed Mar 29, 2021
1 parent 5f4c78a commit 8d89f5f
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 88 deletions.
13 changes: 2 additions & 11 deletions builtin/settingtypes.txt
Expand Up @@ -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
Expand All @@ -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.
Expand Down
12 changes: 0 additions & 12 deletions po/minetest.pot
Expand Up @@ -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 ""
Expand Down
3 changes: 3 additions & 0 deletions src/CMakeLists.txt
Expand Up @@ -668,7 +668,10 @@ endif(BUILD_SERVER)
# see issue #4638
set(GETTEXT_BLACKLISTED_LOCALES
ar
dv
he
hi
kn
ky
ms_Arab
th
Expand Down
95 changes: 44 additions & 51 deletions src/client/fontengine.cpp
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -204,20 +215,9 @@ unsigned int FontEngine::getFontSize(FontMode mode)
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_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_size[FM_Standard] = g_settings->getU16("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_bold = g_settings->getBool("font_bold");
m_default_italic = g_settings->getBool("font_italic");
Expand Down Expand Up @@ -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:
setting_prefix = "mono_";
break;
default:
break;
}
if (spec.mode == FM_Mono)
setting_prefix = "mono_";

std::string setting_suffix = "";
if (spec.bold)
Expand All @@ -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;

errorstream << "FontEngine: Cannot load '" << font_path <<
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 */
Expand Down
8 changes: 5 additions & 3 deletions src/client/fontengine.h
Expand Up @@ -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,
Expand All @@ -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);
}
Expand Down Expand Up @@ -132,10 +132,12 @@ class FontEngine
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 */
Expand Down
5 changes: 0 additions & 5 deletions src/defaultsettings.cpp
Expand Up @@ -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"));
Expand Down
64 changes: 59 additions & 5 deletions src/irrlicht_changes/CGUITTFont.cpp
Expand Up @@ -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");
Expand Down Expand Up @@ -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]);
}
offset.X += getWidthFromCharacter(currentChar);
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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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];

Expand Down Expand Up @@ -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;
}
Expand Down

0 comments on commit 8d89f5f

Please sign in to comment.