Utility/SimpleTextDrawer: Add optimized AppendText method

Former-commit-id: 4608e95d19fe5256d205a0d02b75b1f496f629cc
This commit is contained in:
Lynix 2016-01-05 13:50:52 +01:00
parent 856df11d5c
commit 235fbf1877
2 changed files with 124 additions and 126 deletions

View File

@ -24,6 +24,8 @@ namespace Nz
SimpleTextDrawer(SimpleTextDrawer&& drawer);
virtual ~SimpleTextDrawer();
void AppendText(const String& str);
const Rectui& GetBounds() const override;
unsigned int GetCharacterSize() const;
const Color& GetColor() const;
@ -50,6 +52,7 @@ namespace Nz
private:
void ConnectFontSlots();
void DisconnectFontSlots();
void GenerateGlyphs(const String& text) const;
void OnFontAtlasLayerChanged(const Font* font, AbstractImage* oldLayer, AbstractImage* newLayer);
void OnFontInvalidated(const Font* font);
void OnFontRelease(const Font* object);
@ -63,9 +66,12 @@ namespace Nz
mutable std::vector<Glyph> m_glyphs;
Color m_color;
FontRef m_font;
mutable Rectf m_workingBounds;
mutable Rectui m_bounds;
String m_text;
mutable UInt32 m_previousCharacter;
UInt32 m_style;
mutable Vector2ui m_drawPos;
mutable bool m_glyphUpdated;
unsigned int m_characterSize;
};

View File

@ -31,9 +31,15 @@ namespace Nz
operator=(std::move(drawer));
}
SimpleTextDrawer::~SimpleTextDrawer() = default;
void SimpleTextDrawer::AppendText(const String& str)
{
m_text.Append(str);
if (m_glyphUpdated)
GenerateGlyphs(str);
}
const Rectui& SimpleTextDrawer::GetBounds() const
{
if (!m_glyphUpdated)
@ -59,13 +65,7 @@ namespace Nz
Font* SimpleTextDrawer::GetFont(unsigned int index) const
{
#if NAZARA_UTILITY_SAFE
if (index > 0)
{
NazaraError("Font index out of range (" + String::Number(index) + " >= 1)");
return nullptr;
}
#endif
NazaraAssert(index == 0, "Font index out of range");
return m_font;
}
@ -215,6 +215,109 @@ namespace Nz
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)
{
NazaraUnused(font);
@ -228,7 +331,7 @@ namespace Nz
#endif
// Update atlas layer pointer
// Note: This can happend while updating
// Note: This can happen while updating
for (Glyph& glyph : m_glyphs)
{
if (glyph.atlas == oldLayer)
@ -269,126 +372,15 @@ namespace Nz
void SimpleTextDrawer::UpdateGlyphs() const
{
NazaraAssert(m_font && m_font->IsValid(), "Invalid font");
m_bounds.MakeZero();
m_drawPos.Set(0, m_characterSize); //< Our draw "cursor"
m_glyphs.clear();
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
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)));
GenerateGlyphs(m_text);
}
}