NazaraEngine/src/Nazara/Utility/SimpleTextDrawer.cpp

301 lines
6.6 KiB
C++

// Copyright (C) 2015 Jérôme Leclercq
// This file is part of the "Nazara Engine - Utility module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Utility/SimpleTextDrawer.hpp>
#include <memory>
#include <Nazara/Utility/Debug.hpp>
NzSimpleTextDrawer::NzSimpleTextDrawer() :
m_color(NzColor::White),
m_fontListener(this),
m_style(nzTextStyle_Regular)
{
SetFont(NzFont::GetDefault());
}
const NzRectui& NzSimpleTextDrawer::GetBounds() const
{
if (!m_glyphUpdated)
UpdateGlyphs();
return m_bounds;
}
unsigned int NzSimpleTextDrawer::GetCharacterSize() const
{
return m_characterSize;
}
const NzColor& NzSimpleTextDrawer::GetColor() const
{
return m_color;
}
NzFont* NzSimpleTextDrawer::GetFont() const
{
return m_font;
}
nzUInt32 NzSimpleTextDrawer::GetStyle() const
{
return m_style;
}
void NzSimpleTextDrawer::SetCharacterSize(unsigned int characterSize)
{
m_characterSize = characterSize;
m_glyphUpdated = false;
}
void NzSimpleTextDrawer::SetColor(const NzColor& color)
{
m_color = color;
m_glyphUpdated = false;
}
void NzSimpleTextDrawer::SetFont(NzFont* font)
{
m_font = font;
m_fontListener = font;
m_glyphUpdated = false;
}
void NzSimpleTextDrawer::SetStyle(nzUInt32 style)
{
m_style = style;
m_glyphUpdated = false;
}
void NzSimpleTextDrawer::SetText(const NzString& str)
{
m_text = str;
m_glyphUpdated = false;
}
NzSimpleTextDrawer NzSimpleTextDrawer::Draw(const NzString& str, unsigned int characterSize, nzUInt32 style, const NzColor& color)
{
NzSimpleTextDrawer drawer;
drawer.SetCharacterSize(characterSize);
drawer.SetColor(color);
drawer.SetStyle(style);
drawer.SetText(str);
return drawer;
}
NzSimpleTextDrawer NzSimpleTextDrawer::Draw(NzFont* font, const NzString& str, unsigned int characterSize, nzUInt32 style, const NzColor& color)
{
NzSimpleTextDrawer drawer;
drawer.SetCharacterSize(characterSize);
drawer.SetColor(color);
drawer.SetFont(font);
drawer.SetStyle(style);
drawer.SetText(str);
return drawer;
}
NzFont* NzSimpleTextDrawer::GetFont(unsigned int index) const
{
#if NAZARA_UTILITY_SAFE
if (index > 0)
{
NazaraError("Font index out of range (" + NzString::Number(index) + " >= 1)");
return nullptr;
}
#endif
return m_font;
}
unsigned int NzSimpleTextDrawer::GetFontCount() const
{
return 1;
}
const NzAbstractTextDrawer::Glyph& NzSimpleTextDrawer::GetGlyph(unsigned int index) const
{
if (!m_glyphUpdated)
UpdateGlyphs();
return m_glyphs[index];
}
unsigned int NzSimpleTextDrawer::GetGlyphCount() const
{
if (!m_glyphUpdated)
UpdateGlyphs();
return m_glyphs.size();
}
bool NzSimpleTextDrawer::OnObjectModified(const NzRefCounted* object, int index, unsigned int code)
{
NazaraUnused(object);
NazaraUnused(index);
#ifdef NAZARA_DEBUG
if (m_font != object)
{
NazaraInternalError("Not listening to " + NzString::Pointer(object));
return false;
}
#endif
if (code == NzFont::ModificationCode_AtlasChanged ||
code == NzFont::ModificationCode_AtlasLayerChanged ||
code == NzFont::ModificationCode_GlyphCacheCleared)
{
m_glyphUpdated = false;
}
return true;
}
void NzSimpleTextDrawer::OnObjectReleased(const NzRefCounted* object, int index)
{
NazaraUnused(object);
NazaraUnused(index);
#ifdef NAZARA_DEBUG
if (m_font != object)
{
NazaraInternalError("Not listening to " + NzString::Pointer(object));
return;
}
#endif
SetFont(nullptr);
}
void NzSimpleTextDrawer::UpdateGlyphs() const
{
m_bounds.MakeZero();
m_glyphs.clear();
m_glyphUpdated = true;
#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 NzFont::SizeInfo& sizeInfo = m_font->GetSizeInfo(m_characterSize);
// "Curseur" de dessin
NzVector2ui 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;
NzRectf textBounds = NzRectf::Zero();
nzUInt32 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 NzFont::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;
NzRectf 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)
NzVector2f center = bounds.GetCenter();
bounds.width *= 1.1f;
bounds.height *= 1.1f;
// On le replace à la bonne hauteur
NzVector2f 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(NzRectf(std::floor(textBounds.x), std::floor(textBounds.y), std::ceil(textBounds.width), std::ceil(textBounds.height)));
}