[cstdstring] drops Mid(), replacing with substr()
[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   // convert to utf16
221   CStdStringW utf16;
222   utf8ToW(text, utf16);
223
224   // update
225   return UpdateW(utf16, maxWidth, forceUpdate, forceLTRReadingOrder);
226 }
227
228 bool CGUITextLayout::UpdateW(const CStdStringW &text, float maxWidth /*= 0*/, bool forceUpdate /*= false*/, bool forceLTRReadingOrder /*= false*/)
229 {
230   if (text.Equals(m_lastText) && !forceUpdate)
231     return false;
232
233   // parse the text for style information
234   vecText parsedText;
235   vecColors colors;
236   ParseText(text, m_font ? m_font->GetStyle() : 0, m_textColor, colors, parsedText);
237
238   // and update
239   UpdateStyled(parsedText, colors, maxWidth, forceLTRReadingOrder);
240   m_lastText = text;
241   return true;
242 }
243
244 void CGUITextLayout::UpdateStyled(const vecText &text, const vecColors &colors, float maxWidth, bool forceLTRReadingOrder)
245 {
246   // empty out our previous string
247   m_lines.clear();
248   m_colors = colors;
249
250   // if we need to wrap the text, then do so
251   if (m_wrap && maxWidth > 0)
252     WrapText(text, maxWidth);
253   else
254     LineBreakText(text, m_lines);
255
256   // remove any trailing blank lines
257   while (!m_lines.empty() && m_lines.back().m_text.empty())
258     m_lines.pop_back();
259
260   BidiTransform(m_lines, forceLTRReadingOrder);
261
262   // and cache the width and height for later reading
263   CalcTextExtent();
264 }
265
266 // BidiTransform is used to handle RTL text flipping in the string
267 void CGUITextLayout::BidiTransform(vector<CGUIString> &lines, bool forceLTRReadingOrder)
268 {
269   for (unsigned int i=0; i<lines.size(); i++)
270   {
271     CGUIString &line = lines[i];
272
273     // reserve enough space in the flipped text
274     vecText flippedText;
275     flippedText.reserve(line.m_text.size());
276
277     character_t sectionStyle = 0xffff0000; // impossible to achieve
278     CStdStringW sectionText;
279     for (vecText::iterator it = line.m_text.begin(); it != line.m_text.end(); ++it)
280     {
281       character_t style = *it & 0xffff0000;
282       if (style != sectionStyle)
283       {
284         if (!sectionText.empty())
285         { // style has changed, bidi flip text
286           CStdStringW sectionFlipped = BidiFlip(sectionText, forceLTRReadingOrder);
287           for (unsigned int j = 0; j < sectionFlipped.size(); j++)
288             flippedText.push_back(sectionStyle | sectionFlipped[j]);
289         }
290         sectionStyle = style;
291         sectionText.clear();
292       }
293       sectionText.push_back( (wchar_t)(*it & 0xffff) );
294     }
295
296     // handle the last section
297     if (!sectionText.empty())
298     {
299       CStdStringW sectionFlipped = BidiFlip(sectionText, forceLTRReadingOrder);
300       for (unsigned int j = 0; j < sectionFlipped.size(); j++)
301         flippedText.push_back(sectionStyle | sectionFlipped[j]);
302     }
303
304     // replace the original line with the proccessed one
305     lines[i] = CGUIString(flippedText.begin(), flippedText.end(), line.m_carriageReturn);
306   }
307 }
308
309 CStdStringW CGUITextLayout::BidiFlip(const CStdStringW &text, bool forceLTRReadingOrder)
310 {
311   CStdStringA utf8text;
312   CStdStringW visualText;
313
314   // convert to utf8, and back to utf16 with bidi flipping
315   g_charsetConverter.wToUTF8(text, utf8text);
316   g_charsetConverter.utf8ToW(utf8text, visualText, true, forceLTRReadingOrder);
317
318   return visualText;
319 }
320
321 void CGUITextLayout::Filter(CStdString &text)
322 {
323   CStdStringW utf16;
324   utf8ToW(text, utf16);
325   vecColors colors;
326   vecText parsedText;
327   ParseText(utf16, 0, 0xffffffff, colors, parsedText);
328   utf16.clear();
329   for (unsigned int i = 0; i < parsedText.size(); i++)
330     utf16 += (wchar_t)(0xffff & parsedText[i]);
331   g_charsetConverter.wToUTF8(utf16, text);
332 }
333
334 void CGUITextLayout::ParseText(const CStdStringW &text, uint32_t defaultStyle, color_t defaultColor, vecColors &colors, vecText &parsedText)
335 {
336   // run through the string, searching for:
337   // [B] or [/B] -> toggle bold on and off
338   // [I] or [/I] -> toggle italics on and off
339   // [COLOR ffab007f] or [/COLOR] -> toggle color on and off
340   // [CAPS <option>] or [/CAPS] -> toggle capatilization on and off
341
342   uint32_t currentStyle = defaultStyle; // start with the default font's style
343   color_t currentColor = 0;
344
345   colors.push_back(defaultColor);
346   stack<color_t> colorStack;
347   colorStack.push(0);
348
349   // these aren't independent, but that's probably not too much of an issue
350   // eg [UPPERCASE]Glah[LOWERCASE]FReD[/LOWERCASE]Georeg[/UPPERCASE] will work (lower case >> upper case)
351   // but [LOWERCASE]Glah[UPPERCASE]FReD[/UPPERCASE]Georeg[/LOWERCASE] won't
352
353   int startPos = 0;
354   size_t pos = text.find(L'[');
355   while (pos != std::string::npos && pos + 1 < text.size())
356   {
357     uint32_t newStyle = 0;
358     color_t newColor = currentColor;
359     bool colorTagChange = false;
360     bool newLine = false;
361     // have a [ - check if it's an ON or OFF switch
362     bool on(true);
363     size_t endPos = pos++; // finish of string
364     if (text[pos] == L'/')
365     {
366       on = false;
367       pos++;
368     }
369     // check for each type
370     if (text.compare(pos, 2, L"B]") == 0)
371     { // bold - finish the current text block and assign the bold state
372       pos += 2;
373       if ((on && text.find(L"[/B]",pos) != std::string::npos) ||          // check for a matching end point
374          (!on && (currentStyle & FONT_STYLE_BOLD)))       // or matching start point
375         newStyle = FONT_STYLE_BOLD;
376     }
377     else if (text.compare(pos, 2, L"I]") == 0)
378     { // italics
379       pos += 2;
380       if ((on && text.find(L"[/I]", pos) != std::string::npos) ||          // check for a matching end point
381          (!on && (currentStyle & FONT_STYLE_ITALICS)))    // or matching start point
382         newStyle = FONT_STYLE_ITALICS;
383     }
384     else if (text.compare(pos, 10, L"UPPERCASE]") == 0)
385     {
386       pos += 10;
387       if ((on && text.find(L"[/UPPERCASE]", pos) != std::string::npos) ||  // check for a matching end point
388          (!on && (currentStyle & FONT_STYLE_UPPERCASE)))  // or matching start point
389         newStyle = FONT_STYLE_UPPERCASE;
390     }
391     else if (text.compare(pos, 10, L"LOWERCASE]") == 0)
392     {
393       pos += 10;
394       if ((on && text.find(L"[/LOWERCASE]", pos) != std::string::npos) ||  // check for a matching end point
395          (!on && (currentStyle & FONT_STYLE_LOWERCASE)))  // or matching start point
396         newStyle = FONT_STYLE_LOWERCASE;
397     }
398     else if (text.compare(pos, 3, L"CR]") == 0 && on)
399     {
400       newLine = true;
401       pos += 3;
402     }
403     else if (text.compare(pos,5, L"COLOR") == 0)
404     { // color
405       size_t finish = text.find(L']', pos + 5);
406       if (on && finish != std::string::npos && text.find(L"[/COLOR]",finish) != std::string::npos)
407       {
408         color_t color = g_colorManager.GetColor(text.substr(pos + 5, finish - pos - 5));
409         vecColors::const_iterator it = std::find(colors.begin(), colors.end(), color);
410         if (it == colors.end())
411         { // create new color
412           if (colors.size() <= 0xFF)
413           {
414             newColor = colors.size();
415             colors.push_back(color);
416           }
417           else // we have only 8 bits for color index, fallback to first color if reach max.
418             newColor = 0;
419         }
420         else
421           // reuse existing color
422           newColor = it - colors.begin();
423         colorStack.push(newColor);
424         colorTagChange = true;
425       }
426       else if (!on && finish == pos + 5 && colorStack.size() > 1)
427       { // revert to previous color
428         colorStack.pop();
429         newColor = colorStack.top();
430         colorTagChange = true;
431       }
432       if (finish != CStdString::npos)
433         pos = finish + 1;
434     }
435
436     if (newStyle || colorTagChange || newLine)
437     { // we have a new style or a new color, so format up the previous segment
438       CStdStringW subText = text.substr(startPos, endPos - startPos);
439       if (currentStyle & FONT_STYLE_UPPERCASE)
440         StringUtils::ToUpper(subText);
441       if (currentStyle & FONT_STYLE_LOWERCASE)
442         StringUtils::ToLower(subText);
443       AppendToUTF32(subText, ((currentStyle & 3) << 24) | (currentColor << 16), parsedText);
444       if (newLine)
445         parsedText.push_back(L'\n');
446
447       // and switch to the new style
448       startPos = pos;
449       currentColor = newColor;
450       if (on)
451         currentStyle |= newStyle;
452       else
453         currentStyle &= ~newStyle;
454     }
455     pos = text.Find(L'[',pos);
456   }
457   // now grab the remainder of the string
458   CStdStringW subText = text.substr(startPos);
459   if (currentStyle & FONT_STYLE_UPPERCASE)
460     StringUtils::ToUpper(subText);
461   if (currentStyle & FONT_STYLE_LOWERCASE)
462     StringUtils::ToLower(subText);
463   AppendToUTF32(subText, ((currentStyle & 3) << 24) | (currentColor << 16), parsedText);
464 }
465
466 void CGUITextLayout::SetMaxHeight(float fHeight)
467 {
468   m_maxHeight = fHeight;
469 }
470
471 void CGUITextLayout::WrapText(const vecText &text, float maxWidth)
472 {
473   if (!m_font)
474     return;
475
476   int nMaxLines = (m_maxHeight > 0 && m_font->GetLineHeight() > 0)?(int)ceilf(m_maxHeight / m_font->GetLineHeight()):-1;
477
478   m_lines.clear();
479
480   vector<CGUIString> lines;
481   LineBreakText(text, lines);
482
483   for (unsigned int i = 0; i < lines.size(); i++)
484   {
485     const CGUIString &line = lines[i];
486     vecText::const_iterator lastSpace = line.m_text.begin();
487     vecText::const_iterator pos = line.m_text.begin();
488     unsigned int lastSpaceInLine = 0;
489     vecText curLine;
490     while (pos != line.m_text.end())
491     {
492       // Get the current letter in the string
493       character_t letter = *pos;
494       // check for a space
495       if (CanWrapAtLetter(letter))
496       {
497         float width = m_font->GetTextWidth(curLine);
498         if (width > maxWidth)
499         {
500           if (lastSpace != line.m_text.begin() && lastSpaceInLine > 0)
501           {
502             CGUIString string(curLine.begin(), curLine.begin() + lastSpaceInLine, false);
503             m_lines.push_back(string);
504             // check for exceeding our number of lines
505             if (nMaxLines > 0 && m_lines.size() >= (size_t)nMaxLines)
506               return;
507             // skip over spaces
508             pos = lastSpace;
509             while (pos != line.m_text.end() && IsSpace(*pos))
510               ++pos;
511             curLine.clear();
512             lastSpaceInLine = 0;
513             lastSpace = line.m_text.begin();
514             continue;
515           }
516         }
517         lastSpace = pos;
518         lastSpaceInLine = curLine.size();
519       }
520       curLine.push_back(letter);
521       ++pos;
522     }
523     // now add whatever we have left to the string
524     float width = m_font->GetTextWidth(curLine);
525     if (width > maxWidth)
526     {
527       // too long - put up to the last space on if we can + remove it from what's left.
528       if (lastSpace != line.m_text.begin() && lastSpaceInLine > 0)
529       {
530         CGUIString string(curLine.begin(), curLine.begin() + lastSpaceInLine, false);
531         m_lines.push_back(string);
532         // check for exceeding our number of lines
533         if (nMaxLines > 0 && m_lines.size() >= (size_t)nMaxLines)
534           return;
535         curLine.erase(curLine.begin(), curLine.begin() + lastSpaceInLine);
536         while (curLine.size() && IsSpace(curLine.at(0)))
537           curLine.erase(curLine.begin());
538       }
539     }
540     CGUIString string(curLine.begin(), curLine.end(), true);
541     m_lines.push_back(string);
542     // check for exceeding our number of lines
543     if (nMaxLines > 0 && m_lines.size() >= (size_t)nMaxLines)
544       return;
545   }
546 }
547
548 void CGUITextLayout::LineBreakText(const vecText &text, vector<CGUIString> &lines)
549 {
550   int nMaxLines = (m_maxHeight > 0 && m_font && m_font->GetLineHeight() > 0)?(int)ceilf(m_maxHeight / m_font->GetLineHeight()):-1;
551   vecText::const_iterator lineStart = text.begin();
552   vecText::const_iterator pos = text.begin();
553   while (pos != text.end() && (nMaxLines <= 0 || lines.size() < (size_t)nMaxLines))
554   {
555     // Get the current letter in the string
556     character_t letter = *pos;
557
558     // Handle the newline character
559     if ((letter & 0xffff) == L'\n' )
560     { // push back everything up till now
561       CGUIString string(lineStart, pos, true);
562       lines.push_back(string);
563       lineStart = pos + 1;
564     }
565     ++pos;
566   }
567   // handle the last line if non-empty
568   if (lineStart < text.end() && (nMaxLines <= 0 || lines.size() < (size_t)nMaxLines))
569   {
570     CGUIString string(lineStart, text.end(), true);
571     lines.push_back(string);
572   }
573 }
574
575 void CGUITextLayout::GetTextExtent(float &width, float &height) const
576 {
577   width = m_textWidth;
578   height = m_textHeight;
579 }
580
581 void CGUITextLayout::CalcTextExtent()
582 {
583   m_textWidth = 0;
584   m_textHeight = 0;
585   if (!m_font) return;
586
587   for (vector<CGUIString>::iterator i = m_lines.begin(); i != m_lines.end(); ++i)
588   {
589     const CGUIString &string = *i;
590     float w = m_font->GetTextWidth(string.m_text);
591     if (w > m_textWidth)
592       m_textWidth = w;
593   }
594   m_textHeight = m_font->GetTextHeight(m_lines.size());
595 }
596
597 unsigned int CGUITextLayout::GetTextLength() const
598 {
599   unsigned int length = 0;
600   for (vector<CGUIString>::const_iterator i = m_lines.begin(); i != m_lines.end(); ++i)
601     length += i->m_text.size();
602   return length;
603 }
604
605 void CGUITextLayout::GetFirstText(vecText &text) const
606 {
607   text.clear();
608   if (m_lines.size())
609     text = m_lines[0].m_text;
610 }
611
612 float CGUITextLayout::GetTextWidth(const CStdStringW &text) const
613 {
614   // NOTE: Assumes a single line of text
615   if (!m_font) return 0;
616   vecText utf32;
617   AppendToUTF32(text, (m_font->GetStyle() & 3) << 24, utf32);
618   return m_font->GetTextWidth(utf32);
619 }
620
621 void CGUITextLayout::DrawText(CGUIFont *font, float x, float y, color_t color, color_t shadowColor, const CStdString &text, uint32_t align)
622 {
623   if (!font) return;
624   vecText utf32;
625   AppendToUTF32(text, 0, utf32);
626   font->DrawText(x, y, color, shadowColor, utf32, align, 0);
627 }
628
629 void CGUITextLayout::AppendToUTF32(const CStdStringW &utf16, character_t colStyle, vecText &utf32)
630 {
631   // NOTE: Assumes a single line of text
632   utf32.reserve(utf32.size() + utf16.size());
633   for (unsigned int i = 0; i < utf16.size(); i++)
634     utf32.push_back(utf16[i] | colStyle);
635 }
636
637 void CGUITextLayout::utf8ToW(const CStdString &utf8, CStdStringW &utf16)
638 {
639 #ifdef WORK_AROUND_NEEDED_FOR_LINE_BREAKS
640   // NOTE: This appears to strip \n characters from text.  This may be a consequence of incorrect
641   //       expression of the \n in utf8 (we just use character code 10) or it might be something
642   //       more sinister.  For now, we use the workaround below.
643   CStdStringArray multiLines;
644   StringUtils::SplitString(utf8, "\n", multiLines);
645   for (unsigned int i = 0; i < multiLines.size(); i++)
646   {
647     CStdStringW line;
648     // no need to bidiflip here - it's done in BidiTransform above
649     g_charsetConverter.utf8ToW(multiLines[i], line, false);
650     utf16 += line;
651     if (i < multiLines.size() - 1)
652       utf16.push_back(L'\n');
653   }
654 #else
655   // no need to bidiflip here - it's done in BidiTransform above
656   g_charsetConverter.utf8ToW(utf8, utf16, false);
657 #endif
658 }
659
660 void CGUITextLayout::AppendToUTF32(const CStdString &utf8, character_t colStyle, vecText &utf32)
661 {
662   CStdStringW utf16;
663   utf8ToW(utf8, utf16);
664   AppendToUTF32(utf16, colStyle, utf32);
665 }
666
667 void CGUITextLayout::Reset()
668 {
669   m_lines.clear();
670   m_lastText.clear();
671   m_textWidth = m_textHeight = 0;
672 }
673
674