summaryrefslogtreecommitdiffstats
path: root/xbmc/guilib/GUITextLayout.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/guilib/GUITextLayout.cpp')
-rw-r--r--xbmc/guilib/GUITextLayout.cpp736
1 files changed, 736 insertions, 0 deletions
diff --git a/xbmc/guilib/GUITextLayout.cpp b/xbmc/guilib/GUITextLayout.cpp
new file mode 100644
index 0000000..5058853
--- /dev/null
+++ b/xbmc/guilib/GUITextLayout.cpp
@@ -0,0 +1,736 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUITextLayout.h"
+
+#include "GUIColorManager.h"
+#include "GUIComponent.h"
+#include "GUIControl.h"
+#include "GUIFont.h"
+#include "utils/CharsetConverter.h"
+#include "utils/StringUtils.h"
+
+CGUIString::CGUIString(iString start, iString end, bool carriageReturn)
+{
+ m_text.assign(start, end);
+ m_carriageReturn = carriageReturn;
+}
+
+std::string CGUIString::GetAsString() const
+{
+ std::string text;
+ for (unsigned int i = 0; i < m_text.size(); i++)
+ text += (char)(m_text[i] & 0xff);
+ return text;
+}
+
+CGUITextLayout::CGUITextLayout(CGUIFont *font, bool wrap, float fHeight, CGUIFont *borderFont)
+{
+ m_varFont = m_font = font;
+ m_borderFont = borderFont;
+ m_textColor = 0;
+ m_wrap = wrap;
+ m_maxHeight = fHeight;
+ m_textWidth = 0;
+ m_textHeight = 0;
+ m_lastUpdateW = false;
+}
+
+void CGUITextLayout::SetWrap(bool bWrap)
+{
+ m_wrap = bWrap;
+}
+
+void CGUITextLayout::Render(float x,
+ float y,
+ float angle,
+ UTILS::COLOR::Color color,
+ UTILS::COLOR::Color shadowColor,
+ uint32_t alignment,
+ float maxWidth,
+ bool solid)
+{
+ if (!m_font)
+ return;
+
+ // set the main text color
+ if (m_colors.size())
+ m_colors[0] = color;
+
+ // render the text at the required location, angle, and size
+ if (angle)
+ {
+ static const float degrees_to_radians = 0.01745329252f;
+ CServiceBroker::GetWinSystem()->GetGfxContext().AddTransform(TransformMatrix::CreateZRotation(angle * degrees_to_radians, x, y, CServiceBroker::GetWinSystem()->GetGfxContext().GetScalingPixelRatio()));
+ }
+ // center our text vertically
+ if (alignment & XBFONT_CENTER_Y)
+ {
+ y -= m_font->GetTextHeight(m_lines.size()) * 0.5f;
+ alignment &= ~XBFONT_CENTER_Y;
+ }
+ m_font->Begin();
+ for (const auto& string : m_lines)
+ {
+ uint32_t align = alignment;
+ if (align & XBFONT_JUSTIFIED && string.m_carriageReturn)
+ align &= ~XBFONT_JUSTIFIED;
+ if (solid)
+ m_font->DrawText(x, y, m_colors[0], shadowColor, string.m_text, align, maxWidth);
+ else
+ m_font->DrawText(x, y, m_colors, shadowColor, string.m_text, align, maxWidth);
+ y += m_font->GetLineHeight();
+ }
+ m_font->End();
+ if (angle)
+ CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
+}
+
+bool CGUITextLayout::UpdateScrollinfo(CScrollInfo &scrollInfo)
+{
+ if (!m_font)
+ return false;
+ if (m_lines.empty())
+ return false;
+
+ return m_font->UpdateScrollInfo(m_lines[0].m_text, scrollInfo);
+}
+
+
+void CGUITextLayout::RenderScrolling(float x,
+ float y,
+ float angle,
+ UTILS::COLOR::Color color,
+ UTILS::COLOR::Color shadowColor,
+ uint32_t alignment,
+ float maxWidth,
+ const CScrollInfo& scrollInfo)
+{
+ if (!m_font)
+ return;
+
+ // set the main text color
+ if (m_colors.size())
+ m_colors[0] = color;
+
+ // render the text at the required location, angle, and size
+ if (angle)
+ {
+ static const float degrees_to_radians = 0.01745329252f;
+ CServiceBroker::GetWinSystem()->GetGfxContext().AddTransform(TransformMatrix::CreateZRotation(angle * degrees_to_radians, x, y, CServiceBroker::GetWinSystem()->GetGfxContext().GetScalingPixelRatio()));
+ }
+ // center our text vertically
+ if (alignment & XBFONT_CENTER_Y)
+ {
+ y -= m_font->GetTextHeight(m_lines.size()) * 0.5f;
+ alignment &= ~XBFONT_CENTER_Y;
+ }
+ m_font->Begin();
+ // NOTE: This workaround is needed as otherwise multi-line text that scrolls
+ // will scroll in proportion to the number of lines. Ideally we should
+ // do the DrawScrollingText calculation here. This probably won't make
+ // any difference to the smoothness of scrolling though which will be
+ // jumpy with this sort of thing. It's not exactly a well used situation
+ // though, so this hack is probably OK.
+ for (const auto& string : m_lines)
+ {
+ m_font->DrawScrollingText(x, y, m_colors, shadowColor, string.m_text, alignment, maxWidth, scrollInfo);
+ y += m_font->GetLineHeight();
+ }
+ m_font->End();
+ if (angle)
+ CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
+}
+
+void CGUITextLayout::RenderOutline(float x,
+ float y,
+ UTILS::COLOR::Color color,
+ UTILS::COLOR::Color outlineColor,
+ uint32_t alignment,
+ float maxWidth)
+{
+ if (!m_font)
+ return;
+
+ // set the outline color
+ std::vector<UTILS::COLOR::Color> outlineColors;
+ if (m_colors.size())
+ outlineColors.push_back(outlineColor);
+
+ // center our text vertically
+ if (alignment & XBFONT_CENTER_Y)
+ {
+ y -= m_font->GetTextHeight(m_lines.size()) * 0.5f;
+ alignment &= ~XBFONT_CENTER_Y;
+ }
+ if (m_borderFont)
+ {
+ // adjust so the baselines of the fonts align
+ float by = y + m_font->GetTextBaseLine() - m_borderFont->GetTextBaseLine();
+ m_borderFont->Begin();
+ for (const auto& string : m_lines)
+ {
+ uint32_t align = alignment;
+ if (align & XBFONT_JUSTIFIED && string.m_carriageReturn)
+ align &= ~XBFONT_JUSTIFIED;
+ // text centered horizontally must be computed using the original font, not the bordered
+ // font, as the bordered font will be wider, and thus will end up uncentered.
+ //! @todo We should really have a better way to handle text extent - at the moment we assume
+ //! that text is rendered from a posx, posy, width, and height which isn't enough to
+ //! accurately position text. We need a vertical and horizontal offset of the baseline
+ //! and cursor as well.
+ float bx = x;
+ if (align & XBFONT_CENTER_X)
+ {
+ bx -= m_font->GetTextWidth(string.m_text) * 0.5f;
+ align &= ~XBFONT_CENTER_X;
+ }
+
+ // don't pass maxWidth through to the renderer for the same reason above: it will cause clipping
+ // on the left.
+ m_borderFont->DrawText(bx, by, outlineColors, 0, string.m_text, align, 0);
+ by += m_borderFont->GetLineHeight();
+ }
+ m_borderFont->End();
+ }
+
+ // set the main text color
+ if (m_colors.size())
+ m_colors[0] = color;
+
+ m_font->Begin();
+ for (const auto& string : m_lines)
+ {
+ uint32_t align = alignment;
+ if (align & XBFONT_JUSTIFIED && string.m_carriageReturn)
+ align &= ~XBFONT_JUSTIFIED;
+
+ // don't pass maxWidth through to the renderer for the reason above.
+ m_font->DrawText(x, y, m_colors, 0, string.m_text, align, 0);
+ y += m_font->GetLineHeight();
+ }
+ m_font->End();
+}
+
+bool CGUITextLayout::Update(const std::string &text, float maxWidth, bool forceUpdate /*= false*/, bool forceLTRReadingOrder /*= false*/)
+{
+ if (text == m_lastUtf8Text && !forceUpdate && !m_lastUpdateW)
+ return false;
+
+ m_lastUtf8Text = text;
+ m_lastUpdateW = false;
+ std::wstring utf16;
+ g_charsetConverter.utf8ToW(text, utf16, false);
+ UpdateCommon(utf16, maxWidth, forceLTRReadingOrder);
+ return true;
+}
+
+bool CGUITextLayout::UpdateW(const std::wstring &text, float maxWidth /*= 0*/, bool forceUpdate /*= false*/, bool forceLTRReadingOrder /*= false*/)
+{
+ if (text == m_lastText && !forceUpdate && m_lastUpdateW)
+ return false;
+
+ m_lastText = text;
+ m_lastUpdateW = true;
+ UpdateCommon(text, maxWidth, forceLTRReadingOrder);
+ return true;
+}
+
+void CGUITextLayout::UpdateCommon(const std::wstring &text, float maxWidth, bool forceLTRReadingOrder)
+{
+ // parse the text for style information
+ vecText parsedText;
+ std::vector<UTILS::COLOR::Color> colors;
+ ParseText(text, m_font ? m_font->GetStyle() : 0, m_textColor, colors, parsedText);
+
+ // and update
+ UpdateStyled(parsedText, colors, maxWidth, forceLTRReadingOrder);
+}
+
+void CGUITextLayout::UpdateStyled(const vecText& text,
+ const std::vector<UTILS::COLOR::Color>& colors,
+ float maxWidth,
+ bool forceLTRReadingOrder)
+{
+ // empty out our previous string
+ m_lines.clear();
+ m_colors = colors;
+
+ // if we need to wrap the text, then do so
+ if (m_wrap && maxWidth > 0)
+ WrapText(text, maxWidth);
+ else
+ LineBreakText(text, m_lines);
+
+ // remove any trailing blank lines
+ while (!m_lines.empty() && m_lines.back().m_text.empty())
+ m_lines.pop_back();
+
+ BidiTransform(m_lines, forceLTRReadingOrder);
+
+ // and cache the width and height for later reading
+ CalcTextExtent();
+}
+
+// BidiTransform is used to handle RTL text flipping in the string
+void CGUITextLayout::BidiTransform(std::vector<CGUIString>& lines, bool forceLTRReadingOrder)
+{
+ for (unsigned int i = 0; i < lines.size(); i++)
+ {
+ CGUIString& line = lines[i];
+ unsigned int lineLength = line.m_text.size();
+ std::wstring logicalText;
+ vecText style;
+
+ logicalText.reserve(lineLength);
+ style.reserve(lineLength);
+
+ // Separate the text and style for the input styled text
+ for (const auto& it : line.m_text)
+ {
+ logicalText.push_back((wchar_t)(it & 0xffff));
+ style.push_back(it & 0xffff0000);
+ }
+
+ // Allocate memory for visual to logical map and call bidi
+ int* visualToLogicalMap = new (std::nothrow) int[lineLength + 1]();
+ std::wstring visualText = BidiFlip(logicalText, forceLTRReadingOrder, visualToLogicalMap);
+
+ vecText styledVisualText;
+ styledVisualText.reserve(lineLength);
+
+ // If memory allocation failed, fallback to text with no styling
+ if (!visualToLogicalMap)
+ {
+ for (unsigned int j = 0; j < visualText.size(); j++)
+ {
+ styledVisualText.push_back(visualText[j]);
+ }
+ }
+ else
+ {
+ for (unsigned int j = 0; j < visualText.size(); j++)
+ {
+ styledVisualText.push_back(style[visualToLogicalMap[j]] | visualText[j]);
+ }
+ }
+
+ delete[] visualToLogicalMap;
+
+ // replace the original line with the processed one
+ lines[i] = CGUIString(styledVisualText.begin(), styledVisualText.end(), line.m_carriageReturn);
+ }
+}
+
+std::wstring CGUITextLayout::BidiFlip(const std::wstring& text,
+ bool forceLTRReadingOrder,
+ int* visualToLogicalMap /*= nullptr*/)
+{
+ std::wstring visualText;
+ std::u32string utf32logical;
+ std::u32string utf32visual;
+
+ // Convert to utf32, call bidi then convert the result back to utf16
+ g_charsetConverter.wToUtf32(text, utf32logical);
+ g_charsetConverter.utf32logicalToVisualBiDi(utf32logical, utf32visual, forceLTRReadingOrder,
+ false,
+ visualToLogicalMap);
+ g_charsetConverter.utf32ToW(utf32visual, visualText);
+
+ return visualText;
+}
+
+void CGUITextLayout::Filter(std::string &text)
+{
+ std::wstring utf16;
+ g_charsetConverter.utf8ToW(text, utf16, false);
+ std::vector<UTILS::COLOR::Color> colors;
+ vecText parsedText;
+ ParseText(utf16, 0, 0xffffffff, colors, parsedText);
+ utf16.clear();
+ for (unsigned int i = 0; i < parsedText.size(); i++)
+ utf16 += (wchar_t)(0xffff & parsedText[i]);
+ g_charsetConverter.wToUTF8(utf16, text);
+}
+
+void CGUITextLayout::ParseText(const std::wstring& text,
+ uint32_t defaultStyle,
+ UTILS::COLOR::Color defaultColor,
+ std::vector<UTILS::COLOR::Color>& colors,
+ vecText& parsedText)
+{
+ // run through the string, searching for:
+ // [B] or [/B] -> toggle bold on and off
+ // [I] or [/I] -> toggle italics on and off
+ // [COLOR ffab007f] or [/COLOR] -> toggle color on and off
+ // [CAPS <option>] or [/CAPS] -> toggle capatilization on and off
+ // [TABS] tab amount [/TABS] -> add tabulator space in view
+
+ uint32_t currentStyle = defaultStyle; // start with the default font's style
+ UTILS::COLOR::Color currentColor = 0;
+
+ colors.push_back(defaultColor);
+ std::stack<UTILS::COLOR::Color> colorStack;
+ colorStack.push(0);
+
+ // these aren't independent, but that's probably not too much of an issue
+ // eg [UPPERCASE]Glah[LOWERCASE]FReD[/LOWERCASE]Georeg[/UPPERCASE] will work (lower case >> upper case)
+ // but [LOWERCASE]Glah[UPPERCASE]FReD[/UPPERCASE]Georeg[/LOWERCASE] won't
+
+ int startPos = 0;
+ size_t pos = text.find(L'[');
+ while (pos != std::string::npos && pos + 1 < text.size())
+ {
+ uint32_t newStyle = 0;
+ UTILS::COLOR::Color newColor = currentColor;
+ bool colorTagChange = false;
+ bool newLine = false;
+ int tabs = 0;
+ // have a [ - check if it's an ON or OFF switch
+ bool on(true);
+ size_t endPos = pos++; // finish of string
+ if (text[pos] == L'/')
+ {
+ on = false;
+ pos++;
+ }
+ // check for each type
+ if (text.compare(pos, 2, L"B]") == 0)
+ { // bold - finish the current text block and assign the bold state
+ pos += 2;
+ if ((on && text.find(L"[/B]",pos) != std::string::npos) || // check for a matching end point
+ (!on && (currentStyle & FONT_STYLE_BOLD))) // or matching start point
+ newStyle = FONT_STYLE_BOLD;
+ }
+ else if (text.compare(pos, 2, L"I]") == 0)
+ { // italics
+ pos += 2;
+ if ((on && text.find(L"[/I]", pos) != std::string::npos) || // check for a matching end point
+ (!on && (currentStyle & FONT_STYLE_ITALICS))) // or matching start point
+ newStyle = FONT_STYLE_ITALICS;
+ }
+ else if (text.compare(pos, 10, L"UPPERCASE]") == 0)
+ {
+ pos += 10;
+ if ((on && text.find(L"[/UPPERCASE]", pos) != std::string::npos) || // check for a matching end point
+ (!on && (currentStyle & FONT_STYLE_UPPERCASE))) // or matching start point
+ newStyle = FONT_STYLE_UPPERCASE;
+ }
+ else if (text.compare(pos, 10, L"LOWERCASE]") == 0)
+ {
+ pos += 10;
+ if ((on && text.find(L"[/LOWERCASE]", pos) != std::string::npos) || // check for a matching end point
+ (!on && (currentStyle & FONT_STYLE_LOWERCASE))) // or matching start point
+ newStyle = FONT_STYLE_LOWERCASE;
+ }
+ else if (text.compare(pos, 11, L"CAPITALIZE]") == 0)
+ {
+ pos += 11;
+ if ((on && text.find(L"[/CAPITALIZE]", pos) != std::string::npos) || // check for a matching end point
+ (!on && (currentStyle & FONT_STYLE_CAPITALIZE))) // or matching start point
+ newStyle = FONT_STYLE_CAPITALIZE;
+ }
+ else if (text.compare(pos, 6, L"LIGHT]") == 0)
+ {
+ pos += 6;
+ if ((on && text.find(L"[/LIGHT]", pos) != std::string::npos) ||
+ (!on && (currentStyle & FONT_STYLE_LIGHT)))
+ newStyle = FONT_STYLE_LIGHT;
+ }
+ else if (text.compare(pos, 5, L"TABS]") == 0 && on)
+ {
+ pos += 5;
+ const size_t end = text.find(L"[/TABS]", pos);
+ if (end != std::string::npos)
+ {
+ std::string t;
+ g_charsetConverter.wToUTF8(text.substr(pos), t);
+ tabs = atoi(t.c_str());
+ pos = end + 7;
+ }
+ }
+ else if (text.compare(pos, 3, L"CR]") == 0 && on)
+ {
+ newLine = true;
+ pos += 3;
+ }
+ else if (text.compare(pos,5, L"COLOR") == 0)
+ { // color
+ size_t finish = text.find(L']', pos + 5);
+ if (on && finish != std::string::npos && text.find(L"[/COLOR]",finish) != std::string::npos)
+ {
+ std::string t;
+ g_charsetConverter.wToUTF8(text.substr(pos + 5, finish - pos - 5), t);
+ UTILS::COLOR::Color color = CServiceBroker::GetGUI()->GetColorManager().GetColor(t);
+ const auto& it = std::find(colors.begin(), colors.end(), color);
+ if (it == colors.end())
+ { // create new color
+ if (colors.size() <= 0xFF)
+ {
+ newColor = colors.size();
+ colors.push_back(color);
+ }
+ else // we have only 8 bits for color index, fallback to first color if reach max.
+ newColor = 0;
+ }
+ else
+ // reuse existing color
+ newColor = it - colors.begin();
+ colorStack.push(newColor);
+ colorTagChange = true;
+ }
+ else if (!on && finish == pos + 5 && colorStack.size() > 1)
+ { // revert to previous color
+ colorStack.pop();
+ newColor = colorStack.top();
+ colorTagChange = true;
+ }
+ if (finish != std::string::npos)
+ pos = finish + 1;
+ }
+
+ if (newStyle || colorTagChange || newLine || tabs)
+ { // we have a new style or a new color, so format up the previous segment
+ std::wstring subText = text.substr(startPos, endPos - startPos);
+ if (currentStyle & FONT_STYLE_UPPERCASE)
+ StringUtils::ToUpper(subText);
+ if (currentStyle & FONT_STYLE_LOWERCASE)
+ StringUtils::ToLower(subText);
+ if (currentStyle & FONT_STYLE_CAPITALIZE)
+ StringUtils::ToCapitalize(subText);
+ AppendToUTF32(subText, ((currentStyle & FONT_STYLE_MASK) << 24) | (currentColor << 16), parsedText);
+ if (newLine)
+ parsedText.push_back(L'\n');
+ for (int i = 0; i < tabs; ++i)
+ parsedText.push_back(L'\t');
+
+ // and switch to the new style
+ startPos = pos;
+ currentColor = newColor;
+ if (on)
+ currentStyle |= newStyle;
+ else
+ currentStyle &= ~newStyle;
+ }
+ pos = text.find(L'[', pos);
+ }
+ // now grab the remainder of the string
+ std::wstring subText = text.substr(startPos);
+ if (currentStyle & FONT_STYLE_UPPERCASE)
+ StringUtils::ToUpper(subText);
+ if (currentStyle & FONT_STYLE_LOWERCASE)
+ StringUtils::ToLower(subText);
+ if (currentStyle & FONT_STYLE_CAPITALIZE)
+ StringUtils::ToCapitalize(subText);
+ AppendToUTF32(subText, ((currentStyle & FONT_STYLE_MASK) << 24) | (currentColor << 16), parsedText);
+}
+
+void CGUITextLayout::SetMaxHeight(float fHeight)
+{
+ m_maxHeight = fHeight;
+}
+
+void CGUITextLayout::WrapText(const vecText &text, float maxWidth)
+{
+ if (!m_font)
+ return;
+
+ int nMaxLines = (m_maxHeight > 0 && m_font->GetLineHeight() > 0)?(int)ceilf(m_maxHeight / m_font->GetLineHeight()):-1;
+
+ m_lines.clear();
+
+ std::vector<CGUIString> lines;
+ LineBreakText(text, lines);
+
+ for (unsigned int i = 0; i < lines.size(); i++)
+ {
+ const CGUIString &line = lines[i];
+ vecText::const_iterator lastSpace = line.m_text.begin();
+ vecText::const_iterator pos = line.m_text.begin();
+ unsigned int lastSpaceInLine = 0;
+ vecText curLine;
+ while (pos != line.m_text.end())
+ {
+ // Get the current letter in the string
+ character_t letter = *pos;
+ // check for a space
+ if (CanWrapAtLetter(letter))
+ {
+ float width = m_font->GetTextWidth(curLine);
+ if (width > maxWidth)
+ {
+ if (lastSpace != line.m_text.begin() && lastSpaceInLine > 0)
+ {
+ CGUIString string(curLine.begin(), curLine.begin() + lastSpaceInLine, false);
+ m_lines.push_back(string);
+ // check for exceeding our number of lines
+ if (nMaxLines > 0 && m_lines.size() >= (size_t)nMaxLines)
+ return;
+ // skip over spaces
+ pos = lastSpace;
+ while (pos != line.m_text.end() && IsSpace(*pos))
+ ++pos;
+ curLine.clear();
+ lastSpaceInLine = 0;
+ lastSpace = line.m_text.begin();
+ continue;
+ }
+ }
+ lastSpace = pos;
+ lastSpaceInLine = curLine.size();
+ }
+ curLine.push_back(letter);
+ ++pos;
+ }
+ // now add whatever we have left to the string
+ float width = m_font->GetTextWidth(curLine);
+ if (width > maxWidth)
+ {
+ // too long - put up to the last space on if we can + remove it from what's left.
+ if (lastSpace != line.m_text.begin() && lastSpaceInLine > 0)
+ {
+ CGUIString string(curLine.begin(), curLine.begin() + lastSpaceInLine, false);
+ m_lines.push_back(string);
+ // check for exceeding our number of lines
+ if (nMaxLines > 0 && m_lines.size() >= (size_t)nMaxLines)
+ return;
+ curLine.erase(curLine.begin(), curLine.begin() + lastSpaceInLine);
+ while (curLine.size() && IsSpace(curLine.at(0)))
+ curLine.erase(curLine.begin());
+ }
+ }
+ CGUIString string(curLine.begin(), curLine.end(), true);
+ m_lines.push_back(string);
+ // check for exceeding our number of lines
+ if (nMaxLines > 0 && m_lines.size() >= (size_t)nMaxLines)
+ return;
+ }
+}
+
+void CGUITextLayout::LineBreakText(const vecText &text, std::vector<CGUIString> &lines)
+{
+ int nMaxLines = (m_maxHeight > 0 && m_font && m_font->GetLineHeight() > 0)?(int)ceilf(m_maxHeight / m_font->GetLineHeight()):-1;
+ vecText::const_iterator lineStart = text.begin();
+ vecText::const_iterator pos = text.begin();
+ while (pos != text.end() && (nMaxLines <= 0 || lines.size() < (size_t)nMaxLines))
+ {
+ // Get the current letter in the string
+ character_t letter = *pos;
+
+ // Handle the newline character
+ if ((letter & 0xffff) == L'\n' )
+ { // push back everything up till now
+ CGUIString string(lineStart, pos, true);
+ lines.push_back(string);
+ lineStart = pos + 1;
+ }
+ ++pos;
+ }
+ // handle the last line if non-empty
+ if (lineStart < text.end() && (nMaxLines <= 0 || lines.size() < (size_t)nMaxLines))
+ {
+ CGUIString string(lineStart, text.end(), true);
+ lines.push_back(string);
+ }
+}
+
+void CGUITextLayout::GetTextExtent(float &width, float &height) const
+{
+ width = m_textWidth;
+ height = m_textHeight;
+}
+
+void CGUITextLayout::CalcTextExtent()
+{
+ m_textWidth = 0;
+ m_textHeight = 0;
+ if (!m_font) return;
+
+ for (const auto& string : m_lines)
+ {
+ float w = m_font->GetTextWidth(string.m_text);
+ if (w > m_textWidth)
+ m_textWidth = w;
+ }
+ m_textHeight = m_font->GetTextHeight(m_lines.size());
+}
+
+unsigned int CGUITextLayout::GetTextLength() const
+{
+ unsigned int length = 0;
+ for (const auto& string : m_lines)
+ length += string.m_text.size();
+ return length;
+}
+
+void CGUITextLayout::GetFirstText(vecText &text) const
+{
+ text.clear();
+ if (m_lines.size())
+ text = m_lines[0].m_text;
+}
+
+float CGUITextLayout::GetTextWidth(const std::wstring &text) const
+{
+ // NOTE: Assumes a single line of text
+ if (!m_font) return 0;
+ vecText utf32;
+ AppendToUTF32(text, (m_font->GetStyle() & FONT_STYLE_MASK) << 24, utf32);
+ return m_font->GetTextWidth(utf32);
+}
+
+std::string CGUITextLayout::GetText() const
+{
+ if (m_lastUpdateW)
+ {
+ std::string utf8;
+ g_charsetConverter.wToUTF8(m_lastText, utf8);
+ return utf8;
+ }
+ return m_lastUtf8Text;
+}
+
+void CGUITextLayout::DrawText(CGUIFont* font,
+ float x,
+ float y,
+ UTILS::COLOR::Color color,
+ UTILS::COLOR::Color shadowColor,
+ const std::string& text,
+ uint32_t align)
+{
+ if (!font) return;
+ vecText utf32;
+ AppendToUTF32(text, 0, utf32);
+ font->DrawText(x, y, color, shadowColor, utf32, align, 0);
+}
+
+void CGUITextLayout::AppendToUTF32(const std::wstring &utf16, character_t colStyle, vecText &utf32)
+{
+ // NOTE: Assumes a single line of text
+ utf32.reserve(utf32.size() + utf16.size());
+ for (unsigned int i = 0; i < utf16.size(); i++)
+ utf32.push_back(utf16[i] | colStyle);
+}
+
+void CGUITextLayout::AppendToUTF32(const std::string &utf8, character_t colStyle, vecText &utf32)
+{
+ std::wstring utf16;
+ // no need to bidiflip here - it's done in BidiTransform above
+ g_charsetConverter.utf8ToW(utf8, utf16, false);
+ AppendToUTF32(utf16, colStyle, utf32);
+}
+
+void CGUITextLayout::Reset()
+{
+ m_lines.clear();
+ m_lastText.clear();
+ m_lastUtf8Text.clear();
+ m_textWidth = m_textHeight = 0;
+}
+
+