From b6a23b1bcccccafa7f124310398c0094cd313d0e Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Wed, 10 Sep 2025 20:23:45 +0200 Subject: [PATCH] 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. --- src/irrlicht_changes/CGUITTFont.cpp | 167 ++++++++++++++-------------- src/irrlicht_changes/CGUITTFont.h | 21 ++-- 2 files changed, 97 insertions(+), 91 deletions(-) diff --git a/src/irrlicht_changes/CGUITTFont.cpp b/src/irrlicht_changes/CGUITTFont.cpp index 31d1dd3766..01303220f4 100644 --- a/src/irrlicht_changes/CGUITTFont.cpp +++ b/src/irrlicht_changes/CGUITTFont.cpp @@ -287,8 +287,7 @@ CGUITTFont* CGUITTFont::createTTFont(IGUIEnvironment *env, //! Constructor. CGUITTFont::CGUITTFont(IGUIEnvironment *env) : use_monochrome(false), use_transparency(true), use_hinting(true), use_auto_hinting(true), -batch_load_size(1), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0), -shadow_offset(0), shadow_alpha(0), fallback(0) +batch_load_size(1) { if (env) { @@ -491,35 +490,34 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect& positio textDimension = getDimension(text.c_str()); if (hcenter) - offset.X = ((position.getWidth() - textDimension.Width) >> 1) + offset.X; + offset.X = ((position.getWidth() - textDimension.Width) / 2) + offset.X; 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. - 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 Render_Map; // Start parsing characters. - u32 n; + // The same logic is applied to `CGUITTFont::getDimension` char32_t previousChar = 0; - std::u32string::const_iterator iter = utext.begin(); - while (iter != utext.end()) + for (size_t i = 0; i < utext.size(); ++i) { - char32_t currentChar = *iter; - n = getGlyphIndexByChar(currentChar); - bool visible = (Invisible.find_first_of(currentChar) == std::u32string::npos); - bool lineBreak=false; - if (currentChar == L'\r') // Mac or Windows breaks + char32_t currentChar = utext[i]; + bool lineBreak = false; + if (currentChar == U'\r') // Mac or Windows breaks { lineBreak = true; - if (*(iter + 1) == (char32_t)'\n') // Windows line breaks. - currentChar = *(++iter); + // `std::u32string` is '\0'-terminated, thus this check is OK + 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; } @@ -527,82 +525,90 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect& positio if (lineBreak) { previousChar = 0; - offset.Y += font_metrics.height / 64; + offset.Y += lineHeight; offset.X = position.UpperLeftCorner.X; if (hcenter) - offset.X += (position.getWidth() - textDimension.Width) >> 1; - ++iter; + offset.X += (position.getWidth() - textDimension.Width) / 2; 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. - s32 offx = Glyphs[n-1].offset.X; - s32 offy = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y; + const s32 offx = glyph->offset.X; + const s32 offy = (font_metrics.ascender / 64) - glyph->offset.Y; // Apply kerning. - core::vector2di k = getKerning(currentChar, previousChar); - offset.X += k.X; - offset.Y += k.Y; + offset += getKerning(currentChar, previousChar); // 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_source_rects.push_back(glyph.source_rect); - const size_t iterPos = iter - utext.begin(); - if (iterPos < colors.size()) - page->render_colors.push_back(colors[iterPos]); + page->render_source_rects.push_back(glyph->source_rect); + if (i < colors.size()) + page->render_colors.push_back(colors[i]); else 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) - { - offset.X += getWidthFromCharacter(currentChar); - } - else if (fallback != 0) + else if (fallback) { // Let the fallback font draw it, this isn't super efficient but hopefully that doesn't matter wchar_t l1[] = { (wchar_t) currentChar, 0 }; - if (visible) - { - // Apply kerning. - offset += fallback->getKerning(*l1, (wchar_t) previousChar); + // Apply kerning. + offset += fallback->getKerning(*l1, (wchar_t) previousChar); - const u32 current_color = iter - utext.begin(); - fallback->draw(core::stringw(l1), - core::rect({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; + fallback->draw(core::stringw(l1), + core::rect({offset.X-1, offset.Y-1}, position.LowerRightCorner), // ??? + i < colors.size() ? colors[i] : video::SColor(255, 255, 255, 255), + false, false, clip); } +skip_invisible: + offset.X += width; previousChar = currentChar; - ++iter; } // Draw now. update_glyph_pages(); - auto it = Render_Map.begin(); - auto ie = Render_Map.end(); core::array tmp_positions; core::array tmp_source_rects; - while (it != ie) + for (const auto &it : Render_Map) { - CGUITTGlyphPage* page = it->second; - ++it; + CGUITTGlyphPage *page = it.second; // render runs of matching color in batch - size_t ibegin; video::SColor colprev; for (size_t i = 0; i < page->render_positions.size(); ++i) { - ibegin = i; + const size_t ibegin = i; colprev = page->render_colors[i]; do ++i; @@ -636,7 +642,7 @@ core::dimension2d CGUITTFont::getDimension(const wchar_t* text) const return getDimension(convertWCharToU32String(text)); } -core::dimension2d CGUITTFont::getDimension(const std::u32string& text) const +core::dimension2d CGUITTFont::getDimension(const std::u32string& utext) const { // 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 @@ -645,39 +651,29 @@ core::dimension2d CGUITTFont::getDimension(const std::u32string& text) cons // 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 // tooltips look nicer. - s32 test1 = getHeightFromCharacter((char32_t)'g') + 1; - s32 test2 = getHeightFromCharacter((char32_t)'j') + 1; - s32 test3 = getHeightFromCharacter((char32_t)'_') + 1; - s32 max_font_height = core::max_(test1, core::max_(test2, test3)); + const u32 lineHeight = getLineHeight(); - core::dimension2d text_dimension(0, max_font_height); - core::dimension2d line(0, max_font_height); + core::dimension2d text_dimension(0, lineHeight); + core::dimension2d line(0, lineHeight); + // The same logic is applied to `CGUITTFont::draw` char32_t previousChar = 0; - std::u32string::const_iterator iter = text.begin(); - for (; iter != text.end(); ++iter) + for (size_t i = 0; i < utext.size(); ++i) { - char32_t p = *iter; + char32_t currentChar = utext[i]; bool lineBreak = false; - if (p == '\r') // Mac or Windows line breaks. + if (currentChar == U'\r') // Mac or Windows breaks { lineBreak = true; - if (iter + 1 != text.end() && *(iter + 1) == '\n') - { - ++iter; - p = *iter; - } + // `std::u32string` is '\0'-terminated, thus this check is OK + if (utext[i + 1] == U'\n') // Windows line breaks. + currentChar = utext[++i]; } - else if (p == '\n') // Unix line breaks. + else if (currentChar == U'\n') // Unix breaks { lineBreak = true; } - // Kerning. - core::vector2di k = getKerning(p, previousChar); - line.Width += k.X; - previousChar = p; - // Check for linebreak. if (lineBreak) { @@ -686,10 +682,15 @@ core::dimension2d CGUITTFont::getDimension(const std::u32string& text) cons if (text_dimension.Width < line.Width) text_dimension.Width = line.Width; line.Width = 0; - line.Height = max_font_height; + line.Height = lineHeight; continue; } - line.Width += getWidthFromCharacter(p); + + // Kerning. + line.Width += getKerning(currentChar, previousChar).X; + + previousChar = currentChar; + line.Width += getWidthFromCharacter(currentChar); } if (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) { - Invisible = convertWCharToU32String(s); + InvisibleChars = convertWCharToU32String(s); } video::IImage* CGUITTFont::createTextureFromChar(const char32_t& ch) diff --git a/src/irrlicht_changes/CGUITTFont.h b/src/irrlicht_changes/CGUITTFont.h index 1073b9fdc1..00e621ee37 100644 --- a/src/irrlicht_changes/CGUITTFont.h +++ b/src/irrlicht_changes/CGUITTFont.h @@ -298,7 +298,7 @@ namespace gui void setFontHinting(const bool enable, const bool enable_auto_hinting = true); //! Draws some text and clips it to the specified rectangle if wanted. - virtual void draw(const core::stringw& text, const core::rect& position, + virtual void draw(const core::stringw& utext, const core::rect& position, video::SColor color, bool hcenter=false, bool vcenter=false, const core::rect* clip=0) override; @@ -383,12 +383,17 @@ namespace gui if (useMonochrome()) load_flags |= FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO; 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 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; core::vector2di getKerning(const char32_t thisLetter, const char32_t previousLetter) const; - video::IVideoDriver* Driver; + video::IVideoDriver* Driver = nullptr; std::optional filename; FT_Face tt_face; FT_Size_Metrics font_metrics; @@ -397,13 +402,13 @@ namespace gui mutable core::array Glyph_Pages; mutable core::array Glyphs; - s32 GlobalKerningWidth; - s32 GlobalKerningHeight; - std::u32string Invisible; - u32 shadow_offset; - u32 shadow_alpha; + s32 GlobalKerningWidth = 0; + s32 GlobalKerningHeight = 0; + std::u32string InvisibleChars; + u32 shadow_offset = 0; + u32 shadow_alpha = 0; - gui::IGUIFont* fallback; + gui::IGUIFont *fallback = nullptr; }; } // end namespace gui