Skip to content

Commit

Permalink
Allow overriding fonts via media files (#15606)
Browse files Browse the repository at this point in the history

Co-authored-by: sfan5 <[email protected]>
  • Loading branch information
appgurueu and sfan5 authored Jan 19, 2025
1 parent eeb6cab commit 547e147
Show file tree
Hide file tree
Showing 8 changed files with 270 additions and 138 deletions.
28 changes: 26 additions & 2 deletions doc/lua_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ Mod directory structure
│   │   │ └── another_subfolder
│   │   └── bar_subfolder
│   ├── sounds
│   ├── media
│   ├── fonts
│ ├── media
│   ├── locale
│   └── <custom data>
└── another
Expand Down Expand Up @@ -265,7 +266,7 @@ The main Lua script. Running this script should register everything it
wants to register. Subsequent execution depends on Luanti calling the
registered callbacks.

### `textures`, `sounds`, `media`, `models`, `locale`
### `textures`, `sounds`, `media`, `models`, `locale`, `fonts`

Media files (textures, sounds, whatever) that will be transferred to the
client and will be available for use by the mod and translation files for
Expand All @@ -278,6 +279,7 @@ Accepted formats are:
images: .png, .jpg, .tga
sounds: .ogg vorbis
models: .x, .b3d, .obj, (since version 5.10:) .gltf, .glb
fonts: .ttf, .woff (both since version 5.11, see notes below)

Other formats won't be sent to the client (e.g. you can store .blend files
in a folder for convenience, without the risk that such files are transferred)
Expand Down Expand Up @@ -342,6 +344,28 @@ For example, if your model used an emissive material,
you should expect that a future version of Luanti may respect this,
and thus cause your model to render differently there.

#### Custom fonts

You can supply custom fonts in TrueType Font (`.ttf`) or Web Open Font Format (`.woff`) format.
The former is supported primarily for convenience. The latter is preferred due to its compression.

In the future, having multiple custom fonts and the ability to switch between them is planned,
but for now this feature is limited to the ability to override Luanti's default fonts via mods.
It is recommended that this only be used by game mods to set a look and feel.

The stems (file names without extension) are self-explanatory:

* Regular variants:
* `regular`
* `bold`
* `italic`
* `bold_italic`
* Monospaced variants:
* `mono`
* `mono_bold`
* `mono_italic`
* `mono_bold_italic`

Naming conventions
------------------

Expand Down
11 changes: 11 additions & 0 deletions src/client/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <IFileSystem.h>
#include <json/json.h>
#include "client.h"
#include "client/fontengine.h"
#include "network/clientopcodes.h"
#include "network/connection.h"
#include "network/networkpacket.h"
Expand Down Expand Up @@ -361,6 +362,9 @@ Client::~Client()
for (auto &csp : m_sounds_client_to_server)
m_sound->freeId(csp.first);
m_sounds_client_to_server.clear();

// Go back to our mainmenu fonts
g_fontengine->clearMediaFonts();
}

void Client::connect(const Address &address, const std::string &address_name,
Expand Down Expand Up @@ -837,6 +841,13 @@ bool Client::loadMedia(const std::string &data, const std::string &filename,
return true;
}

const char *font_ext[] = {".ttf", ".woff", NULL};
name = removeStringEnd(filename, font_ext);
if (!name.empty()) {
g_fontengine->setMediaFont(name, data);
return true;
}

errorstream << "Client: Don't know how to load file \""
<< filename << "\"" << std::endl;
return false;
Expand Down
119 changes: 85 additions & 34 deletions src/client/fontengine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
// Copyright (C) 2010-2014 sapier <sapier at gmx dot net>

#include "fontengine.h"
#include <cmath>

#include "client/renderingengine.h"
#include "config.h"
#include "porting.h"
#include "filesys.h"
#include "gettext.h"
#include "settings.h"
#include "irrlicht_changes/CGUITTFont.h"
#include "util/numeric.h" // rangelim
#include <IGUIEnvironment.h>
#include <IGUIFont.h>

#include <cmath>
#include <cstring>
#include <unordered_set>

/** reference to access font engine, has to be initialized by main */
FontEngine *g_fontengine = nullptr;

Expand All @@ -35,7 +35,6 @@ static const char *settings[] = {
"dpi_change_notifier", "display_density_factor", "gui_scaling",
};

/******************************************************************************/
FontEngine::FontEngine(gui::IGUIEnvironment* env) :
m_env(env)
{
Expand All @@ -53,16 +52,14 @@ FontEngine::FontEngine(gui::IGUIEnvironment* env) :
g_settings->registerChangedCallback(name, font_setting_changed, this);
}

/******************************************************************************/
FontEngine::~FontEngine()
{
g_settings->deregisterAllChangedCallbacks(this);

cleanCache();
clearCache();
}

/******************************************************************************/
void FontEngine::cleanCache()
void FontEngine::clearCache()
{
RecursiveMutexAutoLock l(m_font_mutex);

Expand All @@ -76,7 +73,6 @@ void FontEngine::cleanCache()
}
}

/******************************************************************************/
irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec)
{
return getFont(spec, false);
Expand Down Expand Up @@ -118,15 +114,13 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec, bool may_fail)
return font;
}

/******************************************************************************/
unsigned int FontEngine::getTextHeight(const FontSpec &spec)
{
gui::IGUIFont *font = getFont(spec);

return font->getDimension(L"Some unimportant example String").Height;
}

/******************************************************************************/
unsigned int FontEngine::getTextWidth(const std::wstring &text, const FontSpec &spec)
{
gui::IGUIFont *font = getFont(spec);
Expand All @@ -143,7 +137,6 @@ unsigned int FontEngine::getLineHeight(const FontSpec &spec)
+ font->getKerning(L'S').Y;
}

/******************************************************************************/
unsigned int FontEngine::getDefaultFontSize()
{
return m_default_size[m_currentMode];
Expand All @@ -157,7 +150,6 @@ unsigned int FontEngine::getFontSize(FontMode mode)
return m_default_size[mode];
}

/******************************************************************************/
void FontEngine::readSettings()
{
m_default_size[FM_Standard] = rangelim(g_settings->getU16("font_size"), 5, 72);
Expand All @@ -167,12 +159,9 @@ void FontEngine::readSettings()
m_default_bold = g_settings->getBool("font_bold");
m_default_italic = g_settings->getBool("font_italic");

cleanCache();
updateFontCache();
updateSkin();
refresh();
}

/******************************************************************************/
void FontEngine::updateSkin()
{
gui::IGUIFont *font = getFont();
Expand All @@ -181,15 +170,50 @@ void FontEngine::updateSkin()
m_env->getSkin()->setFont(font);
}

/******************************************************************************/
void FontEngine::updateFontCache()
void FontEngine::updateCache()
{
/* the only font to be initialized is default one,
* all others are re-initialized on demand */
getFont(FONT_SIZE_UNSPECIFIED, FM_Unspecified);
}

/******************************************************************************/
void FontEngine::refresh() {
clearCache();
updateCache();
updateSkin();
}

void FontEngine::setMediaFont(const std::string &name, const std::string &data)
{
static std::unordered_set<std::string> valid_names {
"regular", "bold", "italic", "bold_italic",
"mono", "mono_bold", "mono_italic", "mono_bold_italic",
};
if (!valid_names.count(name)) {
warningstream << "Ignoring unrecognized media font: " << name << std::endl;
return;
}

constexpr char TTF_MAGIC[5] = {0, 1, 0, 0, 0};
if (data.size() < 5 || (memcmp(data.data(), "wOFF", 4) &&
memcmp(data.data(), TTF_MAGIC, 5))) {
warningstream << "Rejecting media font with unrecognized magic" << std::endl;
return;
}

std::string copy = data;
irr_ptr<gui::SGUITTFace> face(gui::SGUITTFace::createFace(std::move(copy)));
m_media_faces.emplace(name, face);
refresh();
}

void FontEngine::clearMediaFonts()
{
RecursiveMutexAutoLock l(m_font_mutex);
m_media_faces.clear();
refresh();
}

gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
{
assert(spec.mode != FM_Unspecified);
Expand Down Expand Up @@ -230,28 +254,55 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
else
path_setting = setting_prefix + "font_path" + setting_suffix;

std::string fallback_settings[] = {
g_settings->get(path_setting),
Settings::getLayer(SL_DEFAULTS)->get(path_setting)
};
std::string media_name = spec.mode == FM_Mono
? "mono" + setting_suffix
: (setting_suffix.empty() ? "" : setting_suffix.substr(1));
if (media_name.empty())
media_name = "regular";

for (const std::string &font_path : fallback_settings) {
gui::CGUITTFont *font = gui::CGUITTFont::createTTFont(m_env,
font_path.c_str(), size, true, true, font_shadow,
auto createFont = [&](gui::SGUITTFace *face) -> gui::CGUITTFont* {
auto *font = gui::CGUITTFont::createTTFont(m_env,
face, size, true, true, font_shadow,
font_shadow_alpha);

if (!font) {
errorstream << "FontEngine: Cannot load '" << font_path <<
"'. Trying to fall back to another path." << std::endl;
continue;
}
if (!font)
return nullptr;

if (spec.mode != _FM_Fallback) {
FontSpec spec2(spec);
spec2.mode = _FM_Fallback;
font->setFallback(getFont(spec2, true));
}

return font;
};

auto it = m_media_faces.find(media_name);
if (it != m_media_faces.end()) {
auto *face = it->second.get();
if (auto *font = createFont(face))
return font;
errorstream << "FontEngine: Cannot load media font '" << media_name <<
"'. Falling back to client settings." << std::endl;
}

std::string fallback_settings[] = {
g_settings->get(path_setting),
Settings::getLayer(SL_DEFAULTS)->get(path_setting)
};
for (const std::string &font_path : fallback_settings) {
infostream << "Creating new font: " << font_path.c_str()
<< " " << size << "pt" << std::endl;

// Grab the face.
if (auto *face = irr::gui::SGUITTFace::loadFace(font_path)) {
auto *font = createFont(face);
face->drop();
return font;
}

errorstream << "FontEngine: Cannot load '" << font_path <<
"'. Trying to fall back to another path." << std::endl;
}
return nullptr;
}
22 changes: 17 additions & 5 deletions src/client/fontengine.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
#pragma once

#include <map>
#include <unordered_map>
#include "irr_ptr.h"
#include "irrlicht_changes/CGUITTFont.h"
#include "util/basic_macros.h"
#include "irrlichttypes.h"
#include "irrString.h" // utf8_to_wide
Expand Down Expand Up @@ -66,7 +69,7 @@ class FontEngine
/** get text height for a specific font */
unsigned int getTextHeight(const FontSpec &spec);

/** get text width if a text for a specific font */
/** get text width of a text for a specific font */
unsigned int getTextHeight(
unsigned int font_size=FONT_SIZE_UNSPECIFIED,
FontMode mode=FM_Unspecified)
Expand All @@ -77,7 +80,7 @@ class FontEngine

unsigned int getTextWidth(const std::wstring &text, const FontSpec &spec);

/** get text width if a text for a specific font */
/** get text width of a text for a specific font */
unsigned int getTextWidth(const std::wstring& text,
unsigned int font_size=FONT_SIZE_UNSPECIFIED,
FontMode mode=FM_Unspecified)
Expand Down Expand Up @@ -118,20 +121,26 @@ class FontEngine
/** update internal parameters from settings */
void readSettings();

void setMediaFont(const std::string &name, const std::string &data);

void clearMediaFonts();

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();
void updateCache();

/** initialize a new TTF font */
gui::IGUIFont *initFont(const FontSpec &spec);

/** update current minetest skin with font changes */
void updateSkin();

/** clean cache */
void cleanCache();
void clearCache();

/** refresh after fonts have been changed */
void refresh();

/** pointer to irrlicht gui environment */
gui::IGUIEnvironment* m_env = nullptr;
Expand All @@ -142,6 +151,9 @@ class FontEngine
/** internal storage for caching fonts of different size */
std::map<unsigned int, irr::gui::IGUIFont*> m_font_cache[FM_MaxMode << 2];

/** media-provided faces, indexed by filename (without extension) */
std::unordered_map<std::string, irr_ptr<gui::SGUITTFace>> m_media_faces;

/** default font size to use */
unsigned int m_default_size[FM_MaxMode];

Expand Down
Loading

0 comments on commit 547e147

Please sign in to comment.