Utility/SimpleTextDrawer: Add optimized AppendText method
Former-commit-id: 4608e95d19fe5256d205a0d02b75b1f496f629cc
This commit is contained in:
parent
856df11d5c
commit
235fbf1877
|
|
@ -24,6 +24,8 @@ namespace Nz
|
||||||
SimpleTextDrawer(SimpleTextDrawer&& drawer);
|
SimpleTextDrawer(SimpleTextDrawer&& drawer);
|
||||||
virtual ~SimpleTextDrawer();
|
virtual ~SimpleTextDrawer();
|
||||||
|
|
||||||
|
void AppendText(const String& str);
|
||||||
|
|
||||||
const Rectui& GetBounds() const override;
|
const Rectui& GetBounds() const override;
|
||||||
unsigned int GetCharacterSize() const;
|
unsigned int GetCharacterSize() const;
|
||||||
const Color& GetColor() const;
|
const Color& GetColor() const;
|
||||||
|
|
@ -50,6 +52,7 @@ namespace Nz
|
||||||
private:
|
private:
|
||||||
void ConnectFontSlots();
|
void ConnectFontSlots();
|
||||||
void DisconnectFontSlots();
|
void DisconnectFontSlots();
|
||||||
|
void GenerateGlyphs(const String& text) const;
|
||||||
void OnFontAtlasLayerChanged(const Font* font, AbstractImage* oldLayer, AbstractImage* newLayer);
|
void OnFontAtlasLayerChanged(const Font* font, AbstractImage* oldLayer, AbstractImage* newLayer);
|
||||||
void OnFontInvalidated(const Font* font);
|
void OnFontInvalidated(const Font* font);
|
||||||
void OnFontRelease(const Font* object);
|
void OnFontRelease(const Font* object);
|
||||||
|
|
@ -63,9 +66,12 @@ namespace Nz
|
||||||
mutable std::vector<Glyph> m_glyphs;
|
mutable std::vector<Glyph> m_glyphs;
|
||||||
Color m_color;
|
Color m_color;
|
||||||
FontRef m_font;
|
FontRef m_font;
|
||||||
|
mutable Rectf m_workingBounds;
|
||||||
mutable Rectui m_bounds;
|
mutable Rectui m_bounds;
|
||||||
String m_text;
|
String m_text;
|
||||||
|
mutable UInt32 m_previousCharacter;
|
||||||
UInt32 m_style;
|
UInt32 m_style;
|
||||||
|
mutable Vector2ui m_drawPos;
|
||||||
mutable bool m_glyphUpdated;
|
mutable bool m_glyphUpdated;
|
||||||
unsigned int m_characterSize;
|
unsigned int m_characterSize;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,15 @@ namespace Nz
|
||||||
operator=(std::move(drawer));
|
operator=(std::move(drawer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SimpleTextDrawer::~SimpleTextDrawer() = default;
|
SimpleTextDrawer::~SimpleTextDrawer() = default;
|
||||||
|
|
||||||
|
void SimpleTextDrawer::AppendText(const String& str)
|
||||||
|
{
|
||||||
|
m_text.Append(str);
|
||||||
|
if (m_glyphUpdated)
|
||||||
|
GenerateGlyphs(str);
|
||||||
|
}
|
||||||
|
|
||||||
const Rectui& SimpleTextDrawer::GetBounds() const
|
const Rectui& SimpleTextDrawer::GetBounds() const
|
||||||
{
|
{
|
||||||
if (!m_glyphUpdated)
|
if (!m_glyphUpdated)
|
||||||
|
|
@ -59,13 +65,7 @@ namespace Nz
|
||||||
|
|
||||||
Font* SimpleTextDrawer::GetFont(unsigned int index) const
|
Font* SimpleTextDrawer::GetFont(unsigned int index) const
|
||||||
{
|
{
|
||||||
#if NAZARA_UTILITY_SAFE
|
NazaraAssert(index == 0, "Font index out of range");
|
||||||
if (index > 0)
|
|
||||||
{
|
|
||||||
NazaraError("Font index out of range (" + String::Number(index) + " >= 1)");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return m_font;
|
return m_font;
|
||||||
}
|
}
|
||||||
|
|
@ -215,6 +215,109 @@ namespace Nz
|
||||||
m_glyphCacheClearedSlot.Disconnect();
|
m_glyphCacheClearedSlot.Disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SimpleTextDrawer::GenerateGlyphs(const String& text) const
|
||||||
|
{
|
||||||
|
if (text.IsEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
///TODO: Allow iteration on Unicode characters without allocating any buffer
|
||||||
|
std::u32string characters = text.GetUtf32String();
|
||||||
|
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());
|
||||||
|
for (char32_t character : characters)
|
||||||
|
{
|
||||||
|
if (m_previousCharacter != 0)
|
||||||
|
m_drawPos.x += m_font->GetKerning(m_characterSize, m_previousCharacter, character);
|
||||||
|
|
||||||
|
m_previousCharacter = character;
|
||||||
|
|
||||||
|
bool whitespace = true;
|
||||||
|
switch (character)
|
||||||
|
{
|
||||||
|
case ' ':
|
||||||
|
m_drawPos.x += sizeInfo.spaceAdvance;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\n':
|
||||||
|
m_drawPos.x = 0;
|
||||||
|
m_drawPos.y += sizeInfo.lineHeight;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\t':
|
||||||
|
m_drawPos.x += sizeInfo.spaceAdvance * 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
whitespace = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (whitespace)
|
||||||
|
continue; // White spaces are blanks and invisible, move the draw position and skip the rest
|
||||||
|
|
||||||
|
const Font::Glyph& fontGlyph = m_font->GetGlyph(m_characterSize, m_style, character);
|
||||||
|
if (!fontGlyph.valid)
|
||||||
|
continue; // Glyph failed to load, just skip it (can't do much)
|
||||||
|
|
||||||
|
Glyph glyph;
|
||||||
|
glyph.atlas = m_font->GetAtlas()->GetLayer(fontGlyph.layerIndex);
|
||||||
|
glyph.atlasRect = fontGlyph.atlasRect;
|
||||||
|
glyph.color = m_color;
|
||||||
|
glyph.flipped = fontGlyph.flipped;
|
||||||
|
|
||||||
|
int advance = fontGlyph.advance;
|
||||||
|
|
||||||
|
Rectf bounds(fontGlyph.aabb);
|
||||||
|
bounds.x += m_drawPos.x;
|
||||||
|
bounds.y += m_drawPos.y;
|
||||||
|
|
||||||
|
if (fontGlyph.requireFauxBold)
|
||||||
|
{
|
||||||
|
// Let's simulate bold by enlarging the glyph (not a neat idea, but should work)
|
||||||
|
Vector2f center = bounds.GetCenter();
|
||||||
|
|
||||||
|
// Enlarge by 10%
|
||||||
|
bounds.width *= 1.1f;
|
||||||
|
bounds.height *= 1.1f;
|
||||||
|
|
||||||
|
// Replace it at the correct height
|
||||||
|
Vector2f offset(bounds.GetCenter() - center);
|
||||||
|
bounds.y -= offset.y;
|
||||||
|
|
||||||
|
// Adjust advance (+10%)
|
||||||
|
advance += advance / 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We "lean" the glyph to simulate italics style
|
||||||
|
float italic = (fontGlyph.requireFauxItalic) ? 0.208f : 0.f;
|
||||||
|
float italicTop = italic * bounds.y;
|
||||||
|
float italicBottom = italic * bounds.GetMaximum().y;
|
||||||
|
|
||||||
|
glyph.corners[0].Set(bounds.x - italicTop, bounds.y);
|
||||||
|
glyph.corners[1].Set(bounds.x + bounds.width - italicTop, bounds.y);
|
||||||
|
glyph.corners[2].Set(bounds.x - italicBottom, bounds.y + bounds.height);
|
||||||
|
glyph.corners[3].Set(bounds.x + bounds.width - italicBottom, bounds.y + bounds.height);
|
||||||
|
|
||||||
|
if (!m_workingBounds.IsValid())
|
||||||
|
m_workingBounds.Set(glyph.corners[0]);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < 4; ++i)
|
||||||
|
m_workingBounds.ExtendTo(glyph.corners[i]);
|
||||||
|
|
||||||
|
m_drawPos.x += advance;
|
||||||
|
m_glyphs.push_back(glyph);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_bounds.Set(Rectf(std::floor(m_workingBounds.x), std::floor(m_workingBounds.y), std::ceil(m_workingBounds.width), std::ceil(m_workingBounds.height)));
|
||||||
|
}
|
||||||
|
|
||||||
void SimpleTextDrawer::OnFontAtlasLayerChanged(const Font* font, AbstractImage* oldLayer, AbstractImage* newLayer)
|
void SimpleTextDrawer::OnFontAtlasLayerChanged(const Font* font, AbstractImage* oldLayer, AbstractImage* newLayer)
|
||||||
{
|
{
|
||||||
NazaraUnused(font);
|
NazaraUnused(font);
|
||||||
|
|
@ -228,7 +331,7 @@ namespace Nz
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Update atlas layer pointer
|
// Update atlas layer pointer
|
||||||
// Note: This can happend while updating
|
// Note: This can happen while updating
|
||||||
for (Glyph& glyph : m_glyphs)
|
for (Glyph& glyph : m_glyphs)
|
||||||
{
|
{
|
||||||
if (glyph.atlas == oldLayer)
|
if (glyph.atlas == oldLayer)
|
||||||
|
|
@ -269,126 +372,15 @@ namespace Nz
|
||||||
|
|
||||||
void SimpleTextDrawer::UpdateGlyphs() const
|
void SimpleTextDrawer::UpdateGlyphs() const
|
||||||
{
|
{
|
||||||
|
NazaraAssert(m_font && m_font->IsValid(), "Invalid font");
|
||||||
|
|
||||||
m_bounds.MakeZero();
|
m_bounds.MakeZero();
|
||||||
|
m_drawPos.Set(0, m_characterSize); //< Our draw "cursor"
|
||||||
m_glyphs.clear();
|
m_glyphs.clear();
|
||||||
m_glyphUpdated = true;
|
m_glyphUpdated = true;
|
||||||
|
m_previousCharacter = 0;
|
||||||
|
m_workingBounds.MakeZero(); //< Compute bounds as float to speedup bounds computation (as casting between floats and integers is costly)
|
||||||
|
|
||||||
#if NAZARA_UTILITY_SAFE
|
GenerateGlyphs(m_text);
|
||||||
if (!m_font || !m_font->IsValid())
|
|
||||||
{
|
|
||||||
NazaraError("Invalid font");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (m_text.IsEmpty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
///TODO: Itération UTF-8 => UTF-32 sans allocation de buffer (Exposer utf8cpp ?)
|
|
||||||
std::u32string characters = m_text.GetUtf32String();
|
|
||||||
if (characters.empty())
|
|
||||||
{
|
|
||||||
NazaraError("Invalid character set");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Font::SizeInfo& sizeInfo = m_font->GetSizeInfo(m_characterSize);
|
|
||||||
|
|
||||||
// "Curseur" de dessin
|
|
||||||
Vector2ui drawPos(0, m_characterSize);
|
|
||||||
|
|
||||||
// On calcule les bornes en flottants pour accélérer les calculs (il est coûteux de changer de type trop souvent)
|
|
||||||
bool firstGlyph = true;
|
|
||||||
Rectf textBounds = Rectf::Zero();
|
|
||||||
UInt32 previousCharacter = 0;
|
|
||||||
|
|
||||||
m_glyphs.reserve(characters.size());
|
|
||||||
for (char32_t character : characters)
|
|
||||||
{
|
|
||||||
if (previousCharacter != 0)
|
|
||||||
drawPos.x += m_font->GetKerning(m_characterSize, previousCharacter, character);
|
|
||||||
|
|
||||||
previousCharacter = character;
|
|
||||||
|
|
||||||
bool whitespace = true;
|
|
||||||
switch (character)
|
|
||||||
{
|
|
||||||
case ' ':
|
|
||||||
drawPos.x += sizeInfo.spaceAdvance;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '\n':
|
|
||||||
drawPos.x = 0;
|
|
||||||
drawPos.y += sizeInfo.lineHeight;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '\t':
|
|
||||||
drawPos.x += sizeInfo.spaceAdvance*4;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
whitespace = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (whitespace)
|
|
||||||
continue; // Inutile d'avoir un glyphe pour un espace blanc
|
|
||||||
|
|
||||||
const Font::Glyph& fontGlyph = m_font->GetGlyph(m_characterSize, m_style, character);
|
|
||||||
if (!fontGlyph.valid)
|
|
||||||
continue; // Le glyphe n'a pas été correctement chargé, que pouvons-nous faire d'autre que le passer
|
|
||||||
|
|
||||||
Glyph glyph;
|
|
||||||
glyph.atlas = m_font->GetAtlas()->GetLayer(fontGlyph.layerIndex);
|
|
||||||
glyph.atlasRect = fontGlyph.atlasRect;
|
|
||||||
glyph.color = m_color;
|
|
||||||
glyph.flipped = fontGlyph.flipped;
|
|
||||||
|
|
||||||
int advance = fontGlyph.advance;
|
|
||||||
|
|
||||||
Rectf bounds(fontGlyph.aabb);
|
|
||||||
bounds.x += drawPos.x;
|
|
||||||
bounds.y += drawPos.y;
|
|
||||||
|
|
||||||
if (fontGlyph.requireFauxBold)
|
|
||||||
{
|
|
||||||
// On va agrandir le glyphe pour simuler le gras (idée moisie, mais idée quand même)
|
|
||||||
Vector2f center = bounds.GetCenter();
|
|
||||||
|
|
||||||
bounds.width *= 1.1f;
|
|
||||||
bounds.height *= 1.1f;
|
|
||||||
|
|
||||||
// On le replace à la bonne hauteur
|
|
||||||
Vector2f offset(bounds.GetCenter() - center);
|
|
||||||
bounds.y -= offset.y;
|
|
||||||
|
|
||||||
// On ajuste l'espacement
|
|
||||||
advance += advance/10;
|
|
||||||
}
|
|
||||||
|
|
||||||
// On "penche" le glyphe pour obtenir un semblant d'italique
|
|
||||||
float italic = (fontGlyph.requireFauxItalic) ? 0.208f : 0.f;
|
|
||||||
float italicTop = italic * bounds.y;
|
|
||||||
float italicBottom = italic * bounds.GetMaximum().y;
|
|
||||||
|
|
||||||
glyph.corners[0].Set(bounds.x - italicTop, bounds.y);
|
|
||||||
glyph.corners[1].Set(bounds.x + bounds.width - italicTop, bounds.y);
|
|
||||||
glyph.corners[2].Set(bounds.x - italicBottom, bounds.y + bounds.height);
|
|
||||||
glyph.corners[3].Set(bounds.x + bounds.width - italicBottom, bounds.y + bounds.height);
|
|
||||||
|
|
||||||
if (firstGlyph)
|
|
||||||
{
|
|
||||||
textBounds.Set(glyph.corners[0]);
|
|
||||||
firstGlyph = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (unsigned int i = 0; i < 4; ++i)
|
|
||||||
textBounds.ExtendTo(glyph.corners[i]);
|
|
||||||
|
|
||||||
drawPos.x += advance;
|
|
||||||
m_glyphs.push_back(glyph);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_bounds.Set(Rectf(std::floor(textBounds.x), std::floor(textBounds.y), std::ceil(textBounds.width), std::ceil(textBounds.height)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue