2014-01-17 03:54:57 +01:00
|
|
|
/* Copyright (C) 2014 Wildfire Games.
|
2009-04-18 19:00:33 +02:00
|
|
|
* This file is part of 0 A.D.
|
|
|
|
*
|
|
|
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* 0 A.D. is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2004-06-03 20:38:14 +02:00
|
|
|
#include "precompiled.h"
|
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
#include <algorithm>
|
|
|
|
|
2004-05-29 06:06:50 +02:00
|
|
|
#include "GUI.h"
|
2014-11-16 03:10:28 +01:00
|
|
|
#include "lib/utf8.h"
|
2013-10-18 17:53:07 +02:00
|
|
|
#include "graphics/FontMetrics.h"
|
2006-06-02 04:10:27 +02:00
|
|
|
#include "ps/CLogger.h"
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2004-06-11 04:20:59 +02:00
|
|
|
|
2014-03-17 11:13:49 +01:00
|
|
|
// List of word demlimitor bounds
|
|
|
|
// The list contains ranges of word delimitors. The odd indexed chars are the start
|
|
|
|
// of a range, the even are the end of a range. The list must be sorted in INCREASING ORDER
|
|
|
|
static const int NUM_WORD_DELIMITORS = 4*2;
|
|
|
|
static const u16 WordDelimitors[NUM_WORD_DELIMITORS] = {
|
|
|
|
' ' , ' ', // spaces
|
|
|
|
'-' , '-', // hyphens
|
2014-05-09 14:13:42 +02:00
|
|
|
0x3000, 0x31FF, // ideographic symbols
|
|
|
|
0x3400, 0x9FFF
|
2014-03-17 11:13:49 +01:00
|
|
|
// TODO add unicode blocks of other languages that don't use spaces
|
|
|
|
};
|
2004-05-29 06:06:50 +02:00
|
|
|
|
|
|
|
void CGUIString::SFeedback::Reset()
|
|
|
|
{
|
|
|
|
m_Images[Left].clear();
|
|
|
|
m_Images[Right].clear();
|
|
|
|
m_TextCalls.clear();
|
|
|
|
m_SpriteCalls.clear();
|
|
|
|
m_Size = CSize();
|
|
|
|
m_NewLine=false;
|
|
|
|
}
|
|
|
|
|
2014-01-17 03:54:57 +01:00
|
|
|
void CGUIString::GenerateTextCall(const CGUI *pGUI,
|
|
|
|
SFeedback &Feedback,
|
2013-10-18 18:15:42 +02:00
|
|
|
CStrIntern DefaultFont,
|
2004-09-04 22:35:12 +02:00
|
|
|
const int &from, const int &to,
|
|
|
|
const bool FirstLine,
|
2004-08-31 04:09:58 +02:00
|
|
|
const IGUIObject *pObject) const
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
|
|
|
// Reset width and height, because they will be determined with incrementation
|
|
|
|
// or comparisons.
|
|
|
|
Feedback.Reset();
|
|
|
|
|
|
|
|
// Check out which text chunk this is within.
|
|
|
|
//bool match_found = false;
|
2008-07-13 23:22:03 +02:00
|
|
|
std::vector<TextChunk>::const_iterator itTextChunk;
|
2004-05-29 06:06:50 +02:00
|
|
|
for (itTextChunk=m_TextChunks.begin(); itTextChunk!=m_TextChunks.end(); ++itTextChunk)
|
|
|
|
{
|
|
|
|
// Get the area that is overlapped by both the TextChunk and
|
|
|
|
// by the from/to inputted.
|
|
|
|
int _from, _to;
|
2008-07-13 23:22:03 +02:00
|
|
|
_from = std::max(from, itTextChunk->m_From);
|
|
|
|
_to = std::min(to, itTextChunk->m_To);
|
2013-09-14 01:49:46 +02:00
|
|
|
|
2004-05-29 06:06:50 +02:00
|
|
|
// If from is larger than to, than they are not overlapping
|
2004-08-31 04:09:58 +02:00
|
|
|
if (_to == _from && itTextChunk->m_From == itTextChunk->m_To)
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
|
|
|
// These should never be able to have more than one tag.
|
2011-04-30 15:01:45 +02:00
|
|
|
ENSURE(itTextChunk->m_Tags.size()==1);
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2004-08-31 04:09:58 +02:00
|
|
|
// Now do second check
|
|
|
|
// because icons and images are placed on exactly one position
|
|
|
|
// in the words-list, it can be counted twice if placed on an
|
|
|
|
// edge. But there is always only one logical preference that
|
|
|
|
// we want. This check filters the unwanted.
|
2013-09-14 01:49:46 +02:00
|
|
|
|
|
|
|
// it's in the end of one word, and the icon
|
2004-08-31 04:09:58 +02:00
|
|
|
// should really belong to the beginning of the next one
|
2004-09-02 05:02:32 +02:00
|
|
|
if (_to == to && to >= 1)
|
2004-08-31 04:09:58 +02:00
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
if (m_RawString[to-1] == ' ' ||
|
|
|
|
m_RawString[to-1] == '-' ||
|
|
|
|
m_RawString[to-1] == '\n')
|
2004-08-31 04:09:58 +02:00
|
|
|
continue;
|
|
|
|
}
|
2008-07-13 23:22:03 +02:00
|
|
|
// This std::string is just a break
|
2004-08-31 04:09:58 +02:00
|
|
|
if (_from == from && from >= 1)
|
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
if (m_RawString[from] == '\n' &&
|
|
|
|
m_RawString[from-1] != '\n' &&
|
|
|
|
m_RawString[from-1] != ' ' &&
|
|
|
|
m_RawString[from-1] != '-')
|
2004-08-31 04:09:58 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
const CGUIString::TextChunk::Tag& tag = itTextChunk->m_Tags[0];
|
|
|
|
ENSURE(tag.m_TagType == CGUIString::TextChunk::Tag::TAG_IMGLEFT
|
|
|
|
|| tag.m_TagType == CGUIString::TextChunk::Tag::TAG_IMGRIGHT
|
|
|
|
|| tag.m_TagType == CGUIString::TextChunk::Tag::TAG_ICON);
|
|
|
|
|
|
|
|
const std::string& path = utf8_from_wstring(tag.m_TagValue);
|
|
|
|
if (!pGUI->IconExists(path))
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
if (pObject)
|
|
|
|
LOGERROR(L"Trying to use an icon, imgleft or imgright-tag with an undefined icon (\"%hs\").", path.c_str());
|
|
|
|
continue;
|
2004-05-29 06:06:50 +02:00
|
|
|
}
|
2014-11-16 03:10:28 +01:00
|
|
|
|
|
|
|
switch (tag.m_TagType)
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
case CGUIString::TextChunk::Tag::TAG_IMGLEFT:
|
|
|
|
Feedback.m_Images[SFeedback::Left].push_back(path);
|
|
|
|
break;
|
|
|
|
case CGUIString::TextChunk::Tag::TAG_IMGRIGHT:
|
|
|
|
Feedback.m_Images[SFeedback::Right].push_back(path);
|
|
|
|
break;
|
2004-08-31 04:09:58 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
case CGUIString::TextChunk::Tag::TAG_ICON:
|
|
|
|
// We'll need to setup a text-call that will point
|
|
|
|
// to the icon, this is to be able to iterate
|
|
|
|
// through the text-calls without having to
|
|
|
|
// complex the structure virtually for nothing more.
|
|
|
|
SGUIText::STextCall TextCall;
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
// Also add it to the sprites being rendered.
|
|
|
|
SGUIText::SSpriteCall SpriteCall;
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
// Get Icon from icon database in pGUI
|
|
|
|
SGUIIcon icon = pGUI->GetIcon(path);
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
CSize size = icon.m_Size;
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
// append width, and make maximum height the height.
|
|
|
|
Feedback.m_Size.cx += size.cx;
|
|
|
|
Feedback.m_Size.cy = std::max(Feedback.m_Size.cy, size.cy);
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
// These are also needed later
|
|
|
|
TextCall.m_Size = size;
|
|
|
|
SpriteCall.m_Area = size;
|
2010-10-30 06:02:42 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
// Handle additional attributes
|
|
|
|
std::vector<TextChunk::Tag::TagAttribute>::const_iterator att_it;
|
|
|
|
for (att_it = tag.m_TagAttributes.begin(); att_it != tag.m_TagAttributes.end(); ++att_it)
|
|
|
|
{
|
|
|
|
const TextChunk::Tag::TagAttribute& tagAttrib = *att_it;
|
2010-10-30 06:02:42 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
if (tagAttrib.attrib == L"displace" && !tagAttrib.value.empty())
|
|
|
|
{
|
|
|
|
//Displace the sprite
|
|
|
|
CSize displacement;
|
|
|
|
// Parse the value
|
|
|
|
if (!GUI<CSize>::ParseString(tagAttrib.value, displacement))
|
|
|
|
LOGERROR(L"Error parsing 'displace' value for tag [ICON]");
|
|
|
|
else
|
|
|
|
SpriteCall.m_Area += displacement;
|
2004-10-14 04:32:26 +02:00
|
|
|
}
|
2014-12-13 02:08:29 +01:00
|
|
|
else if (tagAttrib.attrib == L"tooltip")
|
2014-11-16 03:10:28 +01:00
|
|
|
SpriteCall.m_Tooltip = tagAttrib.value;
|
2014-12-13 02:08:29 +01:00
|
|
|
else if (tagAttrib.attrib == L"tooltip_style")
|
2014-11-16 03:10:28 +01:00
|
|
|
SpriteCall.m_TooltipStyle = tagAttrib.value;
|
|
|
|
}
|
2004-10-14 04:32:26 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
SpriteCall.m_Sprite = icon.m_SpriteName;
|
|
|
|
SpriteCall.m_CellID = icon.m_CellID;
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
// Add sprite call
|
|
|
|
Feedback.m_SpriteCalls.push_back(SpriteCall);
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
// Finalize text call
|
|
|
|
TextCall.m_pSpriteCall = &Feedback.m_SpriteCalls.back();
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
// Add text call
|
|
|
|
Feedback.m_TextCalls.push_back(TextCall);
|
|
|
|
break;
|
2004-05-29 06:06:50 +02:00
|
|
|
}
|
|
|
|
}
|
2014-11-16 03:10:28 +01:00
|
|
|
else if (_to > _from && !Feedback.m_NewLine)
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
|
|
|
SGUIText::STextCall TextCall;
|
|
|
|
|
|
|
|
// Set defaults
|
|
|
|
TextCall.m_Font = DefaultFont;
|
|
|
|
TextCall.m_UseCustomColor = false;
|
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
TextCall.m_String = m_RawString.substr(_from, _to-_from);
|
2013-09-14 01:49:46 +02:00
|
|
|
|
2004-05-29 06:06:50 +02:00
|
|
|
// Go through tags and apply changes.
|
2008-07-13 23:22:03 +02:00
|
|
|
std::vector<CGUIString::TextChunk::Tag>::const_iterator it2;
|
2004-05-29 06:06:50 +02:00
|
|
|
for (it2 = itTextChunk->m_Tags.begin(); it2 != itTextChunk->m_Tags.end(); ++it2)
|
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
switch (it2->m_TagType)
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
case CGUIString::TextChunk::Tag::TAG_COLOR:
|
2004-05-29 06:06:50 +02:00
|
|
|
TextCall.m_UseCustomColor = true;
|
2013-09-14 01:49:46 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
if (!GUI<CColor>::ParseString(it2->m_TagValue, TextCall.m_Color)
|
|
|
|
&& pObject)
|
|
|
|
LOGERROR(L"Error parsing the value of a [color]-tag in GUI text when reading object \"%hs\".", pObject->GetPresentableName().c_str());
|
|
|
|
break;
|
|
|
|
case CGUIString::TextChunk::Tag::TAG_FONT:
|
2004-08-31 04:09:58 +02:00
|
|
|
// TODO Gee: (2004-08-15) Check if Font exists?
|
2014-11-16 03:10:28 +01:00
|
|
|
TextCall.m_Font = CStrIntern(utf8_from_wstring(it2->m_TagValue));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
LOGERROR(L"Encountered unexpected tag applied to text");
|
|
|
|
break;
|
2004-05-29 06:06:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2004-08-28 00:08:30 +02:00
|
|
|
// Calculate the size of the font
|
2004-05-29 06:06:50 +02:00
|
|
|
CSize size;
|
2004-09-03 07:48:47 +02:00
|
|
|
int cx, cy;
|
2013-10-18 17:53:07 +02:00
|
|
|
CFontMetrics font (TextCall.m_Font);
|
2012-02-25 18:14:47 +01:00
|
|
|
font.CalculateStringSize(TextCall.m_String.c_str(), cx, cy);
|
2004-09-04 22:35:12 +02:00
|
|
|
// For anything other than the first line, the line spacing
|
|
|
|
// needs to be considered rather than just the height of the text
|
2014-11-16 03:10:28 +01:00
|
|
|
if (!FirstLine)
|
2004-09-04 22:35:12 +02:00
|
|
|
cy = font.GetLineSpacing();
|
2004-09-03 07:48:47 +02:00
|
|
|
|
|
|
|
size.cx = (float)cx;
|
|
|
|
size.cy = (float)cy;
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2004-08-28 00:08:30 +02:00
|
|
|
// Append width, and make maximum height the height.
|
2004-05-29 06:06:50 +02:00
|
|
|
Feedback.m_Size.cx += size.cx;
|
2008-07-13 23:22:03 +02:00
|
|
|
Feedback.m_Size.cy = std::max(Feedback.m_Size.cy, size.cy);
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2004-09-06 04:21:21 +02:00
|
|
|
// These are also needed later
|
2004-05-29 06:06:50 +02:00
|
|
|
TextCall.m_Size = size;
|
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
if (!TextCall.m_String.empty() && TextCall.m_String[0] == '\n')
|
|
|
|
Feedback.m_NewLine = true;
|
2004-05-29 06:06:50 +02:00
|
|
|
|
|
|
|
// Add text-chunk
|
|
|
|
Feedback.m_TextCalls.push_back(TextCall);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
bool CGUIString::TextChunk::Tag::SetTagType(const CStrW& tagtype)
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
TagType t = GetTagType(tagtype);
|
|
|
|
if (t == TAG_INVALID)
|
|
|
|
return false;
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
m_TagType = t;
|
|
|
|
return true;
|
|
|
|
}
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
CGUIString::TextChunk::Tag::TagType CGUIString::TextChunk::Tag::GetTagType(const CStrW& tagtype)
|
|
|
|
{
|
|
|
|
if (tagtype == L"color")
|
|
|
|
return TAG_COLOR;
|
|
|
|
if (tagtype == L"font")
|
|
|
|
return TAG_FONT;
|
|
|
|
if (tagtype == L"icon")
|
|
|
|
return TAG_ICON;
|
|
|
|
if (tagtype == L"imgleft")
|
|
|
|
return TAG_IMGLEFT;
|
|
|
|
if (tagtype == L"imgright")
|
|
|
|
return TAG_IMGRIGHT;
|
|
|
|
|
|
|
|
return TAG_INVALID;
|
2004-05-29 06:06:50 +02:00
|
|
|
}
|
|
|
|
|
2004-09-06 04:21:21 +02:00
|
|
|
void CGUIString::SetValue(const CStrW& str)
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
2011-02-16 21:40:15 +01:00
|
|
|
m_OriginalString = str;
|
|
|
|
|
2004-05-29 06:06:50 +02:00
|
|
|
m_TextChunks.clear();
|
|
|
|
m_Words.clear();
|
2014-11-16 03:10:28 +01:00
|
|
|
m_RawString.clear();
|
2004-05-29 06:06:50 +02:00
|
|
|
|
|
|
|
// Current Text Chunk
|
|
|
|
CGUIString::TextChunk CurrentTextChunk;
|
2014-11-16 03:10:28 +01:00
|
|
|
CurrentTextChunk.m_From = 0;
|
|
|
|
|
|
|
|
int l = str.length();
|
|
|
|
int rawpos = 0;
|
|
|
|
CStrW tag;
|
|
|
|
std::vector<CStrW> tags;
|
|
|
|
bool closing = false;
|
|
|
|
for (int p = 0; p < l; ++p)
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
TextChunk::Tag tag_;
|
|
|
|
switch (str[p])
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
case L'[':
|
|
|
|
CurrentTextChunk.m_To = rawpos;
|
|
|
|
// Add the current chunks if it is not empty
|
|
|
|
if (CurrentTextChunk.m_From != rawpos)
|
2004-05-29 06:06:50 +02:00
|
|
|
m_TextChunks.push_back(CurrentTextChunk);
|
2014-11-16 03:10:28 +01:00
|
|
|
CurrentTextChunk.m_From = rawpos;
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
closing = false;
|
|
|
|
if (++p == l)
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
LOGERROR(L"Partial tag at end of string '%ls'", str.c_str());
|
|
|
|
break;
|
2004-05-29 06:06:50 +02:00
|
|
|
}
|
2014-11-16 03:10:28 +01:00
|
|
|
if (str[p] == L'/')
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
closing = true;
|
|
|
|
if (tags.empty())
|
|
|
|
{
|
|
|
|
LOGERROR(L"Encountered closing tag without having any open tags. At %d in '%ls'", p, str.c_str());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (++p == l)
|
|
|
|
{
|
|
|
|
LOGERROR(L"Partial closing tag at end of string '%ls'", str.c_str());
|
|
|
|
break;
|
|
|
|
}
|
2004-05-29 06:06:50 +02:00
|
|
|
}
|
2014-11-16 03:10:28 +01:00
|
|
|
tag.clear();
|
|
|
|
// Parse tag
|
|
|
|
for (; p < l && str[p] != L']'; ++p)
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
CStrW name, param;
|
|
|
|
switch (str[p])
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
case L' ':
|
|
|
|
if (closing) // We still parse them to make error handling cleaner
|
|
|
|
LOGERROR(L"Closing tags do not support parameters (at pos %d '%ls')", p, str.c_str());
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
// parse something="something else"
|
|
|
|
for (++p; p < l && str[p] != L'='; ++p)
|
|
|
|
name.push_back(str[p]);
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
if (p == l)
|
|
|
|
{
|
|
|
|
LOGERROR(L"Parameter without value at pos %d '%ls'", p, str.c_str());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// fall-through
|
|
|
|
case L'=':
|
|
|
|
// parse a quoted parameter
|
|
|
|
if (closing) // We still parse them to make error handling cleaner
|
|
|
|
LOGERROR(L"Closing tags do not support parameters (at pos %d '%ls')", p, str.c_str());
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
if (++p == l)
|
|
|
|
{
|
|
|
|
LOGERROR(L"Expected parameter, got end of string '%ls'", str.c_str());
|
|
|
|
break;
|
2004-05-29 06:06:50 +02:00
|
|
|
}
|
2014-11-16 03:10:28 +01:00
|
|
|
if (str[p] != L'"')
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
LOGERROR(L"Unquoted parameters are not supported (at pos %d '%ls')", p, str.c_str());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
for (++p; p < l && str[p] != L'"'; ++p)
|
|
|
|
{
|
|
|
|
switch (str[p])
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
case L'\\':
|
|
|
|
if (++p == l)
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
LOGERROR(L"Escape character at end of string '%ls'", str.c_str());
|
|
|
|
break;
|
2004-05-29 06:06:50 +02:00
|
|
|
}
|
2014-12-13 02:08:29 +01:00
|
|
|
// NOTE: We do not support \n in tag parameters
|
2014-11-16 03:10:28 +01:00
|
|
|
// fall-through
|
|
|
|
default:
|
|
|
|
param.push_back(str[p]);
|
2004-05-29 06:06:50 +02:00
|
|
|
}
|
|
|
|
}
|
2014-11-16 03:10:28 +01:00
|
|
|
|
|
|
|
if (!name.empty())
|
|
|
|
{
|
|
|
|
TextChunk::Tag::TagAttribute a = {name, param};
|
|
|
|
tag_.m_TagAttributes.push_back(a);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
tag_.m_TagValue = param;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
tag.push_back(str[p]);
|
|
|
|
break;
|
2004-05-29 06:06:50 +02:00
|
|
|
}
|
2014-11-16 03:10:28 +01:00
|
|
|
}
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
if (!tag_.SetTagType(tag))
|
|
|
|
{
|
|
|
|
LOGERROR(L"Invalid tag '%ls' at %d in '%ls'", tag.c_str(), p, str.c_str());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!closing)
|
|
|
|
{
|
|
|
|
if (tag_.m_TagType == TextChunk::Tag::TAG_IMGRIGHT
|
|
|
|
|| tag_.m_TagType == TextChunk::Tag::TAG_IMGLEFT
|
|
|
|
|| tag_.m_TagType == TextChunk::Tag::TAG_ICON)
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
TextChunk FreshTextChunk = { rawpos, rawpos };
|
|
|
|
FreshTextChunk.m_Tags.push_back(tag_);
|
|
|
|
m_TextChunks.push_back(FreshTextChunk);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
tags.push_back(tag);
|
|
|
|
CurrentTextChunk.m_Tags.push_back(tag_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (tag != tags.back())
|
|
|
|
{
|
|
|
|
LOGERROR(L"Closing tag '%ls' does not match last opened tag '%ls' at %d in '%ls'", tag.c_str(), tags.back().c_str(), p, str.c_str());
|
|
|
|
break;
|
2004-05-29 06:06:50 +02:00
|
|
|
}
|
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
tags.pop_back();
|
|
|
|
CurrentTextChunk.m_Tags.pop_back();
|
2004-05-29 06:06:50 +02:00
|
|
|
}
|
2014-11-16 03:10:28 +01:00
|
|
|
break;
|
|
|
|
case L'\\':
|
|
|
|
if (++p == l)
|
|
|
|
{
|
|
|
|
LOGERROR(L"Escape character at end of string '%ls'", str.c_str());
|
|
|
|
break;
|
|
|
|
}
|
2014-12-13 02:08:29 +01:00
|
|
|
if (str[p] == L'n')
|
|
|
|
{
|
|
|
|
++rawpos;
|
|
|
|
m_RawString.push_back(L'\n');
|
|
|
|
break;
|
|
|
|
}
|
2014-11-16 03:10:28 +01:00
|
|
|
// fall-through
|
|
|
|
default:
|
|
|
|
++rawpos;
|
|
|
|
m_RawString.push_back(str[p]);
|
|
|
|
break;
|
2004-05-29 06:06:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-16 03:10:28 +01:00
|
|
|
// Add the chunk after the last tag
|
|
|
|
if (CurrentTextChunk.m_From != rawpos)
|
|
|
|
{
|
|
|
|
CurrentTextChunk.m_To = rawpos;
|
|
|
|
m_TextChunks.push_back(CurrentTextChunk);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-05-29 06:06:50 +02:00
|
|
|
// Add a delimiter at start and at end, it helps when
|
|
|
|
// processing later, because we don't have make exceptions for
|
|
|
|
// those cases.
|
|
|
|
m_Words.push_back(0);
|
|
|
|
|
2014-03-17 11:13:49 +01:00
|
|
|
// Add word boundaries in increasing order
|
|
|
|
for (u32 i = 0; i < m_RawString.length(); ++i)
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
2014-03-17 11:13:49 +01:00
|
|
|
wchar_t c = m_RawString[i];
|
|
|
|
if (c == '\n')
|
|
|
|
{
|
|
|
|
m_Words.push_back((int)i);
|
|
|
|
m_Words.push_back((int)i+1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
for (int n = 0; n < NUM_WORD_DELIMITORS; n += 2)
|
|
|
|
{
|
|
|
|
if (c <= WordDelimitors[n+1])
|
|
|
|
{
|
|
|
|
if (c >= WordDelimitors[n])
|
|
|
|
m_Words.push_back((int)i+1);
|
|
|
|
// assume the WordDelimitors list is stored in increasing order
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2004-05-29 06:06:50 +02:00
|
|
|
}
|
|
|
|
|
2014-03-17 11:13:49 +01:00
|
|
|
m_Words.push_back((int)m_RawString.length());
|
2004-05-29 06:06:50 +02:00
|
|
|
|
2004-08-31 04:09:58 +02:00
|
|
|
// Remove duplicates (only if larger than 2)
|
2014-11-16 03:10:28 +01:00
|
|
|
if (m_Words.size() <= 2)
|
|
|
|
return;
|
|
|
|
|
|
|
|
std::vector<int>::iterator it;
|
|
|
|
int last_word = -1;
|
|
|
|
for (it = m_Words.begin(); it != m_Words.end(); )
|
2004-05-29 06:06:50 +02:00
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
if (last_word == *it)
|
2004-07-31 13:28:24 +02:00
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
it = m_Words.erase(it);
|
2004-07-31 13:28:24 +02:00
|
|
|
}
|
2014-11-16 03:10:28 +01:00
|
|
|
else
|
2004-07-31 13:28:24 +02:00
|
|
|
{
|
2014-11-16 03:10:28 +01:00
|
|
|
last_word = *it;
|
|
|
|
++it;
|
2004-07-31 13:28:24 +02:00
|
|
|
}
|
2004-05-29 06:06:50 +02:00
|
|
|
}
|
2004-11-07 22:30:47 +01:00
|
|
|
}
|