// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) // 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 std::size_t RichTextDrawer::FindBlock(std::size_t glyphIndex) const { auto it = m_blocks.begin(); for (; it != m_blocks.end(); ++it) { if (it->glyphIndex > glyphIndex) break; } assert(it != m_blocks.begin()); return std::distance(m_blocks.begin(), it) - 1; /* // Binary search std::size_t count = m_blocks.size(); std::size_t step; std::size_t i = InvalidBlockIndex; std::size_t first = 0; std::size_t last = count; while (count > 0) { i = first; step = count / 2; i += step; if (m_blocks[i].glyphIndex < glyphIndex) { first = ++i; count -= step + 1; } else count = step; } return i;*/ } inline auto RichTextDrawer::GetBlock(std::size_t index) -> BlockRef { NazaraAssert(index < m_blocks.size(), "Invalid block index"); return BlockRef(*this, index); } inline unsigned int RichTextDrawer::GetBlockCharacterSize(std::size_t index) const { NazaraAssert(index < m_blocks.size(), "Invalid block index"); return m_blocks[index].characterSize; } inline float RichTextDrawer::GetBlockCharacterSpacingOffset(std::size_t index) const { NazaraAssert(index < m_blocks.size(), "Invalid block index"); return m_blocks[index].characterSpacingOffset; } 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 std::size_t RichTextDrawer::GetBlockFirstGlyphIndex(std::size_t index) const { NazaraAssert(index < m_blocks.size(), "Invalid block index"); return m_blocks[index].glyphIndex; } inline const std::shared_ptr& 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 float RichTextDrawer::GetBlockLineHeight(std::size_t index) const { NazaraAssert(index < m_blocks.size(), "Invalid block index"); return m_blocks[index].lineSpacingOffset; } inline float RichTextDrawer::GetBlockLineSpacingOffset(std::size_t index) const { NazaraAssert(index < m_blocks.size(), "Invalid block index"); return m_blocks[index].lineSpacingOffset; } inline const Color& RichTextDrawer::GetBlockOutlineColor(std::size_t index) const { NazaraAssert(index < m_blocks.size(), "Invalid block index"); return m_blocks[index].outlineColor; } inline float RichTextDrawer::GetBlockOutlineThickness(std::size_t index) const { NazaraAssert(index < m_blocks.size(), "Invalid block index"); return m_blocks[index].outlineThickness; } inline TextStyleFlags RichTextDrawer::GetBlockStyle(std::size_t index) const { NazaraAssert(index < m_blocks.size(), "Invalid block index"); return m_blocks[index].style; } inline const std::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 float RichTextDrawer::GetDefaultCharacterSpacingOffset() const { return m_defaultCharacterSpacingOffset; } inline const Color& RichTextDrawer::GetDefaultColor() const { return m_defaultColor; } inline const std::shared_ptr& RichTextDrawer::GetDefaultFont() const { return m_defaultFont; } inline float RichTextDrawer::GetDefaultLineSpacingOffset() const { return m_defaultLineSpacingOffset; } inline const Color& RichTextDrawer::GetDefaultOutlineColor() const { return m_defaultOutlineColor; } inline float RichTextDrawer::GetDefaultOutlineThickness() const { return m_defaultOutlineThickness; } inline TextStyleFlags RichTextDrawer::GetDefaultStyle() const { return m_defaultStyle; } inline void RichTextDrawer::AppendNewLine(const Font& font, unsigned int characterSize, float lineSpacingOffset) const { AppendNewLine(font, characterSize, lineSpacingOffset, InvalidGlyph, 0); } inline void RichTextDrawer::ClearGlyphs() const { m_bounds.MakeZero(); m_lastSeparatorGlyph = InvalidGlyph; m_lines.clear(); m_glyphs.clear(); m_glyphUpdated = true; m_bounds.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 float RichTextDrawer::GetLineHeight(const Block& block) const { assert(block.fontIndex < m_fonts.size()); const FontData& fontData = m_fonts[block.fontIndex]; return GetLineHeight(block.lineSpacingOffset, fontData.font->GetSizeInfo(block.characterSize)); } inline float RichTextDrawer::GetLineHeight(float lineSpacingOffset, const Font::SizeInfo& sizeInfo) const { return float(sizeInfo.lineHeight) + lineSpacingOffset; } inline std::size_t RichTextDrawer::HandleFontAddition(const std::shared_ptr& 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& fontIndexe : m_fontIndexes) { if (fontIndexe.second > fontIndex) fontIndexe.second--; } m_fonts.erase(m_fonts.begin() + fontIndex); } } inline bool RichTextDrawer::ShouldLineWrap(float size) const { if (m_lines.back().glyphIndex > m_glyphs.size()) return false; return m_lines.back().bounds.GetMaximum().x + size > m_maxLineWidth; } inline bool RichTextDrawer::HasBlocks() const { return !m_blocks.empty(); } 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::SetBlockCharacterSpacingOffset(std::size_t index, float offset) { NazaraAssert(index < m_blocks.size(), "Invalid block index"); m_blocks[index].characterSpacingOffset = offset; 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, std::shared_ptr 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; } InvalidateGlyphs(); } inline void RichTextDrawer::SetBlockLineSpacingOffset(std::size_t index, float offset) { NazaraAssert(index < m_blocks.size(), "Invalid block index"); m_blocks[index].lineSpacingOffset = offset; InvalidateGlyphs(); } inline void RichTextDrawer::SetBlockOutlineColor(std::size_t index, const Color& color) { NazaraAssert(index < m_blocks.size(), "Invalid block index"); m_blocks[index].outlineColor = color; InvalidateGlyphs(); } inline void RichTextDrawer::SetBlockOutlineThickness(std::size_t index, float thickness) { NazaraAssert(index < m_blocks.size(), "Invalid block index"); m_blocks[index].outlineThickness = thickness; InvalidateGlyphs(); } 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, std::string str) { NazaraAssert(index < m_blocks.size(), "Invalid block index"); std::size_t previousLength = m_blocks[index].text.size(); //< FIXME: Count Unicode glyphs m_blocks[index].text = std::move(str); std::size_t newLength = m_blocks[index].text.size(); //< FIXME: Count Unicode glyphs if (newLength != previousLength) { std::size_t delta = newLength - previousLength; //< Underflow allowed for (std::size_t i = index + 1; i < m_blocks.size(); ++i) m_blocks[i].glyphIndex += delta; } InvalidateGlyphs(); } inline void RichTextDrawer::SetDefaultCharacterSize(unsigned int characterSize) { m_defaultCharacterSize = characterSize; } inline void RichTextDrawer::SetDefaultCharacterSpacingOffset(float offset) { m_defaultCharacterSpacingOffset = offset; } inline void RichTextDrawer::SetDefaultColor(const Color& color) { m_defaultColor = color; } inline void RichTextDrawer::SetDefaultFont(const std::shared_ptr& font) { m_defaultFont = font; } inline void RichTextDrawer::SetDefaultLineSpacingOffset(float offset) { m_defaultLineSpacingOffset = offset; } inline void RichTextDrawer::SetDefaultOutlineColor(const Color& color) { m_defaultOutlineColor = color; } inline void RichTextDrawer::SetDefaultOutlineThickness(float thickness) { m_defaultOutlineThickness = thickness; } 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. * * \warning This class is meant for temporary use, moving or destroying the RichTextDrawer or one of its blocks invalidates all BlockRef */ inline RichTextDrawer::BlockRef::BlockRef(RichTextDrawer& drawer, std::size_t index) : m_blockIndex(index), m_drawer(drawer) { } /*! * Returns the character spacing offset used for the characters of the referenced block * \return The referenced block character size * * \see GetColor, GetFont, GetStyle, GetText, SetCharacterSize */ inline float RichTextDrawer::BlockRef::GetCharacterSpacingOffset() const { return m_drawer.GetBlockCharacterSpacingOffset(m_blockIndex); } /*! * Returns the character size used for the characters of the referenced block * \return The referenced block character size * * \see GetColor, GetFont, GetStyle, GetText, SetCharacterSize */ inline unsigned int RichTextDrawer::BlockRef::GetCharacterSize() const { return m_drawer.GetBlockCharacterSize(m_blockIndex); } /*! * Returns the color used for the characters of the referenced block * \return The referenced block color * * \see GetCharacterSize, GetFont, GetStyle, GetText, SetColor */ inline Color RichTextDrawer::BlockRef::GetColor() const { return m_drawer.GetBlockColor(m_blockIndex); } /*! * Returns the font used for the characters of the referenced block * \return A reference on the referenced block font * * \see GetCharacterSize, GetColor, GetStyle, GetText, SetFont */ inline const std::shared_ptr& RichTextDrawer::BlockRef::GetFont() const { return m_drawer.GetBlockFont(m_blockIndex); } /*! * Returns the line spacing offset used for the characters of the referenced block * \return The referenced block character size * * \see GetColor, GetFont, GetStyle, GetText, SetCharacterSize */ inline float RichTextDrawer::BlockRef::GetLineSpacingOffset() const { return m_drawer.GetBlockLineSpacingOffset(m_blockIndex); } /*! * Returns the outline color used for the characters of the referenced block * \return The referenced block outline color * * \see GetCharacterSize, GetColor, GetStyle, GetText, SetFont */ inline Color RichTextDrawer::BlockRef::GetOutlineColor() const { return m_drawer.GetBlockOutlineColor(m_blockIndex); } /*! * Returns the outline thickness used for the characters of the referenced block * \return The referenced block outline thickness * * \see GetCharacterSize, GetColor, GetStyle, GetText, SetFont */ inline float RichTextDrawer::BlockRef::GetOutlineThickness() const { return m_drawer.GetBlockOutlineThickness(m_blockIndex); } /*! * Returns the style flags used for the characters of the referenced block * \return The referenced block style flags (see TextStyleFlags) * * \see GetCharacterSize, GetColor, GetFont, GetText, SetStyle */ inline TextStyleFlags RichTextDrawer::BlockRef::GetStyle() const { return m_drawer.GetBlockStyle(m_blockIndex); } /*! * Returns the first glyph index at which starts the referenced block * \return The first glyph index concerned by this block * * \see GetText */ inline std::size_t RichTextDrawer::BlockRef::GetFirstGlyphIndex() const { return m_drawer.GetBlockFirstGlyphIndex(m_blockIndex); } /*! * Returns the text of the referenced block * \return The referenced block text * * \see GetCharacterSize, GetColor, GetFont, GetStyle, SetText */ inline const std::string& RichTextDrawer::BlockRef::GetText() const { return m_drawer.GetBlockText(m_blockIndex); } /*! * Changes the character spacing offset of the referenced block characters * \remark This invalidates the drawer and will force a (complete or partial, depending on the block index) glyph regeneration to occur. * * \see GetCharacterSpacingOffset, SetColor, SetFont, SetStyle, SetText */ inline void RichTextDrawer::BlockRef::SetCharacterSpacingOffset(float offset) { m_drawer.SetBlockCharacterSpacingOffset(m_blockIndex, offset); } /*! * Changes the character size of the referenced block characters * \remark This invalidates the drawer and will force a (complete or partial, depending on the block index) glyph regeneration to occur. * * \see GetCharacterSize, SetColor, SetFont, SetStyle, SetText */ inline void RichTextDrawer::BlockRef::SetCharacterSize(unsigned int size) { m_drawer.SetBlockCharacterSize(m_blockIndex, size); } /*! * Changes the color of the referenced block characters * \remark This is the only property that can be changed without forcing a glyph regeneration * * \see GetColor, SetCharacterSize, SetFont, SetStyle, SetText */ inline void RichTextDrawer::BlockRef::SetColor(Color color) { m_drawer.SetBlockColor(m_blockIndex, color); } /*! * Changes the font of the referenced block characters * \remark This invalidates the drawer and will force a (complete or partial, depending on the block index) glyph regeneration to occur. * * \see GetCharacterSize, SetCharacterSize, SetColor, SetStyle, SetText */ inline void RichTextDrawer::BlockRef::SetFont(std::shared_ptr font) { m_drawer.SetBlockFont(m_blockIndex, std::move(font)); } /*! * Changes the line spacing offset of the referenced block characters * \remark This invalidates the drawer and will force a (complete or partial, depending on the block index) glyph regeneration to occur. * * \see GetLineSpacingOffset, SetColor, SetFont, SetStyle, SetText */ inline void RichTextDrawer::BlockRef::SetLineSpacingOffset(float offset) { m_drawer.SetBlockLineSpacingOffset(m_blockIndex, offset); } /*! * Changes the outline color of the referenced block characters * \remark This invalidates the drawer and will force a (complete or partial, depending on the block index) glyph regeneration to occur. * * \see GetCharacterSize, SetCharacterSize, SetColor, SetStyle, SetText */ inline void RichTextDrawer::BlockRef::SetOutlineColor(Color color) { m_drawer.SetBlockOutlineColor(m_blockIndex, std::move(color)); } /*! * Changes the outline thickness of the referenced block characters * \remark This invalidates the drawer and will force a (complete or partial, depending on the block index) glyph regeneration to occur. * * \see GetCharacterSize, SetCharacterSize, SetColor, SetStyle, SetText */ inline void RichTextDrawer::BlockRef::SetOutlineThickness(float thickness) { m_drawer.SetBlockOutlineThickness(m_blockIndex, thickness); } /*! * Changes the style flags of the referenced block characters * \remark This invalidates the drawer and will force a (complete or partial, depending on the block index) glyph regeneration to occur. * * \see GetStyle, SetCharacterSize, SetColor, SetFont, SetText */ inline void RichTextDrawer::BlockRef::SetStyle(TextStyleFlags style) { m_drawer.SetBlockStyle(m_blockIndex, style); } /*! * Changes the text of the referenced block * \remark This invalidates the drawer and will force a (complete or partial, depending on the block index) glyph regeneration to occur. * * \see GetText, SetCharacterSize, SetColor, SetFont, SetStyle */ inline void RichTextDrawer::BlockRef::SetText(std::string text) { m_drawer.SetBlockText(m_blockIndex, std::move(text)); } } #include