feat: unicode emoji support + other stuff

This commit is contained in:
Kae 2024-04-24 07:44:53 +10:00
parent 6ac139321b
commit d0f8aec244
16 changed files with 162 additions and 74 deletions

Binary file not shown.

View File

@ -1,9 +1,14 @@
{ {
"font" : {
"defaultFont" : "hobo",
"fallbackFont" : "unifont",
"emojiFont" : "twemoji"
},
"nametag" : { "nametag" : {
"showMasterNames" : true, "showMasterNames" : true,
// border is double here as it otherwise fades out near the end // border is double here as it otherwise fades out near the end
// (could just the end to opaque too, but then diagonals are more jaggy) // (could just the end to opaque too, but then diagonals are more jaggy)
"textStyle" : { "backDirectives" : "?border=1;222;2224?border=1;222;2224" }, "textStyle" : { "backDirectives" : "border=1;222;2224?border=1;222;2224" },
"inspectOpacityRate" : 0.15, "inspectOpacityRate" : 0.15,
"movementThreshold" : 0.5, "movementThreshold" : 0.5,
"offset" : [0, 13] "offset" : [0, 13]
@ -19,7 +24,7 @@
"paneTextStyle" : {}, "paneTextStyle" : {},
"textBoxTextStyle" : {}, "textBoxTextStyle" : {},
"itemSlotTextStyle" : { "itemSlotTextStyle" : {
"backDirectives" : "?border=1;444;4444" "backDirectives" : "border=1;444;4444"
}, },
"teamBarNameStyle" : {}, "teamBarNameStyle" : {},
"cursorTooltip" : { "textStyle" : {} }, "cursorTooltip" : { "textStyle" : {} },
@ -27,7 +32,7 @@
"debugTextStyle" : { "debugTextStyle" : {
"font" : "iosevka-semibold", "font" : "iosevka-semibold",
"fontSize" : 7, "fontSize" : 7,
"backDirectives" : "?border=2;111a;1114" "backDirectives" : "border=1;333a;3334"
}, },
"debugSpatialClearTime" : 0.0, "debugSpatialClearTime" : 0.0,
"debugOffset" : [80, 130], "debugOffset" : [80, 130],
@ -35,7 +40,7 @@
// Change planet name to support the new internal string formatting. // Change planet name to support the new internal string formatting.
"planetNameFormatString" : "- {} -", "planetNameFormatString" : "- {} -",
"planetTextStyle" : { "planetTextStyle" : {
"backDirectives" : "?border=6;000;000", "backDirectives" : "border=6;000;000",
"fontSize" : 24 "fontSize" : 24
}, },

View File

@ -2,7 +2,8 @@
"config" : { "config" : {
"lineHeight" : 1, "lineHeight" : 1,
"padding" : [1, 1], "padding" : [1, 1],
"textStyle" : { "backDirectives" : "?border=1;111a;1114" }, "textStyle" : { "backDirectives" : "border=1;111a;1117" },
"chatFormatString" : "^backdirectives=border=1=333=3337;<^backdirectives=border=1=333=3330?border=1=333=3337,white;{}^reset,backdirectives=border=1=333=3337;>^reset,#eee; {}",
"colors" : { "colors" : {
"local" : "^white;", "local" : "^white;",
"party" : "^blue;", "party" : "^blue;",

View File

@ -7,7 +7,7 @@
namespace Star { namespace Star {
constexpr int FontLoadFlags = FT_LOAD_FORCE_AUTOHINT; constexpr int FontLoadFlags = FT_LOAD_NO_SVG | FT_LOAD_COLOR;
struct FTContext { struct FTContext {
FT_Library library; FT_Library library;
@ -90,47 +90,70 @@ unsigned Font::width(String::Char c) {
} }
std::pair<Image, Vec2I> Font::render(String::Char c) { tuple<Image, Vec2I, bool> Font::render(String::Char c) {
if (!m_fontImpl) if (!m_fontImpl)
throw FontException("Font::render called on uninitialized font."); throw FontException("Font::render called on uninitialized font.");
FT_Face face = m_fontImpl->face; FT_Face face = m_fontImpl->face;
FT_UInt glyph_index = FT_Get_Char_Index(face, c); if (m_loadedPixelSize != m_pixelSize || m_loadedChar != c) {
if (FT_Load_Glyph(face, glyph_index, FontLoadFlags) != 0) FT_UInt glyph_index = FT_Get_Char_Index(face, c);
return {}; if (FT_Load_Glyph(face, glyph_index, FontLoadFlags) != 0)
return {};
/* convert to an anti-aliased bitmap */ /* convert to an anti-aliased bitmap */
if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL) != 0) if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL) != 0)
return {}; return {};
}
m_loadedPixelSize = m_pixelSize;
m_loadedChar = m_loadedChar;
FT_GlyphSlot slot = face->glyph; FT_GlyphSlot slot = face->glyph;
unsigned width = slot->bitmap.width; unsigned width = slot->bitmap.width;
unsigned height = slot->bitmap.rows; unsigned height = slot->bitmap.rows;
bool colored = false;
Image image(width + 2, height + 2, PixelFormat::RGBA32); Image image(width + 2, height + 2, PixelFormat::BGRA32);
Vec4B white(255, 255, 255, 0); if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
image.fill(white); Vec4B white(255, 255, 255, 0);
image.fill(white);
for (unsigned y = 0; y != height; ++y) { for (unsigned y = 0; y != height; ++y) {
uint8_t* p = slot->bitmap.buffer + y * slot->bitmap.pitch; uint8_t* p = slot->bitmap.buffer + y * slot->bitmap.pitch;
for (unsigned x = 0; x != width; ++x) { for (unsigned x = 0; x != width; ++x) {
if (x < width && y < height) { if (x < width && y < height) {
uint8_t value = *(p + x); white[3] = m_alphaThreshold
if (m_alphaThreshold) { ? (*(p + x) >= m_alphaThreshold ? 255 : 0)
if (value >= m_alphaThreshold) { : *(p + x);
white[3] = 255;
image.set(x + 1, height - y, white);
}
} else if (value) {
white[3] = value;
image.set(x + 1, height - y, white); image.set(x + 1, height - y, white);
} }
} }
} }
} else if (colored = slot->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
unsigned bpp = image.bytesPerPixel();
uint8_t* data = image.data() + bpp + ((image.width() * (image.height() - 2)) * bpp); // offset by 1 pixel as it's padded
for (size_t y = 0; y != height; ++y) {
memcpy(data - (y * image.width() * bpp),
slot->bitmap.buffer + y * slot->bitmap.pitch,
slot->bitmap.pitch);
}
// unfortunately FreeType pre-multiplied the color channels :(
image.forEachPixel([](unsigned, unsigned, Vec4B& pixel) {
if (pixel[3] != 0 && pixel[3] != 255) {
float a = byteToFloat(pixel[3]);
pixel[0] /= a;
pixel[1] /= a;
pixel[2] /= a;
}
});
} else {
return {};
} }
return { std::move(image), {slot->bitmap_left - 1, (slot->bitmap_top - height) + (m_pixelSize / 4) - 1} }; return make_tuple(
std::move(image),
Vec2I(slot->bitmap_left - 1, (slot->bitmap_top - height) + (m_pixelSize / 4) - 1),
colored);
} }
bool Font::exists(String::Char c) { bool Font::exists(String::Char c) {

View File

@ -34,13 +34,15 @@ public:
// May return empty image on unrenderable character (Normally, this will // May return empty image on unrenderable character (Normally, this will
// render a box, but if there is an internal freetype error this may return // render a box, but if there is an internal freetype error this may return
// an empty image). // an empty image).
std::pair<Image, Vec2I> render(String::Char c); tuple<Image, Vec2I, bool> render(String::Char c);
bool exists(String::Char c); bool exists(String::Char c);
private: private:
FontImplPtr m_fontImpl; FontImplPtr m_fontImpl;
ByteArrayConstPtr m_fontBuffer; ByteArrayConstPtr m_fontBuffer;
unsigned m_pixelSize; unsigned m_pixelSize;
unsigned m_loadedPixelSize;
String::Char m_loadedChar;
uint8_t m_alphaThreshold; uint8_t m_alphaThreshold;
HashMap<pair<String::Char, unsigned>, unsigned> m_widthCache; HashMap<pair<String::Char, unsigned>, unsigned> m_widthCache;

View File

@ -90,7 +90,8 @@ void BaseScriptPane::tick(float dt) {
bool BaseScriptPane::sendEvent(InputEvent const& event) { bool BaseScriptPane::sendEvent(InputEvent const& event) {
// Intercept GuiClose before the canvas child so GuiClose always closes // Intercept GuiClose before the canvas child so GuiClose always closes
// BaseScriptPanes without having to support it in the script. // BaseScriptPanes without having to support it in the script.
if (context()->actions(event).contains(InterfaceAction::GuiClose)) { if (context()->actions(event).contains(InterfaceAction::GuiClose)
&& m_config.getBool("dismissable", true)) {
dismiss(); dismiss();
return true; return true;
} }

View File

@ -27,6 +27,7 @@ Chat::Chat(UniverseClientPtr client) : m_client(client) {
m_chatVisTime = config.get("visTime").toFloat(); m_chatVisTime = config.get("visTime").toFloat();
m_fadeRate = config.get("fadeRate").toDouble(); m_fadeRate = config.get("fadeRate").toDouble();
m_chatHistoryLimit = config.get("chatHistoryLimit").toInt(); m_chatHistoryLimit = config.get("chatHistoryLimit").toInt();
m_chatFormatString = config.getString("chatFormatString");
m_portraitTextOffset = jsonToVec2I(config.get("portraitTextOffset")); m_portraitTextOffset = jsonToVec2I(config.get("portraitTextOffset"));
m_portraitImageOffset = jsonToVec2I(config.get("portraitImageOffset")); m_portraitImageOffset = jsonToVec2I(config.get("portraitImageOffset"));
@ -180,7 +181,7 @@ void Chat::addMessages(List<ChatReceivedMessage> const& messages, bool showPane)
guiContext.setTextStyle(m_chatTextStyle); guiContext.setTextStyle(m_chatTextStyle);
StringList lines; StringList lines;
if (message.fromNick != "" && message.portrait == "") if (message.fromNick != "" && message.portrait == "")
lines = guiContext.wrapInterfaceText(strf("<{}> {}", message.fromNick, message.text), wrapWidth); lines = guiContext.wrapInterfaceText(strf(m_chatFormatString.utf8Ptr(), message.fromNick, message.text), wrapWidth);
else else
lines = guiContext.wrapInterfaceText(message.text, wrapWidth); lines = guiContext.wrapInterfaceText(message.text, wrapWidth);
@ -203,7 +204,8 @@ void Chat::addMessages(List<ChatReceivedMessage> const& messages, bool showPane)
show(); show();
} }
m_receivedMessages.resize(std::min((unsigned)m_receivedMessages.size(), m_chatHistoryLimit)); if ((unsigned)m_receivedMessages.size() > m_chatHistoryLimit)
m_receivedMessages.resize(m_chatHistoryLimit);
} }
void Chat::addHistory(String const& chat) { void Chat::addHistory(String const& chat) {
@ -216,6 +218,13 @@ void Chat::addHistory(String const& chat) {
m_client->playerStorage()->setMetadata("chatHistory", JsonArray::from(m_chatHistory)); m_client->playerStorage()->setMetadata("chatHistory", JsonArray::from(m_chatHistory));
} }
void Chat::clear(size_t count) {
if (count > m_receivedMessages.size())
m_receivedMessages.clear();
else
m_receivedMessages.erase(m_receivedMessages.begin(), m_receivedMessages.begin() + count);
}
void Chat::renderImpl() { void Chat::renderImpl() {
Pane::renderImpl(); Pane::renderImpl();
if (m_textBox->hasFocus()) if (m_textBox->hasFocus())

View File

@ -30,6 +30,7 @@ public:
void addLine(String const& text, bool showPane = true); void addLine(String const& text, bool showPane = true);
void addMessages(List<ChatReceivedMessage> const& messages, bool showPane = true); void addMessages(List<ChatReceivedMessage> const& messages, bool showPane = true);
void addHistory(String const& chat); void addHistory(String const& chat);
void clear(size_t count = std::numeric_limits<size_t>::max());
String currentChat() const; String currentChat() const;
bool setCurrentChat(String const& chat, bool moveCursor = false); bool setCurrentChat(String const& chat, bool moveCursor = false);
@ -69,6 +70,7 @@ private:
TextStyle m_chatTextStyle; TextStyle m_chatTextStyle;
unsigned m_chatHistoryLimit; unsigned m_chatHistoryLimit;
int m_historyOffset; int m_historyOffset;
String m_chatFormatString;
CanvasWidgetPtr m_chatLog; CanvasWidgetPtr m_chatLog;
Vec2I m_chatLogPadding; Vec2I m_chatLogPadding;

View File

@ -13,6 +13,11 @@ namespace Star {
LuaCallbacks LuaBindings::makeInterfaceCallbacks(MainInterface* mainInterface) { LuaCallbacks LuaBindings::makeInterfaceCallbacks(MainInterface* mainInterface) {
LuaCallbacks callbacks; LuaCallbacks callbacks;
callbacks.registerCallbackWithSignature<bool>(
"hudVisible", bind(mem_fn(&MainInterface::hudVisible), mainInterface));
callbacks.registerCallbackWithSignature<void, bool>(
"setHudVisible", bind(mem_fn(&MainInterface::setHudVisible), mainInterface, _1));
callbacks.registerCallback("bindCanvas", [mainInterface](String const& canvasName, Maybe<bool> ignoreInterfaceScale) -> Maybe<CanvasWidgetPtr> { callbacks.registerCallback("bindCanvas", [mainInterface](String const& canvasName, Maybe<bool> ignoreInterfaceScale) -> Maybe<CanvasWidgetPtr> {
if (auto canvas = mainInterface->fetchCanvas(canvasName, ignoreInterfaceScale.value(false))) if (auto canvas = mainInterface->fetchCanvas(canvasName, ignoreInterfaceScale.value(false)))
return canvas; return canvas;
@ -87,6 +92,10 @@ LuaCallbacks LuaBindings::makeChatCallbacks(MainInterface* mainInterface, Univer
return chat->setCurrentChat(text, moveCursor.value(false)); return chat->setCurrentChat(text, moveCursor.value(false));
}); });
callbacks.registerCallback("clear", [chat](Maybe<size_t> count) {
chat->clear(count.value(std::numeric_limits<size_t>::max()));
});
return callbacks; return callbacks;
} }

View File

@ -893,6 +893,14 @@ bool MainInterface::fixedCamera() const {
return m_clientCommandProcessor->fixedCameraEnabled(); return m_clientCommandProcessor->fixedCameraEnabled();
} }
bool MainInterface::hudVisible() const {
return !m_disableHud;
}
void MainInterface::setHudVisible(bool visible) {
m_disableHud = !visible;
}
void MainInterface::warpToOrbitedWorld(bool deploy) { void MainInterface::warpToOrbitedWorld(bool deploy) {
if (m_client->canBeamDown(deploy)) { if (m_client->canBeamDown(deploy)) {
if (deploy) if (deploy)

View File

@ -100,7 +100,6 @@ public:
Vec2F cursorWorldPosition() const; Vec2F cursorWorldPosition() const;
void toggleDebugDisplay();
bool isDebugDisplayed(); bool isDebugDisplayed();
void doChat(String const& chat, bool addToHistory); void doChat(String const& chat, bool addToHistory);
@ -112,6 +111,8 @@ public:
void queueJoinRequest(pair<String, RpcPromiseKeeper<P2PJoinRequestReply>> request); void queueJoinRequest(pair<String, RpcPromiseKeeper<P2PJoinRequestReply>> request);
bool fixedCamera() const; bool fixedCamera() const;
bool hudVisible() const;
void setHudVisible(bool visible = true);
void warpToOrbitedWorld(bool deploy = false); void warpToOrbitedWorld(bool deploy = false);
void warpToOwnShip(); void warpToOwnShip();
@ -209,7 +210,7 @@ private:
QuestIndicatorPainterPtr m_questIndicatorPainter; QuestIndicatorPainterPtr m_questIndicatorPainter;
ChatBubbleManagerPtr m_chatBubbleManager; ChatBubbleManagerPtr m_chatBubbleManager;
bool m_disableHud{false}; bool m_disableHud = false;
String m_lastCommand; String m_lastCommand;

View File

@ -115,7 +115,7 @@ Maybe<Uuid> PlayerStorage::playerUuidByName(String const& name, Maybe<Uuid> exce
RecursiveMutexLocker locker(m_mutex); RecursiveMutexLocker locker(m_mutex);
size_t longest = SIZE_MAX; size_t longest = std::numeric_limits<size_t>::max();
for (auto& cache : m_savedPlayersCache) { for (auto& cache : m_savedPlayersCache) {
if (except && *except == cache.first) if (except && *except == cache.first)
continue; continue;

View File

@ -14,13 +14,13 @@ void FontTextureGroup::cleanup(int64_t timeout) {
void FontTextureGroup::switchFont(String const& font) { void FontTextureGroup::switchFont(String const& font) {
if (font.empty()) { if (font.empty()) {
m_font = m_defaultFont; m_activeFont = m_defaultFont;
m_fontName.clear(); m_fontName.clear();
} }
else if (m_fontName != font) { else if (m_fontName != font) {
m_fontName = font; m_fontName = font;
auto find = m_fonts.find(font); auto find = m_fonts.find(font);
m_font = find != m_fonts.end() ? find->second : m_defaultFont; m_activeFont = find != m_fonts.end() ? find->second : m_defaultFont;
} }
} }
@ -28,31 +28,35 @@ String const& FontTextureGroup::activeFont() {
return m_fontName; return m_fontName;
} }
void FontTextureGroup::addFont(FontPtr const& font, String const& name, bool isDefault) { void FontTextureGroup::addFont(FontPtr const& font, String const& name) {
m_fonts[name] = font; m_fonts[name] = font;
if (isDefault)
m_defaultFont = m_font = font;
} }
void FontTextureGroup::clearFonts() { void FontTextureGroup::clearFonts() {
m_fonts.clear(); m_fonts.clear();
m_font = m_defaultFont; m_activeFont = m_defaultFont;
} }
void FontTextureGroup::setFallbackFont(String const& fontName) { void FontTextureGroup::setFixedFonts(String const& defaultFontName, String const& fallbackFontName, String const& emojiFontName) {
if (auto font = m_fonts.ptr(fontName)) if (auto defaultFont = m_fonts.ptr(defaultFontName))
m_fallbackFont = *font; m_defaultFont = m_activeFont = *defaultFont;
if (auto fallbackFont = m_fonts.ptr(fallbackFontName))
m_fallbackFont = *fallbackFont;
if (auto emojiFont = m_fonts.ptr(emojiFontName))
m_emojiFont = *emojiFont;
} }
const FontTextureGroup::GlyphTexture& FontTextureGroup::glyphTexture(String::Char c, unsigned size, Directives const* processingDirectives) const FontTextureGroup::GlyphTexture& FontTextureGroup::glyphTexture(String::Char c, unsigned size, Directives const* processingDirectives)
{ {
Font* font = (m_font->exists(c) || !m_fallbackFont) ? m_font.get() : m_fallbackFont.get(); Font* font = getFontForCharacter(c);
if (font == m_emojiFont.get())
processingDirectives = nullptr;
auto res = m_glyphs.insert(GlyphDescriptor{c, size, processingDirectives ? processingDirectives->hash() : 0, font}, GlyphTexture()); auto res = m_glyphs.insert(GlyphDescriptor{c, size, processingDirectives ? processingDirectives->hash() : 0, font}, GlyphTexture());
auto& glyphTexture = res.first->second;
if (res.second) { if (res.second) {
font->setPixelSize(size); font->setPixelSize(size);
auto pair = font->render(c); auto renderResult = font->render(c);
Image& image = pair.first; Image& image = get<0>(renderResult);
if (processingDirectives) { if (processingDirectives) {
try { try {
Directives const& directives = *processingDirectives; Directives const& directives = *processingDirectives;
@ -61,7 +65,7 @@ const FontTextureGroup::GlyphTexture& FontTextureGroup::glyphTexture(String::Cha
for (auto& entry : directives->entries) for (auto& entry : directives->entries)
processImageOperation(entry.operation, image); processImageOperation(entry.operation, image);
res.first->second.offset = (preSize - Vec2F(image.size())) / 2; glyphTexture.offset = (preSize - Vec2F(image.size())) / 2;
} }
catch (StarException const&) { catch (StarException const&) {
image.forEachPixel([](unsigned x, unsigned y, Vec4B& pixel) { image.forEachPixel([](unsigned x, unsigned y, Vec4B& pixel) {
@ -69,15 +73,14 @@ const FontTextureGroup::GlyphTexture& FontTextureGroup::glyphTexture(String::Cha
}); });
} }
} }
else
res.first->second.offset = Vec2F();
res.first->second.offset += Vec2F(pair.second); glyphTexture.colored = get<2>(renderResult);
res.first->second.texture = m_textureGroup->create(image); glyphTexture.offset += Vec2F(get<1>(renderResult));
glyphTexture.texture = m_textureGroup->create(image);
} }
res.first->second.time = Time::monotonicMilliseconds(); glyphTexture.time = Time::monotonicMilliseconds();
return res.first->second; return glyphTexture;
} }
TexturePtr FontTextureGroup::glyphTexturePtr(String::Char c, unsigned size) { TexturePtr FontTextureGroup::glyphTexturePtr(String::Char c, unsigned size) {
@ -89,9 +92,31 @@ TexturePtr FontTextureGroup::glyphTexturePtr(String::Char c, unsigned size, Dire
} }
unsigned FontTextureGroup::glyphWidth(String::Char c, unsigned fontSize) { unsigned FontTextureGroup::glyphWidth(String::Char c, unsigned fontSize) {
Font* font = (m_font->exists(c) || !m_fallbackFont) ? m_font.get() : m_fallbackFont.get(); Font* font = getFontForCharacter(c);
font->setPixelSize(fontSize); font->setPixelSize(fontSize);
return font->width(c); return font->width(c);
} }
Font* FontTextureGroup::getFontForCharacter(String::Char c) {
if (((c >= 0x1F600 && c <= 0x1F64F) || // Emoticons
(c >= 0x1F300 && c <= 0x1F5FF) || // Misc Symbols and Pictographs
(c >= 0x1F680 && c <= 0x1F6FF) || // Transport and Map
(c >= 0x1F1E6 && c <= 0x1F1FF) || // Regional country flags
(c >= 0x2600 && c <= 0x26FF ) || // Misc symbols 9728 - 9983
(c >= 0x2700 && c <= 0x27BF ) || // Dingbats
(c >= 0xFE00 && c <= 0xFE0F ) || // Variation Selectors
(c >= 0x1F900 && c <= 0x1F9FF) || // Supplemental Symbols and Pictographs
(c >= 0x1F018 && c <= 0x1F270) || // Various asian characters
(c >= 65024 && c <= 65039 ) || // Variation selector
(c >= 9100 && c <= 9300 ) || // Misc items
(c >= 8400 && c <= 8447 ))&& // Combining Diacritical Marks for Symbols
m_emojiFont->exists(c)
)
return m_emojiFont.get();
else if (m_activeFont->exists(c) || !m_fallbackFont)
return m_activeFont.get();
else
return m_fallbackFont.get();
}
} }

View File

@ -16,7 +16,8 @@ public:
struct GlyphTexture { struct GlyphTexture {
TexturePtr texture; TexturePtr texture;
int64_t time; bool colored = false;
int64_t time = 0;
Vec2F offset; Vec2F offset;
}; };
@ -35,16 +36,18 @@ public:
// Switches the current font // Switches the current font
void switchFont(String const& font); void switchFont(String const& font);
String const& activeFont(); String const& activeFont();
void addFont(FontPtr const& font, String const& name, bool isDefault = false); void addFont(FontPtr const& font, String const& name);
void clearFonts(); void clearFonts();
void setFallbackFont(String const& fontName); void setFixedFonts(String const& defaultFontName, String const& fallbackFontName, String const& emojiFontName);
private: private:
Font* getFontForCharacter(String::Char);
CaseInsensitiveStringMap<FontPtr> m_fonts; CaseInsensitiveStringMap<FontPtr> m_fonts;
String m_fontName; String m_fontName;
FontPtr m_font; FontPtr m_activeFont;
FontPtr m_defaultFont; FontPtr m_defaultFont;
FontPtr m_fallbackFont; FontPtr m_fallbackFont;
FontPtr m_emojiFont;
TextureGroupPtr m_textureGroup; TextureGroupPtr m_textureGroup;
HashMap<GlyphDescriptor, GlyphTexture> m_glyphs; HashMap<GlyphDescriptor, GlyphTexture> m_glyphs;

View File

@ -320,14 +320,9 @@ void TextPainter::reloadFonts() {
m_fontTextureGroup.clearFonts(); m_fontTextureGroup.clearFonts();
m_fontTextureGroup.cleanup(0); m_fontTextureGroup.cleanup(0);
auto assets = Root::singleton().assets(); auto assets = Root::singleton().assets();
String defaultName = "hobo";
auto defaultFont = loadFont("/hobo.ttf", defaultName);
auto loadFontsByExtension = [&](String const& ext) { auto loadFontsByExtension = [&](String const& ext) {
for (auto& fontPath : assets->scanExtension(ext)) { for (auto& fontPath : assets->scanExtension(ext)) {
auto font = assets->font(fontPath); auto font = assets->font(fontPath);
if (font == defaultFont)
continue;
auto name = AssetPath::filename(fontPath); auto name = AssetPath::filename(fontPath);
name = name.substr(0, name.findLast(".")); name = name.substr(0, name.findLast("."));
addFont(loadFont(fontPath, name), name); addFont(loadFont(fontPath, name), name);
@ -335,8 +330,10 @@ void TextPainter::reloadFonts() {
}; };
loadFontsByExtension("ttf"); loadFontsByExtension("ttf");
loadFontsByExtension("woff2"); loadFontsByExtension("woff2");
m_fontTextureGroup.addFont(defaultFont, defaultName, true); m_fontTextureGroup.setFixedFonts(
m_fontTextureGroup.setFallbackFont("unifont"); assets->json("/interface.config:font.defaultFont").toString(),
assets->json("/interface.config:font.fallbackFont").toString(),
assets->json("/interface.config:font.emojiFont").toString());
} }
void TextPainter::cleanup(int64_t timeout) { void TextPainter::cleanup(int64_t timeout) {
@ -518,13 +515,15 @@ void TextPainter::renderPrimitives() {
} }
void TextPainter::renderGlyph(String::Char c, Vec2F const& screenPos, List<RenderPrimitive>& out, unsigned fontSize, void TextPainter::renderGlyph(String::Char c, Vec2F const& screenPos, List<RenderPrimitive>& out, unsigned fontSize,
float scale, Vec4B const& color, Directives const* processingDirectives) { float scale, Vec4B color, Directives const* processingDirectives) {
if (!fontSize) if (!fontSize)
return; return;
const FontTextureGroup::GlyphTexture& glyphTexture = m_fontTextureGroup.glyphTexture(c, fontSize, processingDirectives); const FontTextureGroup::GlyphTexture& glyphTexture = m_fontTextureGroup.glyphTexture(c, fontSize, processingDirectives);
Vec2F offset = glyphTexture.offset * scale; if (glyphTexture.colored)
out.emplace_back(std::in_place_type_t<RenderQuad>(), glyphTexture.texture, Vec2F::round(screenPos + offset), scale, color, 0.0f); color[0] = color[1] = color[2] = 255;
out.emplace_back(std::in_place_type_t<RenderQuad>(),
glyphTexture.texture, Vec2F::round(screenPos + glyphTexture.offset * scale), scale, color, 0.0f);
} }
FontPtr TextPainter::loadFont(String const& fontPath, Maybe<String> fontName) { FontPtr TextPainter::loadFont(String const& fontPath, Maybe<String> fontName) {

View File

@ -86,7 +86,7 @@ private:
RectF doRenderGlyph(String::Char c, TextPositioning const& position, bool reallyRender); RectF doRenderGlyph(String::Char c, TextPositioning const& position, bool reallyRender);
void renderPrimitives(); void renderPrimitives();
void renderGlyph(String::Char c, Vec2F const& screenPos, List<RenderPrimitive>& out, unsigned fontSize, float scale, Vec4B const& color, Directives const* processingDirectives = nullptr); void renderGlyph(String::Char c, Vec2F const& screenPos, List<RenderPrimitive>& out, unsigned fontSize, float scale, Vec4B color, Directives const* processingDirectives = nullptr);
static FontPtr loadFont(String const& fontPath, Maybe<String> fontName = {}); static FontPtr loadFont(String const& fontPath, Maybe<String> fontName = {});
RendererPtr m_renderer; RendererPtr m_renderer;