From ad738a28033f417a9a08e967a1262fcaed0e6a74 Mon Sep 17 00:00:00 2001 From: SirLynix Date: Thu, 24 Aug 2023 08:42:25 +0200 Subject: [PATCH] Core/StringExt: Add IterateOnCodepoints to remove std::u32string allocations --- include/Nazara/Core/StringExt.hpp | 3 + include/Nazara/Utility/Font.hpp | 2 +- src/Nazara/Core/StringExt.cpp | 23 ++++ src/Nazara/Utility/Font.cpp | 20 +-- src/Nazara/Utility/RichTextDrawer.cpp | 148 +++++++++++----------- src/Nazara/Utility/SimpleTextDrawer.cpp | 155 ++++++++++++------------ 6 files changed, 187 insertions(+), 164 deletions(-) diff --git a/include/Nazara/Core/StringExt.hpp b/include/Nazara/Core/StringExt.hpp index 3e8473306..bf5e5beaf 100644 --- a/include/Nazara/Core/StringExt.hpp +++ b/include/Nazara/Core/StringExt.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace Nz @@ -35,6 +36,8 @@ namespace Nz inline bool IsNumber(std::string_view str); + NAZARA_CORE_API void IterateOnCodepoints(std::string_view str, FunctionRef callback); + NAZARA_CORE_API bool MatchPattern(std::string_view str, std::string_view pattern); inline std::string NumberToString(long long number, UInt8 radix = 10); diff --git a/include/Nazara/Utility/Font.hpp b/include/Nazara/Utility/Font.hpp index 05fd16b2b..5fd1718f3 100644 --- a/include/Nazara/Utility/Font.hpp +++ b/include/Nazara/Utility/Font.hpp @@ -73,7 +73,7 @@ namespace Nz bool IsValid() const; bool Precache(unsigned int characterSize, TextStyleFlags style, float outlineThickness, char32_t character) const; - bool Precache(unsigned int characterSize, TextStyleFlags style, float outlineThickness, const std::string& characterSet) const; + bool Precache(unsigned int characterSize, TextStyleFlags style, float outlineThickness, std::string_view characterSet) const; void SetAtlas(std::shared_ptr atlas); void SetGlyphBorder(unsigned int borderSize); diff --git a/src/Nazara/Core/StringExt.cpp b/src/Nazara/Core/StringExt.cpp index 1b1c7bace..e1bd3b710 100644 --- a/src/Nazara/Core/StringExt.cpp +++ b/src/Nazara/Core/StringExt.cpp @@ -259,6 +259,29 @@ namespace Nz return {}; } + void IterateOnCodepoints(std::string_view str, FunctionRef callback) + { + std::array buffer; + std::size_t charCount = 0; + + utf8::unchecked::iterator it(str.data()); + utf8::unchecked::iterator end(str.data() + str.size()); + for (; it != end; ++it) + { + buffer[charCount++] = *it; + if (charCount == buffer.size()) + { + if (!callback(&buffer[0], charCount)) + return; + + charCount = 0; + } + } + + if (charCount != 0) + callback(&buffer[0], charCount); + } + bool MatchPattern(std::string_view str, std::string_view pattern) { if (str.empty() || pattern.empty()) diff --git a/src/Nazara/Utility/Font.cpp b/src/Nazara/Utility/Font.cpp index 36309a587..5d1b5c1d1 100644 --- a/src/Nazara/Utility/Font.cpp +++ b/src/Nazara/Utility/Font.cpp @@ -256,20 +256,20 @@ namespace Nz return PrecacheGlyph(m_glyphes[key], characterSize, style, outlineThickness, character).valid; } - bool Font::Precache(unsigned int characterSize, TextStyleFlags style, float outlineThickness, const std::string& characterSet) const + bool Font::Precache(unsigned int characterSize, TextStyleFlags style, float outlineThickness, std::string_view characterSet) const { - ///TODO: Itération UTF-8 => UTF-32 sans allocation de buffer (Exposer utf8cpp ?) - std::u32string set = ToUtf32String(characterSet); - if (set.empty()) - { - NazaraError("Invalid character set"); - return false; - } + NazaraAssert(!characterSet.empty(), "empty character set"); UInt64 key = ComputeKey(characterSize, style, outlineThickness); auto& glyphMap = m_glyphes[key]; - for (char32_t character : set) - PrecacheGlyph(glyphMap, characterSize, style, outlineThickness, character); + + IterateOnCodepoints(characterSet, [&](const char32_t* characters, std::size_t characterCount) + { + for (std::size_t i = 0; i < characterCount; ++i) + PrecacheGlyph(glyphMap, characterSize, style, outlineThickness, characters[i]); + + return true; + }); return true; } diff --git a/src/Nazara/Utility/RichTextDrawer.cpp b/src/Nazara/Utility/RichTextDrawer.cpp index d39f5d1e6..87f6ef6df 100644 --- a/src/Nazara/Utility/RichTextDrawer.cpp +++ b/src/Nazara/Utility/RichTextDrawer.cpp @@ -381,14 +381,6 @@ namespace Nz if (text.empty()) return; - ///TODO: Allow iteration on Unicode characters without allocating any buffer - std::u32string characters = ToUtf32String(text); - if (characters.empty()) - { - NazaraError("Invalid character set"); - return; - } - char32_t previousCharacter = 0; const Font::SizeInfo& sizeInfo = font.GetSizeInfo(characterSize); @@ -410,85 +402,91 @@ namespace Nz m_lines.back().bounds.height += heightDifference; } - m_glyphs.reserve(m_glyphs.size() + characters.size() * ((outlineThickness > 0.f) ? 2 : 1)); - for (char32_t character : characters) + IterateOnCodepoints(text, [&](const char32_t* characters, std::size_t characterCount) { - if (previousCharacter != 0) - m_drawPos.x += font.GetKerning(characterSize, previousCharacter, character); - - previousCharacter = character; - - bool whitespace = true; - float advance = characterSpacingOffset; - switch (character) + for (std::size_t i = 0; i < characterCount; ++i) { - case ' ': - case '\n': - advance += float(sizeInfo.spaceAdvance); - break; + char32_t character = characters[i]; - case '\t': - advance += float(sizeInfo.spaceAdvance) * 4.f; - break; + if (previousCharacter != 0) + m_drawPos.x += font.GetKerning(characterSize, previousCharacter, character); - default: - whitespace = false; - break; - } + previousCharacter = character; - Glyph glyph; - if (!whitespace) - { - int iAdvance; - if (!GenerateGlyph(glyph, character, 0.f, true, font, color, style, lineSpacingOffset, characterSize, 0, &iAdvance)) - continue; // Glyph failed to load, just skip it (can't do much) - - advance += float(iAdvance); - - if (outlineThickness > 0.f) + bool whitespace = true; + float advance = characterSpacingOffset; + switch (character) { - Glyph outlineGlyph; - if (GenerateGlyph(outlineGlyph, character, outlineThickness, false, font, outlineColor, style, lineSpacingOffset, characterSize, -1, nullptr)) - m_glyphs.push_back(outlineGlyph); - } - } - else - { - if (ShouldLineWrap(advance)) - AppendNewLine(font, characterSize, lineSpacingOffset, m_lastSeparatorGlyph, m_lastSeparatorPosition); + case ' ': + case '\n': + advance += float(sizeInfo.spaceAdvance); + break; - glyph.atlas = nullptr; - glyph.bounds = Rectf(m_drawPos.x, m_lines.back().bounds.y, advance, lineHeight); + case '\t': + advance += float(sizeInfo.spaceAdvance) * 4.f; + break; - glyph.corners[0] = glyph.bounds.GetCorner(RectCorner::LeftTop); - glyph.corners[1] = glyph.bounds.GetCorner(RectCorner::RightTop); - glyph.corners[2] = glyph.bounds.GetCorner(RectCorner::LeftBottom); - glyph.corners[3] = glyph.bounds.GetCorner(RectCorner::RightBottom); - } - - m_lines.back().bounds.ExtendTo(glyph.bounds); - - switch (character) - { - case '\n': - { - AppendNewLine(font, characterSize, lineSpacingOffset); - break; + default: + whitespace = false; + break; } - default: - m_drawPos.x += advance; - break; + Glyph glyph; + if (!whitespace) + { + int iAdvance; + if (!GenerateGlyph(glyph, character, 0.f, true, font, color, style, lineSpacingOffset, characterSize, 0, &iAdvance)) + continue; // Glyph failed to load, just skip it (can't do much) + + advance += float(iAdvance); + + if (outlineThickness > 0.f) + { + Glyph outlineGlyph; + if (GenerateGlyph(outlineGlyph, character, outlineThickness, false, font, outlineColor, style, lineSpacingOffset, characterSize, -1, nullptr)) + m_glyphs.push_back(outlineGlyph); + } + } + else + { + if (ShouldLineWrap(advance)) + AppendNewLine(font, characterSize, lineSpacingOffset, m_lastSeparatorGlyph, m_lastSeparatorPosition); + + glyph.atlas = nullptr; + glyph.bounds = Rectf(m_drawPos.x, m_lines.back().bounds.y, advance, lineHeight); + + glyph.corners[0] = glyph.bounds.GetCorner(RectCorner::LeftTop); + glyph.corners[1] = glyph.bounds.GetCorner(RectCorner::RightTop); + glyph.corners[2] = glyph.bounds.GetCorner(RectCorner::LeftBottom); + glyph.corners[3] = glyph.bounds.GetCorner(RectCorner::RightBottom); + } + + m_lines.back().bounds.ExtendTo(glyph.bounds); + + switch (character) + { + case '\n': + { + AppendNewLine(font, characterSize, lineSpacingOffset); + break; + } + + default: + m_drawPos.x += advance; + break; + } + + if (whitespace) + { + m_lastSeparatorGlyph = m_glyphs.size(); + m_lastSeparatorPosition = m_drawPos.x; + } + + m_glyphs.push_back(glyph); } - if (whitespace) - { - m_lastSeparatorGlyph = m_glyphs.size(); - m_lastSeparatorPosition = m_drawPos.x; - } - - m_glyphs.push_back(glyph); - } + return true; //< continue iteration + }); m_bounds.ExtendTo(m_lines.back().bounds); diff --git a/src/Nazara/Utility/SimpleTextDrawer.cpp b/src/Nazara/Utility/SimpleTextDrawer.cpp index b0aace5ac..0adeeea78 100644 --- a/src/Nazara/Utility/SimpleTextDrawer.cpp +++ b/src/Nazara/Utility/SimpleTextDrawer.cpp @@ -187,97 +187,96 @@ namespace Nz if (text.empty()) return; - ///TODO: Allow iteration on Unicode characters without allocating any buffer - std::u32string characters = ToUtf32String(text); - if (characters.empty()) - { - NazaraError("Invalid character set"); - return; - } - const Font::SizeInfo& sizeInfo = m_font->GetSizeInfo(m_characterSize); - m_glyphs.reserve(m_glyphs.size() + characters.size() * ((m_outlineThickness > 0.f) ? 2 : 1)); - for (char32_t character : characters) + + IterateOnCodepoints(text, [&](const char32_t* characters, std::size_t characterCount) { - if (m_previousCharacter != 0) - m_drawPos.x += m_font->GetKerning(m_characterSize, m_previousCharacter, character); - - m_previousCharacter = character; - - bool whitespace = true; - float advance = m_characterSpacingOffset; - switch (character) + for (std::size_t i = 0; i < characterCount; ++i) { - case ' ': - case '\n': - advance += float(sizeInfo.spaceAdvance); - break; + char32_t character = characters[i]; + + if (m_previousCharacter != 0) + m_drawPos.x += m_font->GetKerning(m_characterSize, m_previousCharacter, character); - case '\t': - advance += float(sizeInfo.spaceAdvance) * 4.f; - break; + m_previousCharacter = character; - default: - whitespace = false; - break; - } - - Glyph glyph; - if (!whitespace) - { - int glyphRenderOrder = (m_outlineThickness > 0.f) ? 1 : 0; - - int iAdvance; - if (!GenerateGlyph(glyph, character, 0.f, true, m_color, glyphRenderOrder, &iAdvance)) - continue; // Glyph failed to load, just skip it (can't do much) - - advance += float(iAdvance); - - if (m_outlineThickness > 0.f) + bool whitespace = true; + float advance = m_characterSpacingOffset; + switch (character) { - Glyph outlineGlyph; - if (GenerateGlyph(outlineGlyph, character, m_outlineThickness, false, m_outlineColor, glyphRenderOrder - 1, nullptr)) - m_glyphs.push_back(outlineGlyph); - } - } - else - { - if (ShouldLineWrap(advance)) - AppendNewLine(m_lastSeparatorGlyph, m_lastSeparatorPosition); + case ' ': + case '\n': + advance += float(sizeInfo.spaceAdvance); + break; - glyph.atlas = nullptr; - glyph.bounds = Rectf(m_drawPos.x, m_lines.back().bounds.y, advance, GetLineHeight(sizeInfo)); + case '\t': + advance += float(sizeInfo.spaceAdvance) * 4.f; + break; - glyph.corners[0] = glyph.bounds.GetCorner(RectCorner::LeftTop); - glyph.corners[1] = glyph.bounds.GetCorner(RectCorner::RightTop); - glyph.corners[2] = glyph.bounds.GetCorner(RectCorner::LeftBottom); - glyph.corners[3] = glyph.bounds.GetCorner(RectCorner::RightBottom); - } - - m_lines.back().bounds.ExtendTo(glyph.bounds); - - switch (character) - { - case '\n': - { - AppendNewLine(); - break; + default: + whitespace = false; + break; } - default: - m_drawPos.x += advance; - break; + Glyph glyph; + if (!whitespace) + { + int glyphRenderOrder = (m_outlineThickness > 0.f) ? 1 : 0; + + int iAdvance; + if (!GenerateGlyph(glyph, character, 0.f, true, m_color, glyphRenderOrder, &iAdvance)) + continue; // Glyph failed to load, just skip it (can't do much) + + advance += float(iAdvance); + + if (m_outlineThickness > 0.f) + { + Glyph outlineGlyph; + if (GenerateGlyph(outlineGlyph, character, m_outlineThickness, false, m_outlineColor, glyphRenderOrder - 1, nullptr)) + m_glyphs.push_back(outlineGlyph); + } + } + else + { + if (ShouldLineWrap(advance)) + AppendNewLine(m_lastSeparatorGlyph, m_lastSeparatorPosition); + + glyph.atlas = nullptr; + glyph.bounds = Rectf(m_drawPos.x, m_lines.back().bounds.y, advance, GetLineHeight(sizeInfo)); + + glyph.corners[0] = glyph.bounds.GetCorner(RectCorner::LeftTop); + glyph.corners[1] = glyph.bounds.GetCorner(RectCorner::RightTop); + glyph.corners[2] = glyph.bounds.GetCorner(RectCorner::LeftBottom); + glyph.corners[3] = glyph.bounds.GetCorner(RectCorner::RightBottom); + } + + m_lines.back().bounds.ExtendTo(glyph.bounds); + + switch (character) + { + case '\n': + { + AppendNewLine(); + break; + } + + default: + m_drawPos.x += advance; + break; + } + + if (whitespace) + { + m_lastSeparatorGlyph = m_glyphs.size(); + m_lastSeparatorPosition = m_drawPos.x; + } + + m_glyphs.push_back(glyph); } - if (whitespace) - { - m_lastSeparatorGlyph = m_glyphs.size(); - m_lastSeparatorPosition = m_drawPos.x; - } - - m_glyphs.push_back(glyph); - } + return true; //< continue iteration + }); m_bounds.ExtendTo(m_lines.back().bounds);