Core/StringExt: Add IterateOnCodepoints to remove std::u32string allocations

This commit is contained in:
SirLynix 2023-08-24 08:42:25 +02:00
parent bd53245f42
commit ad738a2803
6 changed files with 187 additions and 164 deletions

View File

@ -10,6 +10,7 @@
#include <NazaraUtils/Prerequisites.hpp> #include <NazaraUtils/Prerequisites.hpp>
#include <Nazara/Core/Unicode.hpp> #include <Nazara/Core/Unicode.hpp>
#include <NazaraUtils/Algorithm.hpp> #include <NazaraUtils/Algorithm.hpp>
#include <NazaraUtils/FunctionRef.hpp>
#include <string> #include <string>
namespace Nz namespace Nz
@ -35,6 +36,8 @@ namespace Nz
inline bool IsNumber(std::string_view str); inline bool IsNumber(std::string_view str);
NAZARA_CORE_API void IterateOnCodepoints(std::string_view str, FunctionRef<bool(const char32_t* characters, std::size_t characterCount)> callback);
NAZARA_CORE_API bool MatchPattern(std::string_view str, std::string_view pattern); NAZARA_CORE_API bool MatchPattern(std::string_view str, std::string_view pattern);
inline std::string NumberToString(long long number, UInt8 radix = 10); inline std::string NumberToString(long long number, UInt8 radix = 10);

View File

@ -73,7 +73,7 @@ namespace Nz
bool IsValid() const; 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, 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<AbstractAtlas> atlas); void SetAtlas(std::shared_ptr<AbstractAtlas> atlas);
void SetGlyphBorder(unsigned int borderSize); void SetGlyphBorder(unsigned int borderSize);

View File

@ -259,6 +259,29 @@ namespace Nz
return {}; return {};
} }
void IterateOnCodepoints(std::string_view str, FunctionRef<bool(const char32_t* characters, std::size_t characterCount)> callback)
{
std::array<char32_t, 32> buffer;
std::size_t charCount = 0;
utf8::unchecked::iterator<const char*> it(str.data());
utf8::unchecked::iterator<const char*> 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) bool MatchPattern(std::string_view str, std::string_view pattern)
{ {
if (str.empty() || pattern.empty()) if (str.empty() || pattern.empty())

View File

@ -256,20 +256,20 @@ namespace Nz
return PrecacheGlyph(m_glyphes[key], characterSize, style, outlineThickness, character).valid; 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 ?) NazaraAssert(!characterSet.empty(), "empty character set");
std::u32string set = ToUtf32String(characterSet);
if (set.empty())
{
NazaraError("Invalid character set");
return false;
}
UInt64 key = ComputeKey(characterSize, style, outlineThickness); UInt64 key = ComputeKey(characterSize, style, outlineThickness);
auto& glyphMap = m_glyphes[key]; 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; return true;
} }

View File

@ -381,14 +381,6 @@ namespace Nz
if (text.empty()) if (text.empty())
return; 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; char32_t previousCharacter = 0;
const Font::SizeInfo& sizeInfo = font.GetSizeInfo(characterSize); const Font::SizeInfo& sizeInfo = font.GetSizeInfo(characterSize);
@ -410,85 +402,91 @@ namespace Nz
m_lines.back().bounds.height += heightDifference; m_lines.back().bounds.height += heightDifference;
} }
m_glyphs.reserve(m_glyphs.size() + characters.size() * ((outlineThickness > 0.f) ? 2 : 1)); IterateOnCodepoints(text, [&](const char32_t* characters, std::size_t characterCount)
for (char32_t character : characters)
{ {
if (previousCharacter != 0) for (std::size_t i = 0; i < characterCount; ++i)
m_drawPos.x += font.GetKerning(characterSize, previousCharacter, character);
previousCharacter = character;
bool whitespace = true;
float advance = characterSpacingOffset;
switch (character)
{ {
case ' ': char32_t character = characters[i];
case '\n':
advance += float(sizeInfo.spaceAdvance);
break;
case '\t': if (previousCharacter != 0)
advance += float(sizeInfo.spaceAdvance) * 4.f; m_drawPos.x += font.GetKerning(characterSize, previousCharacter, character);
break;
default: previousCharacter = character;
whitespace = false;
break;
}
Glyph glyph; bool whitespace = true;
if (!whitespace) float advance = characterSpacingOffset;
{ switch (character)
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; case ' ':
if (GenerateGlyph(outlineGlyph, character, outlineThickness, false, font, outlineColor, style, lineSpacingOffset, characterSize, -1, nullptr)) case '\n':
m_glyphs.push_back(outlineGlyph); advance += float(sizeInfo.spaceAdvance);
} break;
}
else
{
if (ShouldLineWrap(advance))
AppendNewLine(font, characterSize, lineSpacingOffset, m_lastSeparatorGlyph, m_lastSeparatorPosition);
glyph.atlas = nullptr; case '\t':
glyph.bounds = Rectf(m_drawPos.x, m_lines.back().bounds.y, advance, lineHeight); advance += float(sizeInfo.spaceAdvance) * 4.f;
break;
glyph.corners[0] = glyph.bounds.GetCorner(RectCorner::LeftTop); default:
glyph.corners[1] = glyph.bounds.GetCorner(RectCorner::RightTop); whitespace = false;
glyph.corners[2] = glyph.bounds.GetCorner(RectCorner::LeftBottom); break;
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: Glyph glyph;
m_drawPos.x += advance; if (!whitespace)
break; {
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) return true; //< continue iteration
{ });
m_lastSeparatorGlyph = m_glyphs.size();
m_lastSeparatorPosition = m_drawPos.x;
}
m_glyphs.push_back(glyph);
}
m_bounds.ExtendTo(m_lines.back().bounds); m_bounds.ExtendTo(m_lines.back().bounds);

View File

@ -187,97 +187,96 @@ namespace Nz
if (text.empty()) if (text.empty())
return; 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); 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) for (std::size_t i = 0; i < characterCount; ++i)
m_drawPos.x += m_font->GetKerning(m_characterSize, m_previousCharacter, character);
m_previousCharacter = character;
bool whitespace = true;
float advance = m_characterSpacingOffset;
switch (character)
{ {
case ' ': char32_t character = characters[i];
case '\n':
advance += float(sizeInfo.spaceAdvance); if (m_previousCharacter != 0)
break; m_drawPos.x += m_font->GetKerning(m_characterSize, m_previousCharacter, character);
case '\t': m_previousCharacter = character;
advance += float(sizeInfo.spaceAdvance) * 4.f;
break;
default: bool whitespace = true;
whitespace = false; float advance = m_characterSpacingOffset;
break; switch (character)
}
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; case ' ':
if (GenerateGlyph(outlineGlyph, character, m_outlineThickness, false, m_outlineColor, glyphRenderOrder - 1, nullptr)) case '\n':
m_glyphs.push_back(outlineGlyph); advance += float(sizeInfo.spaceAdvance);
} break;
}
else
{
if (ShouldLineWrap(advance))
AppendNewLine(m_lastSeparatorGlyph, m_lastSeparatorPosition);
glyph.atlas = nullptr; case '\t':
glyph.bounds = Rectf(m_drawPos.x, m_lines.back().bounds.y, advance, GetLineHeight(sizeInfo)); advance += float(sizeInfo.spaceAdvance) * 4.f;
break;
glyph.corners[0] = glyph.bounds.GetCorner(RectCorner::LeftTop); default:
glyph.corners[1] = glyph.bounds.GetCorner(RectCorner::RightTop); whitespace = false;
glyph.corners[2] = glyph.bounds.GetCorner(RectCorner::LeftBottom); break;
glyph.corners[3] = glyph.bounds.GetCorner(RectCorner::RightBottom);
}
m_lines.back().bounds.ExtendTo(glyph.bounds);
switch (character)
{
case '\n':
{
AppendNewLine();
break;
} }
default: Glyph glyph;
m_drawPos.x += advance; if (!whitespace)
break; {
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) return true; //< continue iteration
{ });
m_lastSeparatorGlyph = m_glyphs.size();
m_lastSeparatorPosition = m_drawPos.x;
}
m_glyphs.push_back(glyph);
}
m_bounds.ExtendTo(m_lines.back().bounds); m_bounds.ExtendTo(m_lines.back().bounds);