2 * Copyright (C) 2005-2008 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, write to
17 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18 * http://www.gnu.org/copyleft/gpl.html
22 #include "GUITextLayout.h"
24 #include "GUIControl.h"
25 #include "GUIColorManager.h"
26 #include "utils/CharsetConverter.h"
27 #include "StringUtils.h"
31 #define WORK_AROUND_NEEDED_FOR_LINE_BREAKS
33 CGUIString::CGUIString(iString start, iString end, bool carriageReturn)
35 m_text.assign(start, end);
36 m_carriageReturn = carriageReturn;
39 CStdString CGUIString::GetAsString() const
42 for (unsigned int i = 0; i < m_text.size(); i++)
43 text += (char)(m_text[i] & 0xff);
47 CGUITextLayout::CGUITextLayout(CGUIFont *font, bool wrap, float fHeight, CGUIFont *borderFont)
50 m_borderFont = borderFont;
53 m_maxHeight = fHeight;
58 void CGUITextLayout::SetWrap(bool bWrap)
63 void CGUITextLayout::Render(float x, float y, float angle, color_t color, color_t shadowColor, uint32_t alignment, float maxWidth, bool solid)
68 // set the main text color
72 // render the text at the required location, angle, and size
75 static const float degrees_to_radians = 0.01745329252f;
76 g_graphicsContext.AddTransform(TransformMatrix::CreateZRotation(angle * degrees_to_radians, x, y, g_graphicsContext.GetScalingPixelRatio()));
78 // center our text vertically
79 if (alignment & XBFONT_CENTER_Y)
81 y -= m_font->GetTextHeight(m_lines.size()) * 0.5f;;
82 alignment &= ~XBFONT_CENTER_Y;
85 for (vector<CGUIString>::iterator i = m_lines.begin(); i != m_lines.end(); i++)
87 const CGUIString &string = *i;
88 uint32_t align = alignment;
89 if (align & XBFONT_JUSTIFIED && string.m_carriageReturn)
90 align &= ~XBFONT_JUSTIFIED;
92 m_font->DrawText(x, y, m_colors[0], shadowColor, string.m_text, align, maxWidth);
94 m_font->DrawText(x, y, m_colors, shadowColor, string.m_text, align, maxWidth);
95 y += m_font->GetLineHeight();
99 g_graphicsContext.RemoveTransform();
103 void CGUITextLayout::RenderScrolling(float x, float y, float angle, color_t color, color_t shadowColor, uint32_t alignment, float maxWidth, CScrollInfo &scrollInfo)
108 // set the main text color
112 // render the text at the required location, angle, and size
115 static const float degrees_to_radians = 0.01745329252f;
116 g_graphicsContext.AddTransform(TransformMatrix::CreateZRotation(angle * degrees_to_radians, x, y, g_graphicsContext.GetScalingPixelRatio()));
118 // center our text vertically
119 if (alignment & XBFONT_CENTER_Y)
121 y -= m_font->GetTextHeight(m_lines.size()) * 0.5f;;
122 alignment &= ~XBFONT_CENTER_Y;
125 // NOTE: This workaround is needed as otherwise multi-line text that scrolls
126 // will scroll in proportion to the number of lines. Ideally we should
127 // do the DrawScrollingText calculation here. This probably won't make
128 // any difference to the smoothness of scrolling though which will be
129 // jumpy with this sort of thing. It's not exactly a well used situation
130 // though, so this hack is probably OK.
131 float speed = scrollInfo.pixelSpeed;
132 for (vector<CGUIString>::iterator i = m_lines.begin(); i != m_lines.end(); i++)
134 const CGUIString &string = *i;
135 m_font->DrawScrollingText(x, y, m_colors, shadowColor, string.m_text, alignment, maxWidth, scrollInfo);
136 y += m_font->GetLineHeight();
137 scrollInfo.pixelSpeed = 0;
139 scrollInfo.pixelSpeed = speed;
142 g_graphicsContext.RemoveTransform();
145 void CGUITextLayout::RenderOutline(float x, float y, color_t color, color_t outlineColor, uint32_t alignment, float maxWidth)
150 // set the outline color
151 vecColors outlineColors;
153 outlineColors.push_back(outlineColor);
155 // center our text vertically
156 if (alignment & XBFONT_CENTER_Y)
158 y -= m_font->GetTextHeight(m_lines.size()) * 0.5f;;
159 alignment &= ~XBFONT_CENTER_Y;
164 m_borderFont->Begin();
165 for (vector<CGUIString>::iterator i = m_lines.begin(); i != m_lines.end(); i++)
167 const CGUIString &string = *i;
168 uint32_t align = alignment;
169 if (align & XBFONT_JUSTIFIED && string.m_carriageReturn)
170 align &= ~XBFONT_JUSTIFIED;
172 m_borderFont->DrawText(x, by, outlineColors, 0, string.m_text, align, maxWidth);
173 by += m_borderFont->GetLineHeight();
178 // set the main text color
183 for (vector<CGUIString>::iterator i = m_lines.begin(); i != m_lines.end(); i++)
185 const CGUIString &string = *i;
186 uint32_t align = alignment;
187 if (align & XBFONT_JUSTIFIED && string.m_carriageReturn)
188 align &= ~XBFONT_JUSTIFIED;
190 m_font->DrawText(x, y, m_colors, 0, string.m_text, align, maxWidth);
191 y += m_font->GetLineHeight();
196 bool CGUITextLayout::Update(const CStdString &text, float maxWidth, bool forceUpdate /*= false*/, bool forceLTRReadingOrder /*= false*/)
198 if (text == m_lastText && !forceUpdate)
203 utf8ToW(text, utf16);
206 SetText(utf16, maxWidth, forceLTRReadingOrder);
208 // and set our parameters to indicate no further update is required
213 void CGUITextLayout::SetText(const CStdStringW &text, float maxWidth, bool forceLTRReadingOrder /*= false*/)
217 // empty out our previous string
220 m_colors.push_back(m_textColor);
222 // parse the text into our string objects
223 ParseText(text, parsedText);
225 // add \n to the end of the string
226 parsedText.push_back(L'\n');
228 // if we need to wrap the text, then do so
229 if (m_wrap && maxWidth > 0)
230 WrapText(parsedText, maxWidth);
232 LineBreakText(parsedText, m_lines);
234 // remove any trailing blank lines
235 while (!m_lines.empty() && m_lines.back().m_text.empty())
238 BidiTransform(m_lines, forceLTRReadingOrder);
240 // and cache the width and height for later reading
244 // BidiTransform is used to handle RTL text flipping in the string
245 void CGUITextLayout::BidiTransform(vector<CGUIString> &lines, bool forceLTRReadingOrder)
247 for (unsigned int i=0; i<lines.size(); i++)
249 CGUIString &line = lines[i];
251 // reserve enough space in the flipped text
253 flippedText.reserve(line.m_text.size());
255 character_t sectionStyle = 0xffff0000; // impossible to achieve
256 CStdStringW sectionText;
257 for (vecText::iterator it = line.m_text.begin(); it != line.m_text.end(); ++it)
259 character_t style = *it & 0xffff0000;
260 if (style != sectionStyle)
262 if (!sectionText.IsEmpty())
263 { // style has changed, bidi flip text
264 CStdStringW sectionFlipped = BidiFlip(sectionText, forceLTRReadingOrder);
265 for (unsigned int j = 0; j < sectionFlipped.size(); j++)
266 flippedText.push_back(sectionStyle | sectionFlipped[j]);
268 sectionStyle = style;
271 sectionText.push_back( (wchar_t)(*it & 0xffff) );
274 // handle the last section
275 if (!sectionText.IsEmpty())
277 CStdStringW sectionFlipped = BidiFlip(sectionText, forceLTRReadingOrder);
278 for (unsigned int j = 0; j < sectionFlipped.size(); j++)
279 flippedText.push_back(sectionStyle | sectionFlipped[j]);
282 // replace the original line with the proccessed one
283 lines[i] = CGUIString(flippedText.begin(), flippedText.end(), line.m_carriageReturn);
287 CStdStringW CGUITextLayout::BidiFlip(const CStdStringW &text, bool forceLTRReadingOrder)
289 CStdStringA utf8text;
290 CStdStringW visualText;
292 // convert to utf8, and back to utf16 with bidi flipping
293 g_charsetConverter.wToUTF8(text, utf8text);
294 g_charsetConverter.utf8ToW(utf8text, visualText, true, forceLTRReadingOrder);
299 void CGUITextLayout::Filter(CStdString &text)
302 utf8ToW(text, utf16);
305 ParseText(utf16, 0, colors, parsedText);
307 for (unsigned int i = 0; i < parsedText.size(); i++)
308 utf16 += (wchar_t)(0xffff & parsedText[i]);
309 g_charsetConverter.wToUTF8(utf16, text);
312 void CGUITextLayout::ParseText(const CStdStringW &text, vecText &parsedText)
316 ParseText(text, m_font->GetStyle(), m_colors, parsedText);
319 void CGUITextLayout::ParseText(const CStdStringW &text, uint32_t defaultStyle, vecColors &colors, vecText &parsedText)
321 // run through the string, searching for:
322 // [B] or [/B] -> toggle bold on and off
323 // [I] or [/I] -> toggle italics on and off
324 // [COLOR ffab007f] or [/COLOR] -> toggle color on and off
325 // [CAPS <option>] or [/CAPS] -> toggle capatilization on and off
327 uint32_t currentStyle = defaultStyle; // start with the default font's style
328 color_t currentColor = 0;
330 stack<color_t> colorStack;
333 // these aren't independent, but that's probably not too much of an issue
334 // eg [UPPERCASE]Glah[LOWERCASE]FReD[/LOWERCASE]Georeg[/UPPERCASE] will work (lower case >> upper case)
335 // but [LOWERCASE]Glah[UPPERCASE]FReD[/UPPERCASE]Georeg[/LOWERCASE] won't
336 #define FONT_STYLE_UPPERCASE 4
337 #define FONT_STYLE_LOWERCASE 8
340 size_t pos = text.Find(L'[');
341 while (pos != CStdString::npos && pos + 1 < text.size())
343 uint32_t newStyle = 0;
344 color_t newColor = currentColor;
345 bool newLine = false;
346 // have a [ - check if it's an ON or OFF switch
348 int endPos = pos++; // finish of string
349 if (text[pos] == L'/')
354 // check for each type
355 if (text.Mid(pos,2) == L"B]")
356 { // bold - finish the current text block and assign the bold state
358 if ((on && text.Find(L"[/B]",pos) >= 0) || // check for a matching end point
359 (!on && (currentStyle & FONT_STYLE_BOLD))) // or matching start point
360 newStyle = FONT_STYLE_BOLD;
362 else if (text.Mid(pos,2) == L"I]")
365 if ((on && text.Find(L"[/I]",pos) >= 0) || // check for a matching end point
366 (!on && (currentStyle & FONT_STYLE_ITALICS))) // or matching start point
367 newStyle = FONT_STYLE_ITALICS;
369 else if (text.Mid(pos,10) == L"UPPERCASE]")
372 if ((on && text.Find(L"[/UPPERCASE]",pos) >= 0) || // check for a matching end point
373 (!on && (currentStyle & FONT_STYLE_UPPERCASE))) // or matching start point
374 newStyle = FONT_STYLE_UPPERCASE;
376 else if (text.Mid(pos,10) == L"LOWERCASE]")
379 if ((on && text.Find(L"[/LOWERCASE]",pos) >= 0) || // check for a matching end point
380 (!on && (currentStyle & FONT_STYLE_LOWERCASE))) // or matching start point
381 newStyle = FONT_STYLE_LOWERCASE;
383 else if (text.Mid(pos,3) == L"CR]" && on)
388 else if (text.Mid(pos,5) == L"COLOR")
390 size_t finish = text.Find(L']', pos + 5);
391 if (on && finish != CStdString::npos && (size_t)text.Find(L"[/COLOR]",finish) != CStdString::npos)
392 { // create new color
393 newColor = colors.size();
394 colors.push_back(g_colorManager.GetColor(text.Mid(pos + 5, finish - pos - 5)));
395 colorStack.push(newColor);
397 else if (!on && finish == pos + 5 && colorStack.size() > 1)
398 { // revert to previous color
400 newColor = colorStack.top();
405 if (newStyle || newColor != currentColor || newLine)
406 { // we have a new style or a new color, so format up the previous segment
407 CStdStringW subText = text.Mid(startPos, endPos - startPos);
408 if (currentStyle & FONT_STYLE_UPPERCASE)
410 if (currentStyle & FONT_STYLE_LOWERCASE)
412 AppendToUTF32(subText, ((currentStyle & 3) << 24) | (currentColor << 16), parsedText);
414 parsedText.push_back(L'\n');
416 // and switch to the new style
418 currentColor = newColor;
420 currentStyle |= newStyle;
422 currentStyle &= ~newStyle;
424 pos = text.Find(L'[',pos);
426 // now grab the remainder of the string
427 CStdStringW subText = text.Mid(startPos, text.GetLength() - startPos);
428 if (currentStyle & FONT_STYLE_UPPERCASE)
430 if (currentStyle & FONT_STYLE_LOWERCASE)
432 AppendToUTF32(subText, ((currentStyle & 3) << 24) | (currentColor << 16), parsedText);
435 void CGUITextLayout::SetMaxHeight(float fHeight)
437 m_maxHeight = fHeight;
440 void CGUITextLayout::WrapText(const vecText &text, float maxWidth)
445 int nMaxLines = (m_maxHeight > 0 && m_font->GetLineHeight() > 0)?(int)ceilf(m_maxHeight / m_font->GetLineHeight()):-1;
449 vector<CGUIString> lines;
450 LineBreakText(text, lines);
452 for (unsigned int i = 0; i < lines.size(); i++)
454 const CGUIString &line = lines[i];
455 vecText::const_iterator lastSpace = line.m_text.begin();
456 vecText::const_iterator pos = line.m_text.begin();
457 unsigned int lastSpaceInLine = 0;
459 while (pos != line.m_text.end() && (nMaxLines <= 0 || m_lines.size() < (size_t)nMaxLines))
461 // Get the current letter in the string
462 character_t letter = *pos;
464 if (CanWrapAtLetter(letter))
466 float width = m_font->GetTextWidth(curLine);
467 if (width > maxWidth)
469 if (lastSpace != line.m_text.begin() && lastSpaceInLine > 0)
471 CGUIString string(curLine.begin(), curLine.begin() + lastSpaceInLine, false);
472 m_lines.push_back(string);
475 while (pos != line.m_text.end() && IsSpace(*pos))
479 lastSpace = line.m_text.begin();
484 lastSpaceInLine = curLine.size();
486 curLine.push_back(letter);
489 // now add whatever we have left to the string
490 float width = m_font->GetTextWidth(curLine);
491 if (width > maxWidth)
493 // too long - put up to the last space on if we can + remove it from what's left.
494 if (lastSpace != line.m_text.begin() && lastSpaceInLine > 0)
496 CGUIString string(curLine.begin(), curLine.begin() + lastSpaceInLine, false);
497 m_lines.push_back(string);
498 curLine.erase(curLine.begin(), curLine.begin() + lastSpaceInLine);
499 while (curLine.size() && IsSpace(curLine.at(0)))
500 curLine.erase(curLine.begin());
503 CGUIString string(curLine.begin(), curLine.end(), true);
504 m_lines.push_back(string);
508 void CGUITextLayout::LineBreakText(const vecText &text, vector<CGUIString> &lines)
510 int nMaxLines = (m_maxHeight > 0 && m_font && m_font->GetLineHeight() > 0)?(int)ceilf(m_maxHeight / m_font->GetLineHeight()):-1;
511 vecText::const_iterator lineStart = text.begin();
512 vecText::const_iterator pos = text.begin();
513 while (pos != text.end() && (nMaxLines <= 0 || lines.size() < (size_t)nMaxLines))
515 // Get the current letter in the string
516 character_t letter = *pos;
518 // Handle the newline character
519 if ((letter & 0xffff) == L'\n' )
520 { // push back everything up till now
521 CGUIString string(lineStart, pos, true);
522 lines.push_back(string);
529 void CGUITextLayout::GetTextExtent(float &width, float &height) const
532 height = m_textHeight;
535 void CGUITextLayout::CalcTextExtent()
541 for (vector<CGUIString>::iterator i = m_lines.begin(); i != m_lines.end(); i++)
543 const CGUIString &string = *i;
544 float w = m_font->GetTextWidth(string.m_text);
548 m_textHeight = m_font->GetTextHeight(m_lines.size());
551 unsigned int CGUITextLayout::GetTextLength() const
553 unsigned int length = 0;
554 for (vector<CGUIString>::const_iterator i = m_lines.begin(); i != m_lines.end(); i++)
555 length += i->m_text.size();
559 void CGUITextLayout::GetFirstText(vecText &text) const
563 text = m_lines[0].m_text;
566 float CGUITextLayout::GetTextWidth(const CStdStringW &text) const
568 // NOTE: Assumes a single line of text
569 if (!m_font) return 0;
571 AppendToUTF32(text, (m_font->GetStyle() & 3) << 24, utf32);
572 return m_font->GetTextWidth(utf32);
575 void CGUITextLayout::DrawText(CGUIFont *font, float x, float y, color_t color, color_t shadowColor, const CStdString &text, uint32_t align)
579 AppendToUTF32(text, 0, utf32);
580 font->DrawText(x, y, color, shadowColor, utf32, align, 0);
583 void CGUITextLayout::AppendToUTF32(const CStdStringW &utf16, character_t colStyle, vecText &utf32)
585 // NOTE: Assumes a single line of text
586 utf32.reserve(utf32.size() + utf16.size());
587 for (unsigned int i = 0; i < utf16.size(); i++)
588 utf32.push_back(utf16[i] | colStyle);
591 void CGUITextLayout::utf8ToW(const CStdString &utf8, CStdStringW &utf16)
593 #ifdef WORK_AROUND_NEEDED_FOR_LINE_BREAKS
594 // NOTE: This appears to strip \n characters from text. This may be a consequence of incorrect
595 // expression of the \n in utf8 (we just use character code 10) or it might be something
596 // more sinister. For now, we use the workaround below.
597 CStdStringArray multiLines;
598 StringUtils::SplitString(utf8, "\n", multiLines);
599 for (unsigned int i = 0; i < multiLines.size(); i++)
602 // no need to bidiflip here - it's done in BidiTransform above
603 g_charsetConverter.utf8ToW(multiLines[i], line, false);
605 if (i < multiLines.size() - 1)
606 utf16.push_back(L'\n');
609 // no need to bidiflip here - it's done in BidiTransform above
610 g_charsetConverter.utf8ToW(utf8, utf16, false);
614 void CGUITextLayout::AppendToUTF32(const CStdString &utf8, character_t colStyle, vecText &utf32)
617 utf8ToW(utf8, utf16);
618 AppendToUTF32(utf16, colStyle, utf32);
621 void CGUITextLayout::Reset()
625 m_textWidth = m_textHeight = 0;