diff --git a/include/Nazara/Utility/SimpleTextDrawer.hpp b/include/Nazara/Utility/SimpleTextDrawer.hpp index 68fdc1daa..d2bbd336c 100644 --- a/include/Nazara/Utility/SimpleTextDrawer.hpp +++ b/include/Nazara/Utility/SimpleTextDrawer.hpp @@ -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 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; }; diff --git a/src/Nazara/Utility/SimpleTextDrawer.cpp b/src/Nazara/Utility/SimpleTextDrawer.cpp index 6f7393dd9..62914cd06 100644 --- a/src/Nazara/Utility/SimpleTextDrawer.cpp +++ b/src/Nazara/Utility/SimpleTextDrawer.cpp @@ -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); } }