Improve line wrap
It will now try to separate at word separations
This commit is contained in:
parent
3617f4054a
commit
55e3e05f0c
|
|
@ -62,21 +62,29 @@ namespace Nz
|
||||||
static SimpleTextDrawer Draw(Font* font, const String& str, unsigned int characterSize, TextStyleFlags style, const Color& color, float outlineThickness, const Color& outlineColor);
|
static SimpleTextDrawer Draw(Font* font, const String& str, unsigned int characterSize, TextStyleFlags style, const Color& color, float outlineThickness, const Color& outlineColor);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void AppendNewLine() const;
|
||||||
|
void AppendNewLine(std::size_t glyphIndex, unsigned int glyphPosition) const;
|
||||||
void ClearGlyphs() const;
|
void ClearGlyphs() const;
|
||||||
void ConnectFontSlots();
|
void ConnectFontSlots();
|
||||||
void DisconnectFontSlots();
|
void DisconnectFontSlots();
|
||||||
|
bool GenerateGlyph(Glyph& glyph, char32_t character, float outlineThickness, bool lineWrap, Nz::Color color, int renderOrder, int* advance) const;
|
||||||
void GenerateGlyphs(const String& text) const;
|
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);
|
||||||
|
bool ShouldLineWrap(Glyph& glyph, float size, bool checkFirstGlyph = true) const;
|
||||||
void UpdateGlyphColor() const;
|
void UpdateGlyphColor() const;
|
||||||
void UpdateGlyphs() const;
|
void UpdateGlyphs() const;
|
||||||
|
|
||||||
|
static constexpr std::size_t InvalidGlyph = std::numeric_limits<std::size_t>::max();
|
||||||
|
|
||||||
NazaraSlot(Font, OnFontAtlasChanged, m_atlasChangedSlot);
|
NazaraSlot(Font, OnFontAtlasChanged, m_atlasChangedSlot);
|
||||||
NazaraSlot(Font, OnFontAtlasLayerChanged, m_atlasLayerChangedSlot);
|
NazaraSlot(Font, OnFontAtlasLayerChanged, m_atlasLayerChangedSlot);
|
||||||
NazaraSlot(Font, OnFontGlyphCacheCleared, m_glyphCacheClearedSlot);
|
NazaraSlot(Font, OnFontGlyphCacheCleared, m_glyphCacheClearedSlot);
|
||||||
NazaraSlot(Font, OnFontRelease, m_fontReleaseSlot);
|
NazaraSlot(Font, OnFontRelease, m_fontReleaseSlot);
|
||||||
|
|
||||||
|
mutable std::size_t m_lastSeparatorGlyph;
|
||||||
|
mutable unsigned int m_lastSeparatorPosition;
|
||||||
mutable std::vector<Glyph> m_glyphs;
|
mutable std::vector<Glyph> m_glyphs;
|
||||||
mutable std::vector<Line> m_lines;
|
mutable std::vector<Line> m_lines;
|
||||||
Color m_color;
|
Color m_color;
|
||||||
|
|
|
||||||
|
|
@ -309,11 +309,66 @@ namespace Nz
|
||||||
return drawer;
|
return drawer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SimpleTextDrawer::AppendNewLine() const
|
||||||
|
{
|
||||||
|
AppendNewLine(InvalidGlyph, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleTextDrawer::AppendNewLine(std::size_t glyphIndex, unsigned int glyphPosition) const
|
||||||
|
{
|
||||||
|
// Ensure we're appending from last line
|
||||||
|
Line& lastLine = m_lines.back();
|
||||||
|
|
||||||
|
const Font::SizeInfo& sizeInfo = m_font->GetSizeInfo(m_characterSize);
|
||||||
|
|
||||||
|
unsigned int previousDrawPos = m_drawPos.x;
|
||||||
|
|
||||||
|
// Reset cursor
|
||||||
|
m_drawPos.x = 0;
|
||||||
|
m_drawPos.y += sizeInfo.lineHeight;
|
||||||
|
m_lastSeparatorGlyph = InvalidGlyph;
|
||||||
|
|
||||||
|
m_workingBounds.ExtendTo(lastLine.bounds);
|
||||||
|
m_lines.emplace_back(Line{ Rectf(0.f, float(sizeInfo.lineHeight * m_lines.size()), 0.f, float(sizeInfo.lineHeight)), m_glyphs.size() + 1 });
|
||||||
|
|
||||||
|
if (glyphIndex != InvalidGlyph && glyphIndex > lastLine.glyphIndex)
|
||||||
|
{
|
||||||
|
Line& newLine = m_lines.back();
|
||||||
|
newLine.glyphIndex = glyphIndex;
|
||||||
|
|
||||||
|
for (std::size_t i = glyphIndex; i < m_glyphs.size(); ++i)
|
||||||
|
{
|
||||||
|
Glyph& glyph = m_glyphs[i];
|
||||||
|
glyph.bounds.x -= glyphPosition;
|
||||||
|
glyph.bounds.y += sizeInfo.lineHeight;
|
||||||
|
|
||||||
|
for (auto& corner : glyph.corners)
|
||||||
|
{
|
||||||
|
corner.x -= glyphPosition;
|
||||||
|
corner.y += sizeInfo.lineHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
newLine.bounds.ExtendTo(glyph.bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(previousDrawPos >= glyphPosition);
|
||||||
|
m_drawPos.x += previousDrawPos - glyphPosition;
|
||||||
|
|
||||||
|
lastLine.bounds.width -= lastLine.bounds.GetMaximum().x - m_lastSeparatorPosition;
|
||||||
|
|
||||||
|
// Regenerate working bounds
|
||||||
|
m_workingBounds.MakeZero();
|
||||||
|
for (std::size_t i = 0; i < m_lines.size(); ++i)
|
||||||
|
m_workingBounds.ExtendTo(m_lines[i].bounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SimpleTextDrawer::ClearGlyphs() const
|
void SimpleTextDrawer::ClearGlyphs() const
|
||||||
{
|
{
|
||||||
m_bounds.MakeZero();
|
m_bounds.MakeZero();
|
||||||
m_colorUpdated = true;
|
m_colorUpdated = true;
|
||||||
m_drawPos.Set(0, m_characterSize); //< Our draw "cursor"
|
m_drawPos.Set(0, m_characterSize); //< Our draw "cursor"
|
||||||
|
m_lastSeparatorGlyph = InvalidGlyph;
|
||||||
m_lines.clear();
|
m_lines.clear();
|
||||||
m_glyphs.clear();
|
m_glyphs.clear();
|
||||||
m_glyphUpdated = true;
|
m_glyphUpdated = true;
|
||||||
|
|
@ -342,6 +397,46 @@ namespace Nz
|
||||||
m_glyphCacheClearedSlot.Disconnect();
|
m_glyphCacheClearedSlot.Disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SimpleTextDrawer::GenerateGlyph(Glyph& glyph, char32_t character, float outlineThickness, bool lineWrap, Nz::Color color, int renderOrder, int* advance) const
|
||||||
|
{
|
||||||
|
const Font::Glyph& fontGlyph = m_font->GetGlyph(m_characterSize, m_style, outlineThickness, character);
|
||||||
|
if (fontGlyph.valid && fontGlyph.fauxOutlineThickness <= 0.f)
|
||||||
|
{
|
||||||
|
glyph.atlas = m_font->GetAtlas()->GetLayer(fontGlyph.layerIndex);
|
||||||
|
glyph.atlasRect = fontGlyph.atlasRect;
|
||||||
|
glyph.color = color;
|
||||||
|
glyph.flipped = fontGlyph.flipped;
|
||||||
|
glyph.renderOrder = renderOrder;
|
||||||
|
|
||||||
|
glyph.bounds.Set(fontGlyph.aabb);
|
||||||
|
|
||||||
|
if (lineWrap && ShouldLineWrap(glyph, glyph.bounds.width))
|
||||||
|
AppendNewLine(m_lastSeparatorGlyph, m_lastSeparatorPosition);
|
||||||
|
|
||||||
|
glyph.bounds.x += m_drawPos.x;
|
||||||
|
glyph.bounds.y += m_drawPos.y;
|
||||||
|
|
||||||
|
// Faux bold and faux outline thickness are not supported
|
||||||
|
|
||||||
|
// We "lean" the glyph to simulate italics style
|
||||||
|
float italic = (fontGlyph.requireFauxItalic) ? 0.208f : 0.f;
|
||||||
|
float italicTop = italic * glyph.bounds.y;
|
||||||
|
float italicBottom = italic * glyph.bounds.GetMaximum().y;
|
||||||
|
|
||||||
|
glyph.corners[0].Set(glyph.bounds.x - italicTop - outlineThickness, glyph.bounds.y - outlineThickness);
|
||||||
|
glyph.corners[1].Set(glyph.bounds.x + glyph.bounds.width - italicTop - outlineThickness, glyph.bounds.y - outlineThickness);
|
||||||
|
glyph.corners[2].Set(glyph.bounds.x - italicBottom - outlineThickness, glyph.bounds.y + glyph.bounds.height - outlineThickness);
|
||||||
|
glyph.corners[3].Set(glyph.bounds.x + glyph.bounds.width - italicBottom - outlineThickness, glyph.bounds.y + glyph.bounds.height - outlineThickness);
|
||||||
|
|
||||||
|
if (advance)
|
||||||
|
*advance = fontGlyph.advance;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
void SimpleTextDrawer::GenerateGlyphs(const String& text) const
|
void SimpleTextDrawer::GenerateGlyphs(const String& text) const
|
||||||
{
|
{
|
||||||
if (text.IsEmpty())
|
if (text.IsEmpty())
|
||||||
|
|
@ -383,78 +478,30 @@ namespace Nz
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto AppendNewLine = [&]()
|
|
||||||
{
|
|
||||||
// Reset cursor
|
|
||||||
//advance = 0;
|
|
||||||
m_drawPos.x = 0;
|
|
||||||
m_drawPos.y += sizeInfo.lineHeight;
|
|
||||||
|
|
||||||
m_workingBounds.ExtendTo(m_lines.back().bounds);
|
|
||||||
m_lines.emplace_back(Line{ Rectf(0.f, float(sizeInfo.lineHeight * m_lines.size()), 0.f, float(sizeInfo.lineHeight)), m_glyphs.size() + 1 });
|
|
||||||
};
|
|
||||||
|
|
||||||
auto GenerateGlyph = [&](Glyph& glyph, char32_t character, float outlineThickness, Nz::Color color, int renderOrder, int* advance)
|
|
||||||
{
|
|
||||||
const Font::Glyph& fontGlyph = m_font->GetGlyph(m_characterSize, m_style, outlineThickness, character);
|
|
||||||
if (fontGlyph.valid && fontGlyph.fauxOutlineThickness <= 0.f)
|
|
||||||
{
|
|
||||||
glyph.atlas = m_font->GetAtlas()->GetLayer(fontGlyph.layerIndex);
|
|
||||||
glyph.atlasRect = fontGlyph.atlasRect;
|
|
||||||
glyph.color = color;
|
|
||||||
glyph.flipped = fontGlyph.flipped;
|
|
||||||
glyph.renderOrder = renderOrder;
|
|
||||||
|
|
||||||
glyph.bounds.Set(fontGlyph.aabb);
|
|
||||||
|
|
||||||
if (m_lines.back().glyphIndex <= m_glyphs.size() && m_lines.back().bounds.GetMaximum().x + glyph.bounds.width > m_maxLineWidth)
|
|
||||||
AppendNewLine();
|
|
||||||
|
|
||||||
glyph.bounds.x += m_drawPos.x;
|
|
||||||
glyph.bounds.y += m_drawPos.y;
|
|
||||||
|
|
||||||
// Faux bold and faux outline thickness are not supported
|
|
||||||
|
|
||||||
// We "lean" the glyph to simulate italics style
|
|
||||||
float italic = (fontGlyph.requireFauxItalic) ? 0.208f : 0.f;
|
|
||||||
float italicTop = italic * glyph.bounds.y;
|
|
||||||
float italicBottom = italic * glyph.bounds.GetMaximum().y;
|
|
||||||
|
|
||||||
glyph.corners[0].Set(glyph.bounds.x - italicTop - outlineThickness, glyph.bounds.y - outlineThickness);
|
|
||||||
glyph.corners[1].Set(glyph.bounds.x + glyph.bounds.width - italicTop - outlineThickness, glyph.bounds.y - outlineThickness);
|
|
||||||
glyph.corners[2].Set(glyph.bounds.x - italicBottom - outlineThickness, glyph.bounds.y + glyph.bounds.height - outlineThickness);
|
|
||||||
glyph.corners[3].Set(glyph.bounds.x + glyph.bounds.width - italicBottom - outlineThickness, glyph.bounds.y + glyph.bounds.height - outlineThickness);
|
|
||||||
|
|
||||||
if (advance)
|
|
||||||
*advance = fontGlyph.advance;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
Glyph glyph;
|
Glyph glyph;
|
||||||
if (!whitespace)
|
if (!whitespace)
|
||||||
{
|
{
|
||||||
if (!GenerateGlyph(glyph, character, 0.f, m_color, 0, &advance))
|
if (!GenerateGlyph(glyph, character, 0.f, true, m_color, 0, &advance))
|
||||||
continue; // Glyph failed to load, just skip it (can't do much)
|
continue; // Glyph failed to load, just skip it (can't do much)
|
||||||
|
|
||||||
if (m_outlineThickness > 0.f)
|
if (m_outlineThickness > 0.f)
|
||||||
{
|
{
|
||||||
Glyph outlineGlyph;
|
Glyph outlineGlyph;
|
||||||
if (GenerateGlyph(outlineGlyph, character, m_outlineThickness, m_outlineColor, -1, nullptr))
|
if (GenerateGlyph(outlineGlyph, character, m_outlineThickness, false, m_outlineColor, -1, nullptr))
|
||||||
{
|
{
|
||||||
m_lines.back().bounds.ExtendTo(outlineGlyph.bounds);
|
|
||||||
m_glyphs.push_back(outlineGlyph);
|
m_glyphs.push_back(outlineGlyph);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
glyph.atlas = nullptr;
|
float glyphAdvance = advance;
|
||||||
|
|
||||||
glyph.bounds.Set(float(m_drawPos.x), m_lines.back().bounds.y, float(advance), float(sizeInfo.lineHeight));
|
if (ShouldLineWrap(glyph, glyphAdvance))
|
||||||
|
AppendNewLine(m_lastSeparatorGlyph, m_lastSeparatorPosition);
|
||||||
|
|
||||||
|
glyph.atlas = nullptr;
|
||||||
|
glyph.bounds.Set(float(m_drawPos.x), m_lines.back().bounds.y, glyphAdvance, float(sizeInfo.lineHeight));
|
||||||
|
|
||||||
glyph.corners[0].Set(glyph.bounds.GetCorner(RectCorner_LeftTop));
|
glyph.corners[0].Set(glyph.bounds.GetCorner(RectCorner_LeftTop));
|
||||||
glyph.corners[1].Set(glyph.bounds.GetCorner(RectCorner_RightTop));
|
glyph.corners[1].Set(glyph.bounds.GetCorner(RectCorner_RightTop));
|
||||||
|
|
@ -477,6 +524,12 @@ namespace Nz
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (whitespace)
|
||||||
|
{
|
||||||
|
m_lastSeparatorGlyph = m_glyphs.size();
|
||||||
|
m_lastSeparatorPosition = m_drawPos.x;
|
||||||
|
}
|
||||||
|
|
||||||
m_glyphs.push_back(glyph);
|
m_glyphs.push_back(glyph);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -540,6 +593,14 @@ namespace Nz
|
||||||
SetFont(nullptr);
|
SetFont(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SimpleTextDrawer::ShouldLineWrap(Glyph& glyph, float size, bool checkFirstGlyph) const
|
||||||
|
{
|
||||||
|
if (checkFirstGlyph && m_lines.back().glyphIndex > m_glyphs.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return m_lines.back().bounds.GetMaximum().x + size > m_maxLineWidth;
|
||||||
|
}
|
||||||
|
|
||||||
void SimpleTextDrawer::UpdateGlyphColor() const
|
void SimpleTextDrawer::UpdateGlyphColor() const
|
||||||
{
|
{
|
||||||
if (m_outlineThickness > 0.f)
|
if (m_outlineThickness > 0.f)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue