99682897ca3c38ca2e3517d10f70b4c9c3d480f4
[vuplus_xbmc] / xbmc / guilib / GUITextLayout.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "GUITextLayout.h"
22 #include "GUIFont.h"
23 #include "GUIControl.h"
24 #include "GUIColorManager.h"
25 #include "utils/CharsetConverter.h"
26 #include "utils/StringUtils.h"
27
28 using namespace std;
29
30 #define WORK_AROUND_NEEDED_FOR_LINE_BREAKS
31
32 CGUIString::CGUIString(iString start, iString end, bool carriageReturn)
33 {
34   m_text.assign(start, end);
35   m_carriageReturn = carriageReturn;
36 }
37
38 CStdString CGUIString::GetAsString() const
39 {
40   CStdString text;
41   for (unsigned int i = 0; i < m_text.size(); i++)
42     text += (char)(m_text[i] & 0xff);
43   return text;
44 }
45
46 CGUITextLayout::CGUITextLayout(CGUIFont *font, bool wrap, float fHeight, CGUIFont *borderFont)
47 {
48   m_font = font;
49   m_borderFont = borderFont;
50   m_textColor = 0;
51   m_wrap = wrap;
52   m_maxHeight = fHeight;
53   m_textWidth = 0;
54   m_textHeight = 0;
55 }
56
57 void CGUITextLayout::SetWrap(bool bWrap)
58 {
59   m_wrap = bWrap;
60 }
61
62 void CGUITextLayout::Render(float x, float y, float angle, color_t color, color_t shadowColor, uint32_t alignment, float maxWidth, bool solid)
63 {
64   if (!m_font)
65     return;
66
67   // set the main text color
68   if (m_colors.size())
69     m_colors[0] = color;
70
71   // render the text at the required location, angle, and size
72   if (angle)
73   {
74     static const float degrees_to_radians = 0.01745329252f;
75     g_graphicsContext.AddTransform(TransformMatrix::CreateZRotation(angle * degrees_to_radians, x, y, g_graphicsContext.GetScalingPixelRatio()));
76   }
77   // center our text vertically
78   if (alignment & XBFONT_CENTER_Y)
79   {
80     y -= m_font->GetTextHeight(m_lines.size()) * 0.5f;;
81     alignment &= ~XBFONT_CENTER_Y;
82   }
83   m_font->Begin();
84   for (vector<CGUIString>::iterator i = m_lines.begin(); i != m_lines.end(); ++i)
85   {
86     const CGUIString &string = *i;
87     uint32_t align = alignment;
88     if (align & XBFONT_JUSTIFIED && string.m_carriageReturn)
89       align &= ~XBFONT_JUSTIFIED;
90     if (solid)
91       m_font->DrawText(x, y, m_colors[0], shadowColor, string.m_text, align, maxWidth);
92     else
93       m_font->DrawText(x, y, m_colors, shadowColor, string.m_text, align, maxWidth);
94     y += m_font->GetLineHeight();
95   }
96   m_font->End();
97   if (angle)
98     g_graphicsContext.RemoveTransform();
99 }
100
101 bool CGUITextLayout::UpdateScrollinfo(CScrollInfo &scrollInfo)
102 {
103   if (!m_font)
104     return false;
105   if (m_lines.empty())
106     return false;
107
108   return m_font->UpdateScrollInfo(m_lines[0].m_text, scrollInfo);
109 }
110
111
112 void CGUITextLayout::RenderScrolling(float x, float y, float angle, color_t color, color_t shadowColor, uint32_t alignment, float maxWidth, const CScrollInfo &scrollInfo)
113 {
114   if (!m_font)
115     return;
116
117   // set the main text color
118   if (m_colors.size())
119     m_colors[0] = color;
120
121   // render the text at the required location, angle, and size
122   if (angle)
123   {
124     static const float degrees_to_radians = 0.01745329252f;
125     g_graphicsContext.AddTransform(TransformMatrix::CreateZRotation(angle * degrees_to_radians, x, y, g_graphicsContext.GetScalingPixelRatio()));
126   }
127   // center our text vertically
128   if (alignment & XBFONT_CENTER_Y)
129   {
130     y -= m_font->GetTextHeight(m_lines.size()) * 0.5f;;
131     alignment &= ~XBFONT_CENTER_Y;
132   }
133   m_font->Begin();
134   // NOTE: This workaround is needed as otherwise multi-line text that scrolls
135   //       will scroll in proportion to the number of lines.  Ideally we should
136   //       do the DrawScrollingText calculation here.  This probably won't make
137   //       any difference to the smoothness of scrolling though which will be
138   //       jumpy with this sort of thing.  It's not exactly a well used situation
139   //       though, so this hack is probably OK.
140   for (vector<CGUIString>::iterator i = m_lines.begin(); i != m_lines.end(); ++i)
141   {
142     const CGUIString &string = *i;
143     m_font->DrawScrollingText(x, y, m_colors, shadowColor, string.m_text, alignment, maxWidth, scrollInfo);
144     y += m_font->GetLineHeight();
145   }
146   m_font->End();
147   if (angle)
148     g_graphicsContext.RemoveTransform();
149 }
150
151 void CGUITextLayout::RenderOutline(float x, float y, color_t color, color_t outlineColor, uint32_t alignment, float maxWidth)
152 {
153   if (!m_font)
154     return;
155
156   // set the outline color
157   vecColors outlineColors;
158   if (m_colors.size())
159     outlineColors.push_back(outlineColor);
160
161   // center our text vertically
162   if (alignment & XBFONT_CENTER_Y)
163   {
164     y -= m_font->GetTextHeight(m_lines.size()) * 0.5f;;
165     alignment &= ~XBFONT_CENTER_Y;
166   }
167   if (m_borderFont)
168   {
169     // adjust so the baselines of the fonts align
170     float by = y + m_font->GetTextBaseLine() - m_borderFont->GetTextBaseLine();
171     m_borderFont->Begin();
172     for (vector<CGUIString>::iterator i = m_lines.begin(); i != m_lines.end(); ++i)
173     {
174       const CGUIString &string = *i;
175       uint32_t align = alignment;
176       if (align & XBFONT_JUSTIFIED && string.m_carriageReturn)
177         align &= ~XBFONT_JUSTIFIED;
178       // text centered horizontally must be computed using the original font, not the bordered
179       // font, as the bordered font will be wider, and thus will end up uncentered.
180       // TODO: We should really have a better way to handle text extent - at the moment we assume
181       //       that text is rendered from a posx, posy, width, and height which isn't enough to
182       //       accurately position text. We need a vertical and horizontal offset of the baseline
183       //       and cursor as well.
184       float bx = x;
185       if (align & XBFONT_CENTER_X)
186       {
187         bx -= m_font->GetTextWidth(string.m_text) * 0.5f;
188         align &= ~XBFONT_CENTER_X;
189       }
190
191       // don't pass maxWidth through to the renderer for the same reason above: it will cause clipping
192       // on the left.
193       m_borderFont->DrawText(bx, by, outlineColors, 0, string.m_text, align, 0);
194       by += m_borderFont->GetLineHeight();
195     }
196     m_borderFont->End();
197   }
198
199   // set the main text color
200   if (m_colors.size())
201     m_colors[0] = color;
202
203   m_font->Begin();
204   for (vector<CGUIString>::iterator i = m_lines.begin(); i != m_lines.end(); ++i)
205   {
206     const CGUIString &string = *i;
207     uint32_t align = alignment;
208     if (align & XBFONT_JUSTIFIED && string.m_carriageReturn)
209       align &= ~XBFONT_JUSTIFIED;
210
211     // don't pass maxWidth through to the renderer for the reason above.
212     m_font->DrawText(x, y, m_colors, 0, string.m_text, align, 0);
213     y += m_font->GetLineHeight();
214   }
215   m_font->End();
216 }
217
218 bool CGUITextLayout::Update(const CStdString &text, float maxWidth, bool forceUpdate /*= false*/, bool forceLTRReadingOrder /*= false*/)
219 {
220   if (text == m_lastUtf8Text && !forceUpdate)
221     return false;
222
223   m_lastUtf8Text = text;
224   CStdStringW utf16;
225   utf8ToW(text, utf16);
226   UpdateCommon(utf16, maxWidth, forceLTRReadingOrder);
227   return true;
228 }
229
230 bool CGUITextLayout::UpdateW(const CStdStringW &text, float maxWidth /*= 0*/, bool forceUpdate /*= false*/, bool forceLTRReadingOrder /*= false*/)
231 {
232   if (text == m_lastText && !forceUpdate)
233     return false;
234
235   m_lastText = text;
236   UpdateCommon(text, maxWidth, forceLTRReadingOrder);
237   return true;
238 }
239
240 void CGUITextLayout::UpdateCommon(const CStdStringW &text, float maxWidth, bool forceLTRReadingOrder)
241 {
242   // parse the text for style information
243   vecText parsedText;
244   vecColors colors;
245   ParseText(text, m_font ? m_font->GetStyle() : 0, m_textColor, colors, parsedText);
246
247   // and update
248   UpdateStyled(parsedText, colors, maxWidth, forceLTRReadingOrder);
249 }
250
251 void CGUITextLayout::UpdateStyled(const vecText &text, const vecColors &colors, float maxWidth, bool forceLTRReadingOrder)
252 {
253   // empty out our previous string
254   m_lines.clear();
255   m_colors = colors;
256
257   // if we need to wrap the text, then do so
258   if (m_wrap && maxWidth > 0)
259     WrapText(text, maxWidth);
260   else
261     LineBreakText(text, m_lines);
262
263   // remove any trailing blank lines
264   while (!m_lines.empty() && m_lines.back().m_text.empty())
265     m_lines.pop_back();
266
267   BidiTransform(m_lines, forceLTRReadingOrder);
268
269   // and cache the width and height for later reading
270   CalcTextExtent();
271 }
272
273 // BidiTransform is used to handle RTL text flipping in the string
274 void CGUITextLayout::BidiTransform(vector<CGUIString> &lines, bool forceLTRReadingOrder)
275 {
276   for (unsigned int i=0; i<lines.size(); i++)
277   {
278     CGUIString &line = lines[i];
279
280     // reserve enough space in the flipped text
281     vecText flippedText;
282     flippedText.reserve(line.m_text.size());
283
284     character_t sectionStyle = 0xffff0000; // impossible to achieve
285     CStdStringW sectionText;
286     for (vecText::iterator it = line.m_text.begin(); it != line.m_text.end(); ++it)
287     {
288       character_t style = *it & 0xffff0000;
289       if (style != sectionStyle)
290       {
291         if (!sectionText.empty())
292         { // style has changed, bidi flip text
293           CStdStringW sectionFlipped = BidiFlip(sectionText, forceLTRReadingOrder);
294           for (unsigned int j = 0; j < sectionFlipped.size(); j++)
295             flippedText.push_back(sectionStyle | sectionFlipped[j]);
296         }
297         sectionStyle = style;
298         sectionText.clear();
299       }
300       sectionText.push_back( (wchar_t)(*it & 0xffff) );
301     }
302
303     // handle the last section
304     if (!sectionText.empty())
305     {
306       CStdStringW sectionFlipped = BidiFlip(sectionText, forceLTRReadingOrder);
307       for (unsigned int j = 0; j < sectionFlipped.size(); j++)
308         flippedText.push_back(sectionStyle | sectionFlipped[j]);
309     }
310
311     // replace the original line with the proccessed one
312     lines[i] = CGUIString(flippedText.begin(), flippedText.end(), line.m_carriageReturn);
313   }
314 }
315
316 CStdStringW CGUITextLayout::BidiFlip(const CStdStringW &text, bool forceLTRReadingOrder)
317 {
318   CStdStringA utf8text;
319   CStdStringW visualText;
320
321   // convert to utf8, and back to utf16 with bidi flipping
322   g_charsetConverter.wToUTF8(text, utf8text);
323   g_charsetConverter.utf8ToW(utf8text, visualText, true, forceLTRReadingOrder);
324
325   return visualText;
326 }
327
328 void CGUITextLayout::Filter(CStdString &text)
329 {
330   CStdStringW utf16;
331   utf8ToW(text, utf16);
332   vecColors colors;
333   vecText parsedText;
334   ParseText(utf16, 0, 0xffffffff, colors, parsedText);
335   utf16.clear();
336   for (unsigned int i = 0; i < parsedText.size(); i++)
337     utf16 += (wchar_t)(0xffff & parsedText[i]);
338   g_charsetConverter.wToUTF8(utf16, text);
339 }
340
341 void CGUITextLayout::ParseText(const CStdStringW &text, uint32_t defaultStyle, color_t defaultColor, vecColors &colors, vecText &parsedText)
342 {
343   // run through the string, searching for:
344   // [B] or [/B] -> toggle bold on and off
345   // [I] or [/I] -> toggle italics on and off
346   // [COLOR ffab007f] or [/COLOR] -> toggle color on and off
347   // [CAPS <option>] or [/CAPS] -> toggle capatilization on and off
348
349   uint32_t currentStyle = defaultStyle; // start with the default font's style
350   color_t currentColor = 0;
351
352   colors.push_back(defaultColor);
353   stack<color_t> colorStack;
354   colorStack.push(0);
355
356   // these aren't independent, but that's probably not too much of an issue
357   // eg [UPPERCASE]Glah[LOWERCASE]FReD[/LOWERCASE]Georeg[/UPPERCASE] will work (lower case >> upper case)
358   // but [LOWERCASE]Glah[UPPERCASE]FReD[/UPPERCASE]Georeg[/LOWERCASE] won't
359
360   int startPos = 0;
361   size_t pos = text.find(L'[');
362   while (pos != std::string::npos && pos + 1 < text.size())
363   {
364     uint32_t newStyle = 0;
365     color_t newColor = currentColor;
366     bool colorTagChange = false;
367     bool newLine = false;
368     // have a [ - check if it's an ON or OFF switch
369     bool on(true);
370     size_t endPos = pos++; // finish of string
371     if (text[pos] == L'/')
372     {
373       on = false;
374       pos++;
375     }
376     // check for each type
377     if (text.compare(pos, 2, L"B]") == 0)
378     { // bold - finish the current text block and assign the bold state
379       pos += 2;
380       if ((on && text.find(L"[/B]",pos) != std::string::npos) ||          // check for a matching end point
381          (!on && (currentStyle & FONT_STYLE_BOLD)))       // or matching start point
382         newStyle = FONT_STYLE_BOLD;
383     }
384     else if (text.compare(pos, 2, L"I]") == 0)
385     { // italics
386       pos += 2;
387       if ((on && text.find(L"[/I]", pos) != std::string::npos) ||          // check for a matching end point
388          (!on && (currentStyle & FONT_STYLE_ITALICS)))    // or matching start point
389         newStyle = FONT_STYLE_ITALICS;
390     }
391     else if (text.compare(pos, 10, L"UPPERCASE]") == 0)
392     {
393       pos += 10;
394       if ((on && text.find(L"[/UPPERCASE]", pos) != std::string::npos) ||  // check for a matching end point
395          (!on && (currentStyle & FONT_STYLE_UPPERCASE)))  // or matching start point
396         newStyle = FONT_STYLE_UPPERCASE;
397     }
398     else if (text.compare(pos, 10, L"LOWERCASE]") == 0)
399     {
400       pos += 10;
401       if ((on && text.find(L"[/LOWERCASE]", pos) != std::string::npos) ||  // check for a matching end point
402          (!on && (currentStyle & FONT_STYLE_LOWERCASE)))  // or matching start point
403         newStyle = FONT_STYLE_LOWERCASE;
404     }
405     else if (text.compare(pos, 3, L"CR]") == 0 && on)
406     {
407       newLine = true;
408       pos += 3;
409     }
410     else if (text.compare(pos,5, L"COLOR") == 0)
411     { // color
412       size_t finish = text.find(L']', pos + 5);
413       if (on && finish != std::string::npos && text.find(L"[/COLOR]",finish) != std::string::npos)
414       {
415         std::string t;
416         g_charsetConverter.wToUTF8(text.substr(pos + 5, finish - pos - 5), t);
417         color_t color = g_colorManager.GetColor(t);
418         vecColors::const_iterator it = std::find(colors.begin(), colors.end(), color);
419         if (it == colors.end())
420         { // create new color
421           if (colors.size() <= 0xFF)
422           {
423             newColor = colors.size();
424             colors.push_back(color);
425           }
426           else // we have only 8 bits for color index, fallback to first color if reach max.
427             newColor = 0;
428         }
429         else
430           // reuse existing color
431           newColor = it - colors.begin();
432         colorStack.push(newColor);
433         colorTagChange = true;
434       }
435       else if (!on && finish == pos + 5 && colorStack.size() > 1)
436       { // revert to previous color
437         colorStack.pop();
438         newColor = colorStack.top();
439         colorTagChange = true;
440       }
441       if (finish != CStdString::npos)
442         pos = finish + 1;
443     }
444
445     if (newStyle || colorTagChange || newLine)
446     { // we have a new style or a new color, so format up the previous segment
447       CStdStringW subText = text.substr(startPos, endPos - startPos);
448       if (currentStyle & FONT_STYLE_UPPERCASE)
449         StringUtils::ToUpper(subText);
450       if (currentStyle & FONT_STYLE_LOWERCASE)
451         StringUtils::ToLower(subText);
452       AppendToUTF32(subText, ((currentStyle & 3) << 24) | (currentColor << 16), parsedText);
453       if (newLine)
454         parsedText.push_back(L'\n');
455
456       // and switch to the new style
457       startPos = pos;
458       currentColor = newColor;
459       if (on)
460         currentStyle |= newStyle;
461       else
462         currentStyle &= ~newStyle;
463     }
464     pos = text.find(L'[', pos);
465   }
466   // now grab the remainder of the string
467   CStdStringW subText = text.substr(startPos);
468   if (currentStyle & FONT_STYLE_UPPERCASE)
469     StringUtils::ToUpper(subText);
470   if (currentStyle & FONT_STYLE_LOWERCASE)
471     StringUtils::ToLower(subText);
472   AppendToUTF32(subText, ((currentStyle & 3) << 24) | (currentColor << 16), parsedText);
473 }
474
475 void CGUITextLayout::SetMaxHeight(float fHeight)
476 {
477   m_maxHeight = fHeight;
478 }
479
480 void CGUITextLayout::WrapText(const vecText &text, float maxWidth)
481 {
482   if (!m_font)
483     return;
484
485   int nMaxLines = (m_maxHeight > 0 && m_font->GetLineHeight() > 0)?(int)ceilf(m_maxHeight / m_font->GetLineHeight()):-1;
486
487   m_lines.clear();
488
489   vector<CGUIString> lines;
490   LineBreakText(text, lines);
491
492   for (unsigned int i = 0; i < lines.size(); i++)
493   {
494     const CGUIString &line = lines[i];
495     vecText::const_iterator lastSpace = line.m_text.begin();
496     vecText::const_iterator pos = line.m_text.begin();
497     unsigned int lastSpaceInLine = 0;
498     vecText curLine;
499     while (pos != line.m_text.end())
500     {
501       // Get the current letter in the string
502       character_t letter = *pos;
503       // check for a space
504       if (CanWrapAtLetter(letter))
505       {
506         float width = m_font->GetTextWidth(curLine);
507         if (width > maxWidth)
508         {
509           if (lastSpace != line.m_text.begin() && lastSpaceInLine > 0)
510           {
511             CGUIString string(curLine.begin(), curLine.begin() + lastSpaceInLine, false);
512             m_lines.push_back(string);
513             // check for exceeding our number of lines
514             if (nMaxLines > 0 && m_lines.size() >= (size_t)nMaxLines)
515               return;
516             // skip over spaces
517             pos = lastSpace;
518             while (pos != line.m_text.end() && IsSpace(*pos))
519               ++pos;
520             curLine.clear();
521             lastSpaceInLine = 0;
522             lastSpace = line.m_text.begin();
523             continue;
524           }
525         }
526         lastSpace = pos;
527         lastSpaceInLine = curLine.size();
528       }
529       curLine.push_back(letter);
530       ++pos;
531     }
532     // now add whatever we have left to the string
533     float width = m_font->GetTextWidth(curLine);
534     if (width > maxWidth)
535     {
536       // too long - put up to the last space on if we can + remove it from what's left.
537       if (lastSpace != line.m_text.begin() && lastSpaceInLine > 0)
538       {
539         CGUIString string(curLine.begin(), curLine.begin() + lastSpaceInLine, false);
540         m_lines.push_back(string);
541         // check for exceeding our number of lines
542         if (nMaxLines > 0 && m_lines.size() >= (size_t)nMaxLines)
543           return;
544         curLine.erase(curLine.begin(), curLine.begin() + lastSpaceInLine);
545         while (curLine.size() && IsSpace(curLine.at(0)))
546           curLine.erase(curLine.begin());
547       }
548     }
549     CGUIString string(curLine.begin(), curLine.end(), true);
550     m_lines.push_back(string);
551     // check for exceeding our number of lines
552     if (nMaxLines > 0 && m_lines.size() >= (size_t)nMaxLines)
553       return;
554   }
555 }
556
557 void CGUITextLayout::LineBreakText(const vecText &text, vector<CGUIString> &lines)
558 {
559   int nMaxLines = (m_maxHeight > 0 && m_font && m_font->GetLineHeight() > 0)?(int)ceilf(m_maxHeight / m_font->GetLineHeight()):-1;
560   vecText::const_iterator lineStart = text.begin();
561   vecText::const_iterator pos = text.begin();
562   while (pos != text.end() && (nMaxLines <= 0 || lines.size() < (size_t)nMaxLines))
563   {
564     // Get the current letter in the string
565     character_t letter = *pos;
566
567     // Handle the newline character
568     if ((letter & 0xffff) == L'\n' )
569     { // push back everything up till now
570       CGUIString string(lineStart, pos, true);
571       lines.push_back(string);
572       lineStart = pos + 1;
573     }
574     ++pos;
575   }
576   // handle the last line if non-empty
577   if (lineStart < text.end() && (nMaxLines <= 0 || lines.size() < (size_t)nMaxLines))
578   {
579     CGUIString string(lineStart, text.end(), true);
580     lines.push_back(string);
581   }
582 }
583
584 void CGUITextLayout::GetTextExtent(float &width, float &height) const
585 {
586   width = m_textWidth;
587   height = m_textHeight;
588 }
589
590 void CGUITextLayout::CalcTextExtent()
591 {
592   m_textWidth = 0;
593   m_textHeight = 0;
594   if (!m_font) return;
595
596   for (vector<CGUIString>::iterator i = m_lines.begin(); i != m_lines.end(); ++i)
597   {
598     const CGUIString &string = *i;
599     float w = m_font->GetTextWidth(string.m_text);
600     if (w > m_textWidth)
601       m_textWidth = w;
602   }
603   m_textHeight = m_font->GetTextHeight(m_lines.size());
604 }
605
606 unsigned int CGUITextLayout::GetTextLength() const
607 {
608   unsigned int length = 0;
609   for (vector<CGUIString>::const_iterator i = m_lines.begin(); i != m_lines.end(); ++i)
610     length += i->m_text.size();
611   return length;
612 }
613
614 void CGUITextLayout::GetFirstText(vecText &text) const
615 {
616   text.clear();
617   if (m_lines.size())
618     text = m_lines[0].m_text;
619 }
620
621 float CGUITextLayout::GetTextWidth(const CStdStringW &text) const
622 {
623   // NOTE: Assumes a single line of text
624   if (!m_font) return 0;
625   vecText utf32;
626   AppendToUTF32(text, (m_font->GetStyle() & 3) << 24, utf32);
627   return m_font->GetTextWidth(utf32);
628 }
629
630 void CGUITextLayout::DrawText(CGUIFont *font, float x, float y, color_t color, color_t shadowColor, const CStdString &text, uint32_t align)
631 {
632   if (!font) return;
633   vecText utf32;
634   AppendToUTF32(text, 0, utf32);
635   font->DrawText(x, y, color, shadowColor, utf32, align, 0);
636 }
637
638 void CGUITextLayout::AppendToUTF32(const CStdStringW &utf16, character_t colStyle, vecText &utf32)
639 {
640   // NOTE: Assumes a single line of text
641   utf32.reserve(utf32.size() + utf16.size());
642   for (unsigned int i = 0; i < utf16.size(); i++)
643     utf32.push_back(utf16[i] | colStyle);
644 }
645
646 void CGUITextLayout::utf8ToW(const CStdString &utf8, CStdStringW &utf16)
647 {
648 #ifdef WORK_AROUND_NEEDED_FOR_LINE_BREAKS
649   // NOTE: This appears to strip \n characters from text.  This may be a consequence of incorrect
650   //       expression of the \n in utf8 (we just use character code 10) or it might be something
651   //       more sinister.  For now, we use the workaround below.
652   CStdStringArray multiLines;
653   StringUtils::SplitString(utf8, "\n", multiLines);
654   for (unsigned int i = 0; i < multiLines.size(); i++)
655   {
656     CStdStringW line;
657     // no need to bidiflip here - it's done in BidiTransform above
658     g_charsetConverter.utf8ToW(multiLines[i], line, false);
659     utf16 += line;
660     if (i < multiLines.size() - 1)
661       utf16.push_back(L'\n');
662   }
663 #else
664   // no need to bidiflip here - it's done in BidiTransform above
665   g_charsetConverter.utf8ToW(utf8, utf16, false);
666 #endif
667 }
668
669 void CGUITextLayout::AppendToUTF32(const CStdString &utf8, character_t colStyle, vecText &utf32)
670 {
671   CStdStringW utf16;
672   utf8ToW(utf8, utf16);
673   AppendToUTF32(utf16, colStyle, utf32);
674 }
675
676 void CGUITextLayout::Reset()
677 {
678   m_lines.clear();
679   m_lastText.clear();
680   m_lastUtf8Text.clear();
681   m_textWidth = m_textHeight = 0;
682 }
683
684