From 451b3de69c1421447b033b8f326989d06c716cce Mon Sep 17 00:00:00 2001 From: Lynix Date: Tue, 9 Jul 2019 22:53:06 +0200 Subject: [PATCH] Utility: RichTextDrawer now works (WIP) --- include/Nazara/Utility/RichTextDrawer.hpp | 104 +++-- include/Nazara/Utility/RichTextDrawer.inl | 232 ++++++++++- src/Nazara/Utility/RichTextDrawer.cpp | 476 ++++++++++++++++++++++ 3 files changed, 768 insertions(+), 44 deletions(-) create mode 100644 src/Nazara/Utility/RichTextDrawer.cpp diff --git a/include/Nazara/Utility/RichTextDrawer.hpp b/include/Nazara/Utility/RichTextDrawer.hpp index b3154b740..3a681e20d 100644 --- a/include/Nazara/Utility/RichTextDrawer.hpp +++ b/include/Nazara/Utility/RichTextDrawer.hpp @@ -7,7 +7,7 @@ #ifndef NAZARA_RICHTEXTDRAWER_HPP #define NAZARA_RICHTEXTDRAWER_HPP -#include +#include #include #include #include @@ -24,23 +24,23 @@ namespace Nz RichTextDrawer(); RichTextDrawer(const RichTextDrawer& drawer); RichTextDrawer(RichTextDrawer&& drawer); - virtual ~RichTextDrawer(); + ~RichTextDrawer(); - BlockRef Append(const String& str); + BlockRef AppendText(const String& str); - void Clear(); + inline void Clear(); - unsigned int GetBlockCharacterSize(std::size_t index) const; - const Color& GetBlockColor(std::size_t index) const; - std::size_t GetBlockCount() const; - const FontRef& GetBlockFont(std::size_t index) const; - UInt32 GetBlockStyle(std::size_t index) const; - const String& GetBlockText(std::size_t index) const; + inline unsigned int GetBlockCharacterSize(std::size_t index) const; + inline const Color& GetBlockColor(std::size_t index) const; + inline std::size_t GetBlockCount() const; + inline const FontRef& GetBlockFont(std::size_t index) const; + inline TextStyleFlags GetBlockStyle(std::size_t index) const; + inline const String& GetBlockText(std::size_t index) const; - unsigned int GetDefaultCharacterSize() const; - const Color& GetDefaultColor() const; - const FontRef& GetDefaultFont() const; - UInt32 GetDefaultStyle() const; + inline unsigned int GetDefaultCharacterSize() const; + inline const Color& GetDefaultColor() const; + inline const FontRef& GetDefaultFont() const; + inline TextStyleFlags GetDefaultStyle() const; const Recti& GetBounds() const override; Font* GetFont(std::size_t index) const override; @@ -54,62 +54,82 @@ namespace Nz void RemoveBlock(std::size_t index); - void SetBlockCharacterSize(std::size_t index, unsigned int characterSize); - void SetBlockColor(std::size_t index, const Color& color); - void SetBlockFont(std::size_t index, FontRef font); - void SetBlockStyle(std::size_t index, UInt32 style); - void SetBlockText(std::size_t index, const String& str); + inline void SetBlockCharacterSize(std::size_t index, unsigned int characterSize); + inline void SetBlockColor(std::size_t index, const Color& color); + inline void SetBlockFont(std::size_t index, FontRef font); + inline void SetBlockStyle(std::size_t index, TextStyleFlags style); + inline void SetBlockText(std::size_t index, const String& str); - void SetDefaultCharacterSize(unsigned int characterSize); - void SetDefaultColor(const Color& color); - void SetDefaultFont(FontRef font); - void SetDefaultStyle(UInt32 style); + inline void SetDefaultCharacterSize(unsigned int characterSize); + inline void SetDefaultColor(const Color& color); + inline void SetDefaultFont(const FontRef& font); + inline void SetDefaultStyle(TextStyleFlags style); RichTextDrawer& operator=(const RichTextDrawer& drawer); RichTextDrawer& operator=(RichTextDrawer&& drawer); - static RichTextDrawer Draw(const String& str, unsigned int characterSize, UInt32 style = TextStyle_Regular, const Color& color = Color::White); - static RichTextDrawer Draw(Font* font, const String& str, unsigned int characterSize, UInt32 style = TextStyle_Regular, const Color& color = Color::White); + //static RichTextDrawer Draw(const String& str, unsigned int characterSize, TextStyleFlags style = TextStyle_Regular, const Color& color = Color::White); + //static RichTextDrawer Draw(Font* font, const String& str, unsigned int characterSize, TextStyleFlags style = TextStyle_Regular, const Color& color = Color::White); private: - void ClearGlyphs() const; - void ConnectFontSlots(); - void DisconnectFontSlots(); - void GenerateGlyphs(const String& text) const; + struct Block; + + inline void AppendNewLine(const Font* font, unsigned int characterSize) const; + inline void ClearGlyphs() const; + inline void ConnectFontSlots(); + inline void DisconnectFontSlots(); + bool GenerateGlyph(Glyph& glyph, char32_t character, float outlineThickness, bool lineWrap, const Font* font, const Color& color, TextStyleFlags style, unsigned int characterSize, int renderOrder, int* advance) const; + void GenerateGlyphs(const Font* font, const Color& color, TextStyleFlags style, unsigned int characterSize, const Color& outlineColor, float outlineThickness, const String& text) const; + inline std::size_t HandleFontAddition(const FontRef& font); + inline void ReleaseFont(std::size_t fontIndex); + + inline void InvalidateGlyphs(); + void OnFontAtlasLayerChanged(const Font* font, AbstractImage* oldLayer, AbstractImage* newLayer); void OnFontInvalidated(const Font* font); void OnFontRelease(const Font* object); - void UpdateGlyphs() const; - NazaraSlot(Font, OnFontAtlasChanged, m_atlasChangedSlot); - NazaraSlot(Font, OnFontAtlasLayerChanged, m_atlasLayerChangedSlot); - NazaraSlot(Font, OnFontGlyphCacheCleared, m_glyphCacheClearedSlot); - NazaraSlot(Font, OnFontRelease, m_fontReleaseSlot); + void UpdateGlyphs() const; struct Block { + std::size_t fontIndex; Color color; String text; - UInt32 style; + TextStyleFlags style; unsigned int characterSize; - unsigned int fontIndex; + }; + + struct FontData + { + FontRef font; + std::size_t useCount = 0; + + NazaraSlot(Font, OnFontAtlasChanged, atlasChangedSlot); + NazaraSlot(Font, OnFontAtlasLayerChanged, atlasLayerChangedSlot); + NazaraSlot(Font, OnFontGlyphCacheCleared, glyphCacheClearedSlot); + NazaraSlot(Font, OnFontRelease, fontReleaseSlot); }; Color m_defaultColor; + TextStyleFlags m_defaultStyle; FontRef m_defaultFont; - UInt32 m_defaultStyle; - unsigned int m_defaultCharacterSize; - std::unordered_map m_fonts; + std::unordered_map m_fontIndexes; std::vector m_blocks; + std::vector m_fonts; mutable std::vector m_glyphs; + mutable std::vector m_lines; mutable Rectf m_workingBounds; - mutable Rectui m_bounds; + mutable Recti m_bounds; mutable Vector2ui m_drawPos; mutable bool m_glyphUpdated; + unsigned int m_defaultCharacterSize; }; class RichTextDrawer::BlockRef { + friend RichTextDrawer; + public: BlockRef(const BlockRef&) = default; BlockRef(BlockRef&&) = default; @@ -118,13 +138,13 @@ namespace Nz inline unsigned int GetCharacterSize() const; inline Color GetColor() const; inline const FontRef& GetFont() const; - inline UInt32 GetStyle() const; + inline TextStyleFlags GetStyle() const; inline const String& GetText() const; inline void SetCharacterSize(unsigned int size); inline void SetColor(Color color); inline void SetFont(FontRef font); - inline void SetStyle(UInt32 style); + inline void SetStyle(TextStyleFlags style); inline void SetText(const String& text); BlockRef& operator=(const BlockRef&) = default; diff --git a/include/Nazara/Utility/RichTextDrawer.inl b/include/Nazara/Utility/RichTextDrawer.inl index 5af95ec84..c2591a20b 100644 --- a/include/Nazara/Utility/RichTextDrawer.inl +++ b/include/Nazara/Utility/RichTextDrawer.inl @@ -2,10 +2,238 @@ // This file is part of the "Nazara Engine - Utility module" // For conditions of distribution and use, see copyright notice in Config.hpp +#include #include namespace Nz { + inline void RichTextDrawer::Clear() + { + m_fontIndexes.clear(); + m_blocks.clear(); + m_fonts.clear(); + m_glyphs.clear(); + ClearGlyphs(); + } + + inline unsigned int RichTextDrawer::GetBlockCharacterSize(std::size_t index) const + { + NazaraAssert(index < m_blocks.size(), "Invalid block index"); + return m_blocks[index].characterSize; + } + + inline const Color& RichTextDrawer::GetBlockColor(std::size_t index) const + { + NazaraAssert(index < m_blocks.size(), "Invalid block index"); + return m_blocks[index].color; + } + + inline std::size_t RichTextDrawer::GetBlockCount() const + { + return m_blocks.size(); + } + + inline const FontRef& RichTextDrawer::GetBlockFont(std::size_t index) const + { + NazaraAssert(index < m_blocks.size(), "Invalid block index"); + std::size_t fontIndex = m_blocks[index].fontIndex; + assert(fontIndex < m_fonts.size()); + return m_fonts[fontIndex].font; + } + + inline TextStyleFlags RichTextDrawer::GetBlockStyle(std::size_t index) const + { + NazaraAssert(index < m_blocks.size(), "Invalid block index"); + return m_blocks[index].style; + } + + inline const String& RichTextDrawer::GetBlockText(std::size_t index) const + { + NazaraAssert(index < m_blocks.size(), "Invalid block index"); + return m_blocks[index].text; + } + + inline unsigned int RichTextDrawer::GetDefaultCharacterSize() const + { + return m_defaultCharacterSize; + } + + inline const Color& RichTextDrawer::GetDefaultColor() const + { + return m_defaultColor; + } + + inline const FontRef& RichTextDrawer::GetDefaultFont() const + { + return m_defaultFont; + } + + inline TextStyleFlags RichTextDrawer::GetDefaultStyle() const + { + return m_defaultStyle; + } + + inline void RichTextDrawer::AppendNewLine(const Font* font, unsigned int characterSize) const + { + // Ensure we're appending from last line + Line& lastLine = m_lines.back(); + + const Font::SizeInfo& sizeInfo = font->GetSizeInfo(characterSize); + + unsigned int previousDrawPos = m_drawPos.x; + + // Reset cursor + m_drawPos.x = 0; + m_drawPos.y += sizeInfo.lineHeight; + + 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 }); + } + + inline void RichTextDrawer::ClearGlyphs() const + { + m_bounds.MakeZero(); + m_lines.clear(); + m_glyphs.clear(); + m_glyphUpdated = true; + m_workingBounds.MakeZero(); //< Compute bounds as float to speedup bounds computation (as casting between floats and integers is costly) + } + + inline void RichTextDrawer::ConnectFontSlots() + { + for (auto& fontData : m_fonts) + { + fontData.atlasChangedSlot.Connect(fontData.font->OnFontAtlasChanged, this, &RichTextDrawer::OnFontInvalidated); + fontData.atlasLayerChangedSlot.Connect(fontData.font->OnFontAtlasLayerChanged, this, &RichTextDrawer::OnFontAtlasLayerChanged); + fontData.fontReleaseSlot.Connect(fontData.font->OnFontDestroy, this, &RichTextDrawer::OnFontRelease); + fontData.glyphCacheClearedSlot.Connect(fontData.font->OnFontGlyphCacheCleared, this, &RichTextDrawer::OnFontInvalidated); + } + } + + inline void RichTextDrawer::DisconnectFontSlots() + { + for (auto& fontData : m_fonts) + { + fontData.atlasChangedSlot.Disconnect(); + fontData.atlasLayerChangedSlot.Disconnect(); + fontData.fontReleaseSlot.Disconnect(); + fontData.glyphCacheClearedSlot.Disconnect(); + } + } + + inline std::size_t RichTextDrawer::HandleFontAddition(const FontRef& font) + { + auto it = m_fontIndexes.find(font); + if (it == m_fontIndexes.end()) + { + std::size_t fontIndex = m_fonts.size(); + m_fonts.emplace_back(); + auto& fontData = m_fonts.back(); + fontData.font = font; + fontData.atlasChangedSlot.Connect(font->OnFontAtlasChanged, this, &RichTextDrawer::OnFontInvalidated); + fontData.atlasLayerChangedSlot.Connect(font->OnFontAtlasLayerChanged, this, &RichTextDrawer::OnFontAtlasLayerChanged); + fontData.fontReleaseSlot.Connect(font->OnFontDestroy, this, &RichTextDrawer::OnFontRelease); + fontData.glyphCacheClearedSlot.Connect(font->OnFontGlyphCacheCleared, this, &RichTextDrawer::OnFontInvalidated); + + it = m_fontIndexes.emplace(font, fontIndex).first; + } + + return it->second; + } + + inline void RichTextDrawer::ReleaseFont(std::size_t fontIndex) + { + assert(fontIndex < m_fonts.size()); + + FontData& fontData = m_fonts[fontIndex]; + assert(fontData.useCount > 0); + + if (--fontData.useCount == 0) + { + // Shift font indexes + m_fontIndexes.erase(fontData.font); + for (auto it = m_fontIndexes.begin(); it != m_fontIndexes.end(); ++it) + { + if (it->second > fontIndex) + it->second--; + } + + m_fonts.erase(m_fonts.begin() + fontIndex); + } + } + + inline void RichTextDrawer::SetBlockCharacterSize(std::size_t index, unsigned int characterSize) + { + NazaraAssert(index < m_blocks.size(), "Invalid block index"); + m_blocks[index].characterSize = characterSize; + + InvalidateGlyphs(); + } + + inline void RichTextDrawer::SetBlockColor(std::size_t index, const Color& color) + { + NazaraAssert(index < m_blocks.size(), "Invalid block index"); + m_blocks[index].color = color; + + InvalidateGlyphs(); + } + + inline void RichTextDrawer::SetBlockFont(std::size_t index, FontRef font) + { + NazaraAssert(index < m_blocks.size(), "Invalid block index"); + std::size_t fontIndex = HandleFontAddition(font); + std::size_t oldFontIndex = m_blocks[index].fontIndex; + + if (oldFontIndex != fontIndex) + { + ReleaseFont(oldFontIndex); + + m_fonts[fontIndex].useCount++; + m_blocks[index].fontIndex = fontIndex; + } + } + + inline void RichTextDrawer::SetBlockStyle(std::size_t index, TextStyleFlags style) + { + NazaraAssert(index < m_blocks.size(), "Invalid block index"); + m_blocks[index].style = style; + + InvalidateGlyphs(); + } + + inline void RichTextDrawer::SetBlockText(std::size_t index, const String& str) + { + NazaraAssert(index < m_blocks.size(), "Invalid block index"); + m_blocks[index].text = str; + + InvalidateGlyphs(); + } + + inline void RichTextDrawer::SetDefaultCharacterSize(unsigned int characterSize) + { + m_defaultCharacterSize = characterSize; + } + + inline void RichTextDrawer::SetDefaultColor(const Color& color) + { + m_defaultColor = color; + } + + inline void RichTextDrawer::SetDefaultFont(const FontRef& font) + { + m_defaultFont = font; + } + + inline void RichTextDrawer::SetDefaultStyle(TextStyleFlags style) + { + m_defaultStyle = style; + } + + inline void RichTextDrawer::InvalidateGlyphs() + { + m_glyphUpdated = false; + } + /*! * \class Nz::RichTextDrawer::BlockRef * \brief Helper class representing a block inside a RichTextDrawer, allowing easier access. @@ -58,7 +286,7 @@ namespace Nz * * \see GetCharacterSize, GetColor, GetFont, GetText, SetStyle */ - inline UInt32 RichTextDrawer::BlockRef::GetStyle() const + inline TextStyleFlags RichTextDrawer::BlockRef::GetStyle() const { return m_drawer.GetBlockStyle(m_blockIndex); } @@ -113,7 +341,7 @@ namespace Nz * * \see GetStyle, SetCharacterSize, SetColor, SetFont, SetText */ - inline void RichTextDrawer::BlockRef::SetStyle(UInt32 style) + inline void RichTextDrawer::BlockRef::SetStyle(TextStyleFlags style) { m_drawer.SetBlockStyle(m_blockIndex, style); } diff --git a/src/Nazara/Utility/RichTextDrawer.cpp b/src/Nazara/Utility/RichTextDrawer.cpp new file mode 100644 index 000000000..5480a5c16 --- /dev/null +++ b/src/Nazara/Utility/RichTextDrawer.cpp @@ -0,0 +1,476 @@ +// Copyright (C) 2017 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 +#include +#include +#include + +namespace Nz +{ + RichTextDrawer::RichTextDrawer() : + m_defaultColor(Color::White), + //m_outlineColor(Color::Black), + m_defaultStyle(TextStyle_Regular), + m_glyphUpdated(true), + //m_maxLineWidth(std::numeric_limits::infinity()), + //m_outlineThickness(0.f), + m_defaultCharacterSize(24) + { + SetDefaultFont(Font::GetDefault()); + } + + RichTextDrawer::RichTextDrawer(const RichTextDrawer& drawer) : + m_defaultColor(drawer.m_defaultColor), + m_defaultStyle(drawer.m_defaultStyle), + m_fontIndexes(drawer.m_fontIndexes), + m_blocks(drawer.m_blocks), + m_glyphUpdated(false), + //m_outlineColor(drawer.m_outlineColor), + //m_maxLineWidth(drawer.m_maxLineWidth), + //m_outlineThickness(drawer.m_outlineThickness), + m_defaultCharacterSize(drawer.m_defaultCharacterSize) + { + m_fonts.resize(drawer.m_fonts.size()); + for (std::size_t i = 0; i < m_fonts.size(); ++i) + { + m_fonts[i].font = drawer.m_fonts[i].font; + m_fonts[i].useCount = drawer.m_fonts[i].useCount; + } + + SetDefaultFont(drawer.m_defaultFont); + + ConnectFontSlots(); + } + + RichTextDrawer::RichTextDrawer(RichTextDrawer&& drawer) + { + operator=(std::move(drawer)); + } + + RichTextDrawer::~RichTextDrawer() = default; + + auto RichTextDrawer::AppendText(const String& str) -> BlockRef + { + NazaraAssert(!str.IsEmpty(), "String cannot be empty"); + + std::size_t defaultFontIndex = HandleFontAddition(m_defaultFont); + + auto HasDefaultProperties = [&](const Block& block) + { + return block.characterSize == m_defaultCharacterSize && + block.color == m_defaultColor && + block.fontIndex == defaultFontIndex && + block.style == m_defaultStyle; + }; + + // Check if last block has the same property as default, else create a new block + if (m_blocks.empty() || !HasDefaultProperties(m_blocks.back())) + { + m_blocks.emplace_back(); + Block& newBlock = m_blocks.back(); + newBlock.characterSize = m_defaultCharacterSize; + newBlock.color = m_defaultColor; + newBlock.fontIndex = defaultFontIndex; + newBlock.style = m_defaultStyle; + newBlock.text = str; + + assert(newBlock.fontIndex < m_fonts.size()); + m_fonts[newBlock.fontIndex].useCount++; + } + else + m_blocks.back().text += str; + + InvalidateGlyphs(); + + return BlockRef(*this, m_blocks.size() - 1); + } + + const Recti& RichTextDrawer::GetBounds() const + { + if (!m_glyphUpdated) + UpdateGlyphs(); + + return m_bounds; + } + + Font* RichTextDrawer::GetFont(std::size_t index) const + { + NazaraAssert(index < m_fonts.size(), "Font index out of range"); + + return m_fonts[index].font; + } + + std::size_t RichTextDrawer::GetFontCount() const + { + return m_fonts.size(); + } + + const AbstractTextDrawer::Glyph& RichTextDrawer::GetGlyph(std::size_t index) const + { + if (!m_glyphUpdated) + UpdateGlyphs(); + + return m_glyphs[index]; + } + + std::size_t RichTextDrawer::GetGlyphCount() const + { + if (!m_glyphUpdated) + UpdateGlyphs(); + + return m_glyphs.size(); + } + + const AbstractTextDrawer::Line& RichTextDrawer::GetLine(std::size_t index) const + { + if (!m_glyphUpdated) + UpdateGlyphs(); + + NazaraAssert(index < m_lines.size(), "Line index out of range"); + return m_lines[index]; + } + + std::size_t RichTextDrawer::GetLineCount() const + { + if (!m_glyphUpdated) + UpdateGlyphs(); + + return m_lines.size(); + } + + void RichTextDrawer::MergeBlocks() + { + if (m_blocks.size() < 2) + return; + + auto TestBlockProperties = [](const Block& lhs, const Block& rhs) + { + return lhs.characterSize == rhs.characterSize && + lhs.color == rhs.color && + lhs.fontIndex == rhs.fontIndex && + lhs.style == rhs.style; + }; + + auto lastBlockIt = m_blocks.begin(); + for (auto it = lastBlockIt + 1; it != m_blocks.end();) + { + if (TestBlockProperties(*lastBlockIt, *it)) + { + // Append text to previous block and erase + lastBlockIt->text += it->text; + + ReleaseFont(it->fontIndex); + it = m_blocks.erase(it); + } + else + { + lastBlockIt = it; + ++it; + } + } + } + + void RichTextDrawer::RemoveBlock(std::size_t index) + { + NazaraAssert(index < m_blocks.size(), "Invalid block index"); + + ReleaseFont(m_blocks[index].fontIndex); + m_blocks.erase(m_blocks.begin() + index); + } + + RichTextDrawer& RichTextDrawer::operator=(const RichTextDrawer& drawer) + { + DisconnectFontSlots(); + + m_blocks = drawer.m_blocks; + m_defaultCharacterSize = drawer.m_defaultCharacterSize; + m_defaultColor = drawer.m_defaultColor; + m_defaultFont = drawer.m_defaultFont; + m_defaultStyle = drawer.m_defaultStyle; + m_fontIndexes = drawer.m_fontIndexes; + + m_fonts.resize(drawer.m_fonts.size()); + for (std::size_t i = 0; i < m_fonts.size(); ++i) + { + m_fonts[i].font = drawer.m_fonts[i].font; + m_fonts[i].useCount = drawer.m_fonts[i].useCount; + } + + ConnectFontSlots(); + InvalidateGlyphs(); + + return *this; + } + + RichTextDrawer& RichTextDrawer::operator=(RichTextDrawer&& drawer) + { + m_blocks = std::move(drawer.m_blocks); + m_bounds = std::move(m_bounds); + m_defaultCharacterSize = std::move(drawer.m_defaultCharacterSize); + m_defaultColor = std::move(drawer.m_defaultColor); + m_defaultFont = std::move(drawer.m_defaultFont); + m_defaultStyle = std::move(drawer.m_defaultStyle); + m_drawPos = std::move(m_drawPos); + m_fontIndexes = std::move(drawer.m_fontIndexes); + m_fonts = std::move(drawer.m_fonts); + m_glyphs = std::move(m_glyphs); + m_lines = std::move(m_lines); + m_glyphUpdated = std::move(m_glyphUpdated); + m_workingBounds = std::move(m_workingBounds); + + drawer.DisconnectFontSlots(); + ConnectFontSlots(); + + return *this; + } + + bool RichTextDrawer::GenerateGlyph(Glyph& glyph, char32_t character, float outlineThickness, bool lineWrap, const Font* font, const Color& color, TextStyleFlags style, unsigned int characterSize, int renderOrder, int* advance) const + { + const Font::Glyph& fontGlyph = font->GetGlyph(characterSize, style, outlineThickness, character); + if (fontGlyph.valid && fontGlyph.fauxOutlineThickness <= 0.f) + { + glyph.atlas = 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 RichTextDrawer::GenerateGlyphs(const Font* font, const Color& color, TextStyleFlags style, unsigned int characterSize, const Color& outlineColor, float outlineThickness, 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; + } + + char32_t previousCharacter = 0; + + const Font::SizeInfo& sizeInfo = font->GetSizeInfo(characterSize); + + float heightDifference = sizeInfo.lineHeight - m_lines.back().bounds.height; + if (heightDifference > 0.f) + { + for (std::size_t glyphIndex = m_lines.back().glyphIndex; glyphIndex < m_glyphs.size(); ++glyphIndex) + { + Glyph& glyph = m_glyphs[glyphIndex]; + glyph.bounds.y += heightDifference; + + for (auto& corner : glyph.corners) + corner.y += heightDifference; + } + + m_drawPos.y += heightDifference; + m_lines.back().bounds.height += heightDifference; + } + /*if (firstFont.font) + m_lines.emplace_back(Line{ Rectf(0.f, 0.f, 0.f, float(font->GetSizeInfo(firstBlock.characterSize).lineHeight)), 0 }); + else + m_lines.emplace_back(Line{ Rectf::Zero(), 0 });*/ + + m_glyphs.reserve(m_glyphs.size() + characters.size() * ((outlineThickness > 0.f) ? 2 : 1)); + for (char32_t character : characters) + { + if (previousCharacter != 0) + m_drawPos.x += font->GetKerning(characterSize, previousCharacter, character); + + previousCharacter = character; + + bool whitespace = true; + int advance = 0; + switch (character) + { + case ' ': + case '\n': + advance = sizeInfo.spaceAdvance; + break; + + case '\t': + advance = sizeInfo.spaceAdvance * 4; + break; + + default: + whitespace = false; + break; + } + + Glyph glyph; + if (!whitespace) + { + if (!GenerateGlyph(glyph, character, 0.f, true, font, color, style, characterSize, 0, &advance)) + continue; // Glyph failed to load, just skip it (can't do much) + + if (outlineThickness > 0.f) + { + Glyph outlineGlyph; + if (GenerateGlyph(outlineGlyph, character, outlineThickness, false, font, outlineColor, style, characterSize, -1, nullptr)) + { + m_glyphs.push_back(outlineGlyph); + } + } + } + else + { + float glyphAdvance = advance; + + //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[1].Set(glyph.bounds.GetCorner(RectCorner_RightTop)); + glyph.corners[2].Set(glyph.bounds.GetCorner(RectCorner_LeftBottom)); + glyph.corners[3].Set(glyph.bounds.GetCorner(RectCorner_RightBottom)); + } + + m_lines.back().bounds.ExtendTo(glyph.bounds); + + switch (character) + { + case '\n': + { + AppendNewLine(font, characterSize); + break; + } + + default: + m_drawPos.x += advance; + break; + } + + /*if (whitespace) + { + m_lastSeparatorGlyph = m_glyphs.size(); + m_lastSeparatorPosition = m_drawPos.x; + }*/ + + m_glyphs.push_back(glyph); + } + + m_workingBounds.ExtendTo(m_lines.back().bounds); + + m_bounds.Set(Rectf(std::floor(m_workingBounds.x), std::floor(m_workingBounds.y), std::ceil(m_workingBounds.width), std::ceil(m_workingBounds.height))); + + m_glyphUpdated = true; + } + + void RichTextDrawer::OnFontAtlasLayerChanged(const Font* font, AbstractImage* oldLayer, AbstractImage* newLayer) + { + NazaraUnused(font); + +#ifdef NAZARA_DEBUG + auto it = std::find_if(m_fonts.begin(), m_fonts.end(), [font](const auto& fontData) { return fontData.font == font; }); + if (it == m_fonts.end()) + { + NazaraInternalError("Not listening to " + String::Pointer(font)); + return; + } +#endif + + // Update atlas layer pointer + // Note: This can happen while updating + for (Glyph& glyph : m_glyphs) + { + if (glyph.atlas == oldLayer) + glyph.atlas = newLayer; + } + } + + void RichTextDrawer::OnFontInvalidated(const Font* font) + { + NazaraUnused(font); + +#ifdef NAZARA_DEBUG + auto it = std::find_if(m_fonts.begin(), m_fonts.end(), [font](const auto& fontData) { return fontData.font == font; }); + if (it == m_fonts.end()) + { + NazaraInternalError("Not listening to " + String::Pointer(font)); + return; + } +#endif + + m_glyphUpdated = false; + } + + void RichTextDrawer::OnFontRelease(const Font* font) + { + NazaraUnused(font); + NazaraUnused(font); + +#ifdef NAZARA_DEBUG + auto it = std::find_if(m_fonts.begin(), m_fonts.end(), [font](const auto& fontData) { return fontData.font == font; }); + if (it == m_fonts.end()) + { + NazaraInternalError("Not listening to " + String::Pointer(font)); + return; + } +#endif + + //SetFont(nullptr); + } + + void RichTextDrawer::UpdateGlyphs() const + { + ClearGlyphs(); + + if (!m_blocks.empty()) + { + const Block& firstBlock = m_blocks.front(); + + assert(firstBlock.fontIndex < m_fonts.size()); + const auto& firstFont = m_fonts[firstBlock.fontIndex]; + + if (firstFont.font) + m_lines.emplace_back(Line{ Rectf(0.f, 0.f, 0.f, float(firstFont.font->GetSizeInfo(firstBlock.characterSize).lineHeight)), 0 }); + else + m_lines.emplace_back(Line{ Rectf::Zero(), 0 }); + + m_drawPos.Set(0, firstBlock.characterSize); + + for (const Block& block : m_blocks) + { + assert(block.fontIndex < m_fonts.size()); + const auto& fontData = m_fonts[block.fontIndex]; + + GenerateGlyphs(fontData.font, block.color, block.style, block.characterSize, block.color, 0.f, block.text); + } + } + } +}