1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-09-15 18:57:08 +00:00

CGUITTFont: Clean up, unify and comment draw code (#16380)

These changes were initially made to improve performance. However,
on modern hardware, these changes turned out to make no difference.

This commit unifies the calculations in 'draw' and 'getDimension' and
adds comments to make it more understandable.
This commit is contained in:
SmallJoker 2025-09-10 20:23:45 +02:00 committed by GitHub
parent 69497200f9
commit b6a23b1bcc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 97 additions and 91 deletions

View file

@ -287,8 +287,7 @@ CGUITTFont* CGUITTFont::createTTFont(IGUIEnvironment *env,
//! 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), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0), batch_load_size(1)
shadow_offset(0), shadow_alpha(0), fallback(0)
{ {
if (env) { if (env) {
@ -491,35 +490,34 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& positio
textDimension = getDimension(text.c_str()); textDimension = getDimension(text.c_str());
if (hcenter) if (hcenter)
offset.X = ((position.getWidth() - textDimension.Width) >> 1) + offset.X; offset.X = ((position.getWidth() - textDimension.Width) / 2) + offset.X;
if (vcenter) if (vcenter)
offset.Y = ((position.getHeight() - textDimension.Height) >> 1) + offset.Y; offset.Y = ((position.getHeight() - textDimension.Height) / 2) + offset.Y;
} }
// Convert to a unicode string. // Convert to a unicode string.
std::u32string utext = convertWCharToU32String(text.c_str()); const std::u32string utext = convertWCharToU32String(text.c_str());
const u32 lineHeight = getLineHeight();
// Set up our render map. // Key: Glyph page index. Value: Arrays relevant for rendering
std::map<u32, CGUITTGlyphPage*> Render_Map; std::map<u32, CGUITTGlyphPage*> Render_Map;
// Start parsing characters. // Start parsing characters.
u32 n; // The same logic is applied to `CGUITTFont::getDimension`
char32_t previousChar = 0; char32_t previousChar = 0;
std::u32string::const_iterator iter = utext.begin(); for (size_t i = 0; i < utext.size(); ++i)
while (iter != utext.end())
{ {
char32_t currentChar = *iter; char32_t currentChar = utext[i];
n = getGlyphIndexByChar(currentChar); bool lineBreak = false;
bool visible = (Invisible.find_first_of(currentChar) == std::u32string::npos); if (currentChar == U'\r') // Mac or Windows breaks
bool lineBreak=false;
if (currentChar == L'\r') // Mac or Windows breaks
{ {
lineBreak = true; lineBreak = true;
if (*(iter + 1) == (char32_t)'\n') // Windows line breaks. // `std::u32string` is '\0'-terminated, thus this check is OK
currentChar = *(++iter); if (utext[i + 1] == U'\n') // Windows line breaks.
currentChar = utext[++i];
} }
else if (currentChar == (char32_t)'\n') // Unix breaks else if (currentChar == U'\n') // Unix breaks
{ {
lineBreak = true; lineBreak = true;
} }
@ -527,82 +525,90 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& positio
if (lineBreak) if (lineBreak)
{ {
previousChar = 0; previousChar = 0;
offset.Y += font_metrics.height / 64; offset.Y += lineHeight;
offset.X = position.UpperLeftCorner.X; offset.X = position.UpperLeftCorner.X;
if (hcenter) if (hcenter)
offset.X += (position.getWidth() - textDimension.Width) >> 1; offset.X += (position.getWidth() - textDimension.Width) / 2;
++iter;
continue; continue;
} }
if (n > 0 && visible) // Draw visible text
SGUITTGlyph *glyph = nullptr;
const u32 width = getWidthFromCharacter(currentChar);
// Skip whitespace characters
if (InvisibleChars.find_first_of(currentChar) != std::u32string::npos)
goto skip_invisible;
if (clip) {
// Skip fully clipped characters.
const core::recti rect(
offset,
offset + core::vector2di(width, lineHeight)
);
if (!clip->isRectCollided(rect))
goto skip_invisible;
}
{
// Retrieve the glyph
const u32 n = getGlyphIndexByChar(currentChar);
if (n > 0)
glyph = &Glyphs[n - 1];
}
if (glyph)
{ {
// Calculate the glyph offset. // Calculate the glyph offset.
s32 offx = Glyphs[n-1].offset.X; const s32 offx = glyph->offset.X;
s32 offy = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y; const s32 offy = (font_metrics.ascender / 64) - glyph->offset.Y;
// Apply kerning. // Apply kerning.
core::vector2di k = getKerning(currentChar, previousChar); offset += getKerning(currentChar, previousChar);
offset.X += k.X;
offset.Y += k.Y;
// Determine rendering information. // Determine rendering information.
SGUITTGlyph& glyph = Glyphs[n-1]; CGUITTGlyphPage* const page = Glyph_Pages[glyph->glyph_page];
CGUITTGlyphPage* const page = Glyph_Pages[glyph.glyph_page];
page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy)); page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy));
page->render_source_rects.push_back(glyph.source_rect); page->render_source_rects.push_back(glyph->source_rect);
const size_t iterPos = iter - utext.begin(); if (i < colors.size())
if (iterPos < colors.size()) page->render_colors.push_back(colors[i]);
page->render_colors.push_back(colors[iterPos]);
else else
page->render_colors.push_back(video::SColor(255,255,255,255)); page->render_colors.push_back(video::SColor(255,255,255,255));
Render_Map[glyph.glyph_page] = page; Render_Map[glyph->glyph_page] = page;
} }
if (n > 0) else if (fallback)
{
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 // Let the fallback font draw it, this isn't super efficient but hopefully that doesn't matter
wchar_t l1[] = { (wchar_t) currentChar, 0 }; wchar_t l1[] = { (wchar_t) currentChar, 0 };
if (visible)
{
// Apply kerning. // Apply kerning.
offset += fallback->getKerning(*l1, (wchar_t) previousChar); offset += fallback->getKerning(*l1, (wchar_t) previousChar);
const u32 current_color = iter - utext.begin();
fallback->draw(core::stringw(l1), fallback->draw(core::stringw(l1),
core::rect<s32>({offset.X-1, offset.Y-1}, position.LowerRightCorner), // ??? core::rect<s32>({offset.X-1, offset.Y-1}, position.LowerRightCorner), // ???
current_color < colors.size() ? colors[current_color] : video::SColor(255, 255, 255, 255), i < colors.size() ? colors[i] : video::SColor(255, 255, 255, 255),
false, false, clip); false, false, clip);
} }
offset.X += fallback->getDimension(l1).Width; skip_invisible:
} offset.X += width;
previousChar = currentChar; previousChar = currentChar;
++iter;
} }
// Draw now. // Draw now.
update_glyph_pages(); update_glyph_pages();
auto it = Render_Map.begin();
auto ie = Render_Map.end();
core::array<core::vector2di> tmp_positions; core::array<core::vector2di> tmp_positions;
core::array<core::recti> tmp_source_rects; core::array<core::recti> tmp_source_rects;
while (it != ie) for (const auto &it : Render_Map)
{ {
CGUITTGlyphPage* page = it->second; CGUITTGlyphPage *page = it.second;
++it;
// render runs of matching color in batch // render runs of matching color in batch
size_t ibegin;
video::SColor colprev; video::SColor colprev;
for (size_t i = 0; i < page->render_positions.size(); ++i) { for (size_t i = 0; i < page->render_positions.size(); ++i) {
ibegin = i; const size_t ibegin = i;
colprev = page->render_colors[i]; colprev = page->render_colors[i];
do do
++i; ++i;
@ -636,7 +642,7 @@ core::dimension2d<u32> CGUITTFont::getDimension(const wchar_t* text) const
return getDimension(convertWCharToU32String(text)); return getDimension(convertWCharToU32String(text));
} }
core::dimension2d<u32> CGUITTFont::getDimension(const std::u32string& text) const core::dimension2d<u32> CGUITTFont::getDimension(const std::u32string& utext) const
{ {
// Get the maximum font height. Unfortunately, we have to do this hack as // Get the maximum font height. Unfortunately, we have to do this hack as
// Irrlicht will draw things wrong. In FreeType, the font size is the // Irrlicht will draw things wrong. In FreeType, the font size is the
@ -645,39 +651,29 @@ core::dimension2d<u32> CGUITTFont::getDimension(const std::u32string& text) cons
// Irrlicht does not understand this concept when drawing fonts. Also, I // Irrlicht does not understand this concept when drawing fonts. Also, I
// add +1 to give it a 1 pixel blank border. This makes things like // add +1 to give it a 1 pixel blank border. This makes things like
// tooltips look nicer. // tooltips look nicer.
s32 test1 = getHeightFromCharacter((char32_t)'g') + 1; const u32 lineHeight = getLineHeight();
s32 test2 = getHeightFromCharacter((char32_t)'j') + 1;
s32 test3 = getHeightFromCharacter((char32_t)'_') + 1;
s32 max_font_height = core::max_(test1, core::max_(test2, test3));
core::dimension2d<u32> text_dimension(0, max_font_height); core::dimension2d<u32> text_dimension(0, lineHeight);
core::dimension2d<u32> line(0, max_font_height); core::dimension2d<u32> line(0, lineHeight);
// The same logic is applied to `CGUITTFont::draw`
char32_t previousChar = 0; char32_t previousChar = 0;
std::u32string::const_iterator iter = text.begin(); for (size_t i = 0; i < utext.size(); ++i)
for (; iter != text.end(); ++iter)
{ {
char32_t p = *iter; char32_t currentChar = utext[i];
bool lineBreak = false; bool lineBreak = false;
if (p == '\r') // Mac or Windows line breaks. if (currentChar == U'\r') // Mac or Windows breaks
{ {
lineBreak = true; lineBreak = true;
if (iter + 1 != text.end() && *(iter + 1) == '\n') // `std::u32string` is '\0'-terminated, thus this check is OK
{ if (utext[i + 1] == U'\n') // Windows line breaks.
++iter; currentChar = utext[++i];
p = *iter;
} }
} else if (currentChar == U'\n') // Unix breaks
else if (p == '\n') // Unix line breaks.
{ {
lineBreak = true; lineBreak = true;
} }
// Kerning.
core::vector2di k = getKerning(p, previousChar);
line.Width += k.X;
previousChar = p;
// Check for linebreak. // Check for linebreak.
if (lineBreak) if (lineBreak)
{ {
@ -686,10 +682,15 @@ core::dimension2d<u32> CGUITTFont::getDimension(const std::u32string& text) cons
if (text_dimension.Width < line.Width) if (text_dimension.Width < line.Width)
text_dimension.Width = line.Width; text_dimension.Width = line.Width;
line.Width = 0; line.Width = 0;
line.Height = max_font_height; line.Height = lineHeight;
continue; continue;
} }
line.Width += getWidthFromCharacter(p);
// Kerning.
line.Width += getKerning(currentChar, previousChar).X;
previousChar = currentChar;
line.Width += getWidthFromCharacter(currentChar);
} }
if (text_dimension.Width < line.Width) if (text_dimension.Width < line.Width)
text_dimension.Width = line.Width; text_dimension.Width = line.Width;
@ -873,7 +874,7 @@ core::vector2di CGUITTFont::getKerning(const char32_t thisLetter, const char32_t
void CGUITTFont::setInvisibleCharacters(const wchar_t *s) void CGUITTFont::setInvisibleCharacters(const wchar_t *s)
{ {
Invisible = convertWCharToU32String(s); InvisibleChars = convertWCharToU32String(s);
} }
video::IImage* CGUITTFont::createTextureFromChar(const char32_t& ch) video::IImage* CGUITTFont::createTextureFromChar(const char32_t& ch)

View file

@ -298,7 +298,7 @@ namespace gui
void setFontHinting(const bool enable, const bool enable_auto_hinting = true); void setFontHinting(const bool enable, const bool enable_auto_hinting = true);
//! Draws some text and clips it to the specified rectangle if wanted. //! Draws some text and clips it to the specified rectangle if wanted.
virtual void draw(const core::stringw& text, const core::rect<s32>& position, virtual void draw(const core::stringw& utext, 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) override; const core::rect<s32>* clip=0) override;
@ -383,12 +383,17 @@ namespace gui
if (useMonochrome()) load_flags |= FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO; if (useMonochrome()) load_flags |= FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO;
else load_flags |= FT_LOAD_TARGET_NORMAL; else load_flags |= FT_LOAD_TARGET_NORMAL;
} }
/// Gets the overall font height, including a line gap of 1 px
inline u32 getLineHeight() const { return font_metrics.height / 64 + 1; }
u32 getWidthFromCharacter(char32_t c) const; u32 getWidthFromCharacter(char32_t c) const;
u32 getHeightFromCharacter(char32_t c) const; u32 getHeightFromCharacter(char32_t c) const;
/// Returns (index + 1) of `this->Glyphs`
/// Returns 0 if no such glyph is provided by the font.
u32 getGlyphIndexByChar(char32_t c) const; u32 getGlyphIndexByChar(char32_t c) const;
core::vector2di getKerning(const char32_t thisLetter, const char32_t previousLetter) const; core::vector2di getKerning(const char32_t thisLetter, const char32_t previousLetter) const;
video::IVideoDriver* Driver; video::IVideoDriver* Driver = nullptr;
std::optional<io::path> filename; std::optional<io::path> filename;
FT_Face tt_face; FT_Face tt_face;
FT_Size_Metrics font_metrics; FT_Size_Metrics font_metrics;
@ -397,13 +402,13 @@ namespace gui
mutable core::array<CGUITTGlyphPage*> Glyph_Pages; mutable core::array<CGUITTGlyphPage*> Glyph_Pages;
mutable core::array<SGUITTGlyph> Glyphs; mutable core::array<SGUITTGlyph> Glyphs;
s32 GlobalKerningWidth; s32 GlobalKerningWidth = 0;
s32 GlobalKerningHeight; s32 GlobalKerningHeight = 0;
std::u32string Invisible; std::u32string InvisibleChars;
u32 shadow_offset; u32 shadow_offset = 0;
u32 shadow_alpha; u32 shadow_alpha = 0;
gui::IGUIFont* fallback; gui::IGUIFont *fallback = nullptr;
}; };
} // end namespace gui } // end namespace gui