/* * 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 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 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& 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& 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 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& 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