Core/StringExt: Add IterateOnCodepoints to remove std::u32string allocations
This commit is contained in:
parent
bd53245f42
commit
ad738a2803
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue