Index: ps/trunk/source/gui/CChart.cpp
===================================================================
--- ps/trunk/source/gui/CChart.cpp
+++ ps/trunk/source/gui/CChart.cpp
@@ -20,6 +20,7 @@
#include "CChart.h"
#include "gui/CGUIColor.h"
+#include "gui/CGUIString.h"
#include "gui/GUIMatrix.h"
#include "graphics/ShaderManager.h"
#include "i18n/L10n.h"
Index: ps/trunk/source/gui/CGUIList.h
===================================================================
--- ps/trunk/source/gui/CGUIList.h
+++ ps/trunk/source/gui/CGUIList.h
@@ -15,11 +15,10 @@
* along with 0 A.D. If not, see .
*/
-
#ifndef INCLUDED_CGUILIST
#define INCLUDED_CGUILIST
-#include "GUItext.h"
+#include "gui/CGUIString.h"
class CGUIList
{
Index: ps/trunk/source/gui/CGUIString.h
===================================================================
--- ps/trunk/source/gui/CGUIString.h
+++ ps/trunk/source/gui/CGUIString.h
@@ -0,0 +1,229 @@
+/* Copyright (C) 2019 Wildfire Games.
+ * 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 .
+ */
+
+/*
+ * GUI text, handles text stuff
+ *
+ * --Overview--
+ * Mainly contains struct SGUIText and friends.
+ * Actual text processing is made in CGUI::GenerateText()
+ *
+ * --More info--
+ * Check GUI.h
+ *
+ */
+
+#ifndef INCLUDED_CGUISTRING
+#define INCLUDED_CGUISTRING
+
+#include
+
+#include "gui/CGUISprite.h"
+#include "gui/GUItext.h"
+#include "ps/CStrIntern.h"
+
+class CGUI;
+
+/**
+ * String class, substitute for CStr, but that parses
+ * the tags and builds up a list of all text that will
+ * be different when outputted.
+ *
+ * The difference between CGUIString and SGUIText is that
+ * CGUIString is a string-class that parses the tags
+ * when the value is set. The SGUIText is just a container
+ * which stores the positions and settings of all text-calls
+ * that will have to be made to the Renderer.
+ */
+class CGUIString
+{
+public:
+ /**
+ * A chunk of text that represents one call to the renderer.
+ * In other words, all text in one chunk, will be drawn
+ * exactly with the same settings.
+ */
+ struct TextChunk
+ {
+ /**
+ * A tag looks like this "Hello [b]there[/b] little"
+ */
+ struct Tag
+ {
+ /**
+ * Tag Type
+ */
+ enum TagType
+ {
+ TAG_B,
+ TAG_I,
+ TAG_FONT,
+ TAG_SIZE,
+ TAG_COLOR,
+ TAG_IMGLEFT,
+ TAG_IMGRIGHT,
+ TAG_ICON,
+ TAG_INVALID
+ };
+
+ struct TagAttribute
+ {
+ std::wstring attrib;
+ std::wstring value;
+ };
+
+ /**
+ * Set tag from string
+ *
+ * @param tagtype TagType by string, like 'img' for [img]
+ * @return True if m_TagType was set.
+ */
+ bool SetTagType(const CStrW& tagtype);
+ TagType GetTagType(const CStrW& tagtype) const;
+
+
+ /**
+ * In [b="Hello"][/b]
+ * m_TagType is TAG_B
+ */
+ TagType m_TagType;
+
+ /**
+ * In [b="Hello"][/b]
+ * m_TagValue is 'Hello'
+ */
+ std::wstring m_TagValue;
+
+ /**
+ * Some tags need an additional attributes
+ */
+ std::vector m_TagAttributes;
+ };
+
+ /**
+ * m_From and m_To is the range of the string
+ */
+ int m_From, m_To;
+
+ /**
+ * Tags that are present. [a][b]
+ */
+ std::vector m_Tags;
+ };
+
+ /**
+ * All data generated in GenerateTextCall()
+ */
+ struct SFeedback
+ {
+ // Avoid copying the vector and list containers.
+ NONCOPYABLE(SFeedback);
+ MOVABLE(SFeedback);
+ SFeedback() = default;
+
+ // Constants
+ static const int Left = 0;
+ static const int Right = 1;
+
+ /**
+ * Reset all member data.
+ */
+ void Reset();
+
+ /**
+ * Image stacks, for left and right floating images.
+ */
+ std::vector m_Images[2]; // left and right
+
+ /**
+ * Text and Sprite Calls.
+ */
+ std::vector m_TextCalls;
+ std::list m_SpriteCalls; // list for consistent mem addresses
+ // so that we can point to elements.
+
+ /**
+ * Width and Height *feedback*
+ */
+ CSize m_Size;
+
+ /**
+ * If the word inputted was a new line.
+ */
+ bool m_NewLine;
+ };
+
+ /**
+ * Set the value, the string will automatically
+ * be parsed when set.
+ */
+ void SetValue(const CStrW& str);
+
+ /**
+ * Get String, with tags
+ */
+ const CStrW& GetOriginalString() const { return m_OriginalString; }
+
+ /**
+ * Get String, stripped of tags
+ */
+ const CStrW& GetRawString() const { return m_RawString; }
+
+ /**
+ * Generate Text Call from specified range. The range
+ * must span only within ONE TextChunk though. Otherwise
+ * it can't be fit into a single Text Call
+ *
+ * Notice it won't make it complete, you will have to add
+ * X/Y values and such.
+ *
+ * @param pGUI Pointer to CGUI object making this call, for e.g. icon retrieval.
+ * @param Feedback contains all info that is generated.
+ * @param DefaultFont Default Font
+ * @param from From character n,
+ * @param to to character n.
+ * @param FirstLine Whether this is the first line of text, to calculate its height correctly
+ * @param pObject Only for Error outputting, optional! If NULL
+ * then no Errors will be reported! Useful when you need
+ * to make several GenerateTextCall in different phases,
+ * it avoids duplicates.
+ */
+ void GenerateTextCall(const CGUI* pGUI, SFeedback& Feedback, CStrIntern DefaultFont, const int& from, const int& to, const bool FirstLine, const IGUIObject* pObject = NULL) const;
+
+ /**
+ * Words
+ */
+ std::vector m_Words;
+
+private:
+ /**
+ * TextChunks
+ */
+ std::vector m_TextChunks;
+
+ /**
+ * The full raw string. Stripped of tags.
+ */
+ CStrW m_RawString;
+
+ /**
+ * The original string value passed to SetValue.
+ */
+ CStrW m_OriginalString;
+};
+
+#endif // INCLUDED_CGUISTRING
Index: ps/trunk/source/gui/CGUIString.cpp
===================================================================
--- ps/trunk/source/gui/CGUIString.cpp
+++ ps/trunk/source/gui/CGUIString.cpp
@@ -0,0 +1,472 @@
+/* Copyright (C) 2019 Wildfire Games.
+ * 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 .
+ */
+
+#include "precompiled.h"
+
+#include
+
+#include "gui/GUI.h"
+#include "lib/utf8.h"
+#include "graphics/FontMetrics.h"
+#include "ps/CLogger.h"
+
+
+// List of word delimiter bounds
+// The list contains ranges of word delimiters. 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_DELIMITERS = 4*2;
+static const u16 WordDelimiters[NUM_WORD_DELIMITERS] = {
+ ' ' , ' ', // spaces
+ '-' , '-', // hyphens
+ 0x3000, 0x31FF, // ideographic symbols
+ 0x3400, 0x9FFF
+// TODO add unicode blocks of other languages that don't use spaces
+};
+
+void CGUIString::SFeedback::Reset()
+{
+ m_Images[Left].clear();
+ m_Images[Right].clear();
+ m_TextCalls.clear();
+ m_SpriteCalls.clear();
+ m_Size = CSize();
+ m_NewLine = false;
+}
+
+void CGUIString::GenerateTextCall(const CGUI* pGUI, SFeedback& Feedback, CStrIntern DefaultFont, const int& from, const int& to, const bool FirstLine, const IGUIObject* pObject) const
+{
+ // Reset width and height, because they will be determined with incrementation
+ // or comparisons.
+ Feedback.Reset();
+
+ // Check out which text chunk this is within.
+ for (const TextChunk& textChunk : m_TextChunks)
+ {
+ // Get the area that is overlapped by both the TextChunk and
+ // by the from/to inputted.
+ int _from = std::max(from, textChunk.m_From);
+ int _to = std::min(to, textChunk.m_To);
+
+ // If from is larger than to, then they are not overlapping
+ if (_to == _from && textChunk.m_From == textChunk.m_To)
+ {
+ // These should never be able to have more than one tag.
+ ENSURE(textChunk.m_Tags.size() == 1);
+
+ // Icons and images are placed on exactly one position
+ // in the words-list, and they 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.
+
+ // it's in the end of one word, and the icon
+ // should really belong to the beginning of the next one
+ if (_to == to && to >= 1 && to < (int)m_RawString.length())
+ {
+ if (m_RawString[to-1] == ' ' ||
+ m_RawString[to-1] == '-' ||
+ m_RawString[to-1] == '\n')
+ continue;
+ }
+ // This std::string is just a break
+ if (_from == from && from >= 1)
+ {
+ if (m_RawString[from] == '\n' &&
+ m_RawString[from-1] != '\n' &&
+ m_RawString[from-1] != ' ' &&
+ m_RawString[from-1] != '-')
+ continue;
+ }
+
+ const TextChunk::Tag& tag = textChunk.m_Tags[0];
+ ENSURE(tag.m_TagType == TextChunk::Tag::TAG_IMGLEFT ||
+ tag.m_TagType == TextChunk::Tag::TAG_IMGRIGHT ||
+ tag.m_TagType == TextChunk::Tag::TAG_ICON);
+
+ const std::string& path = utf8_from_wstring(tag.m_TagValue);
+ if (!pGUI->HasIcon(path))
+ {
+ if (pObject)
+ LOGERROR("Trying to use an icon, imgleft or imgright-tag with an undefined icon (\"%s\").", path.c_str());
+ continue;
+ }
+
+ switch (tag.m_TagType)
+ {
+ case TextChunk::Tag::TAG_IMGLEFT:
+ Feedback.m_Images[SFeedback::Left].push_back(path);
+ break;
+ case TextChunk::Tag::TAG_IMGRIGHT:
+ Feedback.m_Images[SFeedback::Right].push_back(path);
+ break;
+ case 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;
+
+ // Also add it to the sprites being rendered.
+ SGUIText::SSpriteCall SpriteCall;
+
+ // Get Icon from icon database in pGUI
+ const SGUIIcon& icon = pGUI->GetIcon(path);
+
+ const CSize& size = icon.m_Size;
+
+ // 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);
+
+ // These are also needed later
+ TextCall.m_Size = size;
+ SpriteCall.m_Area = size;
+
+ // Handle additional attributes
+ for (const TextChunk::Tag::TagAttribute& tagAttrib : tag.m_TagAttributes)
+ {
+ if (tagAttrib.attrib == L"displace" && !tagAttrib.value.empty())
+ {
+ // Displace the sprite
+ CSize displacement;
+ // Parse the value
+ if (!GUI::ParseString(tagAttrib.value, displacement))
+ LOGERROR("Error parsing 'displace' value for tag [ICON]");
+ else
+ SpriteCall.m_Area += displacement;
+ }
+ else if (tagAttrib.attrib == L"tooltip")
+ SpriteCall.m_Tooltip = tagAttrib.value;
+ else if (tagAttrib.attrib == L"tooltip_style")
+ SpriteCall.m_TooltipStyle = tagAttrib.value;
+ }
+
+ SpriteCall.m_Sprite = icon.m_SpriteName;
+ SpriteCall.m_CellID = icon.m_CellID;
+
+ // Add sprite call
+ Feedback.m_SpriteCalls.push_back(std::move(SpriteCall));
+
+ // Finalize text call
+ TextCall.m_pSpriteCall = &Feedback.m_SpriteCalls.back();
+
+ // Add text call
+ Feedback.m_TextCalls.emplace_back(std::move(TextCall));
+
+ break;
+ }
+ NODEFAULT;
+ }
+ }
+ else if (_to > _from && !Feedback.m_NewLine)
+ {
+ SGUIText::STextCall TextCall;
+
+ // Set defaults
+ TextCall.m_Font = DefaultFont;
+ TextCall.m_UseCustomColor = false;
+
+ TextCall.m_String = m_RawString.substr(_from, _to-_from);
+
+ // Go through tags and apply changes.
+ for (const TextChunk::Tag& tag : textChunk.m_Tags)
+ {
+ switch (tag.m_TagType)
+ {
+ case TextChunk::Tag::TAG_COLOR:
+ TextCall.m_UseCustomColor = true;
+
+ if (!GUI::ParseString(tag.m_TagValue, TextCall.m_Color) && pObject)
+ LOGERROR("Error parsing the value of a [color]-tag in GUI text when reading object \"%s\".", pObject->GetPresentableName().c_str());
+ break;
+ case TextChunk::Tag::TAG_FONT:
+ // TODO Gee: (2004-08-15) Check if Font exists?
+ TextCall.m_Font = CStrIntern(utf8_from_wstring(tag.m_TagValue));
+ break;
+ default:
+ LOGERROR("Encountered unexpected tag applied to text");
+ break;
+ }
+ }
+
+ // Calculate the size of the font
+ CSize size;
+ int cx, cy;
+ CFontMetrics font (TextCall.m_Font);
+ font.CalculateStringSize(TextCall.m_String.c_str(), cx, cy);
+ // For anything other than the first line, the line spacing
+ // needs to be considered rather than just the height of the text
+ if (!FirstLine)
+ cy = font.GetLineSpacing();
+
+ size.cx = (float)cx;
+ size.cy = (float)cy;
+
+ // 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);
+
+ // These are also needed later
+ TextCall.m_Size = size;
+
+ if (!TextCall.m_String.empty() && TextCall.m_String[0] == '\n')
+ Feedback.m_NewLine = true;
+
+ // Add text-chunk
+ Feedback.m_TextCalls.emplace_back(std::move(TextCall));
+ }
+ }
+}
+
+bool CGUIString::TextChunk::Tag::SetTagType(const CStrW& tagtype)
+{
+ TagType t = GetTagType(tagtype);
+ if (t == TAG_INVALID)
+ return false;
+
+ m_TagType = t;
+ return true;
+}
+
+CGUIString::TextChunk::Tag::TagType CGUIString::TextChunk::Tag::GetTagType(const CStrW& tagtype) const
+{
+ 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;
+}
+
+void CGUIString::SetValue(const CStrW& str)
+{
+ m_OriginalString = str;
+
+ m_TextChunks.clear();
+ m_Words.clear();
+ m_RawString.clear();
+
+ // Current Text Chunk
+ CGUIString::TextChunk CurrentTextChunk;
+ CurrentTextChunk.m_From = 0;
+
+ int l = str.length();
+ int rawpos = 0;
+ CStrW tag;
+ std::vector tags;
+ bool closing = false;
+ for (int p = 0; p < l; ++p)
+ {
+ TextChunk::Tag tag_;
+ switch (str[p])
+ {
+ case L'[':
+ CurrentTextChunk.m_To = rawpos;
+ // Add the current chunks if it is not empty
+ if (CurrentTextChunk.m_From != rawpos)
+ m_TextChunks.push_back(CurrentTextChunk);
+ CurrentTextChunk.m_From = rawpos;
+
+ closing = false;
+ if (++p == l)
+ {
+ LOGERROR("Partial tag at end of string '%s'", utf8_from_wstring(str));
+ break;
+ }
+ if (str[p] == L'/')
+ {
+ closing = true;
+ if (tags.empty())
+ {
+ LOGERROR("Encountered closing tag without having any open tags. At %d in '%s'", p, utf8_from_wstring(str));
+ break;
+ }
+ if (++p == l)
+ {
+ LOGERROR("Partial closing tag at end of string '%s'", utf8_from_wstring(str));
+ break;
+ }
+ }
+ tag.clear();
+ // Parse tag
+ for (; p < l && str[p] != L']'; ++p)
+ {
+ CStrW name, param;
+ switch (str[p])
+ {
+ case L' ':
+ if (closing) // We still parse them to make error handling cleaner
+ LOGERROR("Closing tags do not support parameters (at pos %d '%s')", p, utf8_from_wstring(str));
+
+ // parse something="something else"
+ for (++p; p < l && str[p] != L'='; ++p)
+ name.push_back(str[p]);
+
+ if (p == l)
+ {
+ LOGERROR("Parameter without value at pos %d '%s'", p, utf8_from_wstring(str));
+ break;
+ }
+ FALLTHROUGH;
+ case L'=':
+ // parse a quoted parameter
+ if (closing) // We still parse them to make error handling cleaner
+ LOGERROR("Closing tags do not support parameters (at pos %d '%s')", p, utf8_from_wstring(str));
+
+ if (++p == l)
+ {
+ LOGERROR("Expected parameter, got end of string '%s'", utf8_from_wstring(str));
+ break;
+ }
+ if (str[p] != L'"')
+ {
+ LOGERROR("Unquoted parameters are not supported (at pos %d '%s')", p, utf8_from_wstring(str));
+ break;
+ }
+ for (++p; p < l && str[p] != L'"'; ++p)
+ {
+ switch (str[p])
+ {
+ case L'\\':
+ if (++p == l)
+ {
+ LOGERROR("Escape character at end of string '%s'", utf8_from_wstring(str));
+ break;
+ }
+ // NOTE: We do not support \n in tag parameters
+ FALLTHROUGH;
+ default:
+ param.push_back(str[p]);
+ }
+ }
+
+ 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;
+ }
+ }
+
+ if (!tag_.SetTagType(tag))
+ {
+ LOGERROR("Invalid tag '%s' at %d in '%s'", utf8_from_wstring(tag), p, utf8_from_wstring(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)
+ {
+ 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("Closing tag '%s' does not match last opened tag '%s' at %d in '%s'", utf8_from_wstring(tag), utf8_from_wstring(tags.back()), p, utf8_from_wstring(str));
+ break;
+ }
+
+ tags.pop_back();
+ CurrentTextChunk.m_Tags.pop_back();
+ }
+ break;
+ case L'\\':
+ if (++p == l)
+ {
+ LOGERROR("Escape character at end of string '%s'", utf8_from_wstring(str));
+ break;
+ }
+ if (str[p] == L'n')
+ {
+ ++rawpos;
+ m_RawString.push_back(L'\n');
+ break;
+ }
+ FALLTHROUGH;
+ default:
+ ++rawpos;
+ m_RawString.push_back(str[p]);
+ break;
+ }
+ }
+
+ // Add the chunk after the last tag
+ if (CurrentTextChunk.m_From != rawpos)
+ {
+ CurrentTextChunk.m_To = rawpos;
+ m_TextChunks.push_back(CurrentTextChunk);
+ }
+
+
+ // 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);
+
+ // Add word boundaries in increasing order
+ for (u32 i = 0; i < m_RawString.length(); ++i)
+ {
+ 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_DELIMITERS; n += 2)
+ {
+ if (c <= WordDelimiters[n+1])
+ {
+ if (c >= WordDelimiters[n])
+ m_Words.push_back((int)i+1);
+ // assume the WordDelimiters list is stored in increasing order
+ break;
+ }
+ }
+ }
+
+ m_Words.push_back((int)m_RawString.length());
+
+ // Remove duplicates (only if larger than 2)
+ if (m_Words.size() <= 2)
+ return;
+
+ m_Words.erase(std::unique(m_Words.begin(), m_Words.end()), m_Words.end());
+}
Index: ps/trunk/source/gui/GUItext.h
===================================================================
--- ps/trunk/source/gui/GUItext.h
+++ ps/trunk/source/gui/GUItext.h
@@ -15,28 +15,15 @@
* along with 0 A.D. If not, see .
*/
-/*
- * GUI text, handles text stuff
- *
- * --Overview--
- * Mainly contains struct SGUIText and friends.
- * Actual text processing is made in CGUI::GenerateText()
- *
- * --More info--
- * Check GUI.h
- *
- */
-
#ifndef INCLUDED_GUITEXT
#define INCLUDED_GUITEXT
#include
-#include "CGUISprite.h"
#include "gui/CGUIColor.h"
+#include "gui/CGUISprite.h"
#include "ps/CStrIntern.h"
-
-class CGUI;
+#include "ps/Shapes.h"
/**
* An SGUIText object is a parsed string, divided into
@@ -163,192 +150,4 @@
CSize m_Size;
};
-/**
- * String class, substitute for CStr, but that parses
- * the tags and builds up a list of all text that will
- * be different when outputted.
- *
- * The difference between CGUIString and SGUIText is that
- * CGUIString is a string-class that parses the tags
- * when the value is set. The SGUIText is just a container
- * which stores the positions and settings of all text-calls
- * that will have to be made to the Renderer.
- */
-class CGUIString
-{
-public:
- /**
- * A chunk of text that represents one call to the renderer.
- * In other words, all text in one chunk, will be drawn
- * exactly with the same settings.
- */
- struct TextChunk
- {
- /**
- * A tag looks like this "Hello [b]there[/b] little"
- */
- struct Tag
- {
- /**
- * Tag Type
- */
- enum TagType
- {
- TAG_B,
- TAG_I,
- TAG_FONT,
- TAG_SIZE,
- TAG_COLOR,
- TAG_IMGLEFT,
- TAG_IMGRIGHT,
- TAG_ICON,
- TAG_INVALID
- };
-
- struct TagAttribute
- {
- std::wstring attrib;
- std::wstring value;
- };
-
- /**
- * Set tag from string
- *
- * @param tagtype TagType by string, like 'img' for [img]
- * @return True if m_TagType was set.
- */
- bool SetTagType(const CStrW& tagtype);
- TagType GetTagType(const CStrW& tagtype) const;
-
-
- /**
- * In [b="Hello"][/b]
- * m_TagType is TAG_B
- */
- TagType m_TagType;
-
- /**
- * In [b="Hello"][/b]
- * m_TagValue is 'Hello'
- */
- std::wstring m_TagValue;
-
- /**
- * Some tags need an additional attributes
- */
- std::vector m_TagAttributes;
- };
-
- /**
- * m_From and m_To is the range of the string
- */
- int m_From, m_To;
-
- /**
- * Tags that are present. [a][b]
- */
- std::vector m_Tags;
- };
-
- /**
- * All data generated in GenerateTextCall()
- */
- struct SFeedback
- {
- // Avoid copying the vector and list containers.
- NONCOPYABLE(SFeedback);
- MOVABLE(SFeedback);
- SFeedback() = default;
-
- // Constants
- static const int Left = 0;
- static const int Right = 1;
-
- /**
- * Reset all member data.
- */
- void Reset();
-
- /**
- * Image stacks, for left and right floating images.
- */
- std::vector m_Images[2]; // left and right
-
- /**
- * Text and Sprite Calls.
- */
- std::vector m_TextCalls;
- std::list m_SpriteCalls; // list for consistent mem addresses
- // so that we can point to elements.
-
- /**
- * Width and Height *feedback*
- */
- CSize m_Size;
-
- /**
- * If the word inputted was a new line.
- */
- bool m_NewLine;
- };
-
- /**
- * Set the value, the string will automatically
- * be parsed when set.
- */
- void SetValue(const CStrW& str);
-
- /**
- * Get String, with tags
- */
- const CStrW& GetOriginalString() const { return m_OriginalString; }
-
- /**
- * Get String, stripped of tags
- */
- const CStrW& GetRawString() const { return m_RawString; }
-
- /**
- * Generate Text Call from specified range. The range
- * must span only within ONE TextChunk though. Otherwise
- * it can't be fit into a single Text Call
- *
- * Notice it won't make it complete, you will have to add
- * X/Y values and such.
- *
- * @param pGUI Pointer to CGUI object making this call, for e.g. icon retrieval.
- * @param Feedback contains all info that is generated.
- * @param DefaultFont Default Font
- * @param from From character n,
- * @param to to character n.
- * @param FirstLine Whether this is the first line of text, to calculate its height correctly
- * @param pObject Only for Error outputting, optional! If NULL
- * then no Errors will be reported! Useful when you need
- * to make several GenerateTextCall in different phases,
- * it avoids duplicates.
- */
- void GenerateTextCall(const CGUI* pGUI, SFeedback& Feedback, CStrIntern DefaultFont, const int& from, const int& to, const bool FirstLine, const IGUIObject* pObject = NULL) const;
-
- /**
- * Words
- */
- std::vector m_Words;
-
-private:
- /**
- * TextChunks
- */
- std::vector m_TextChunks;
-
- /**
- * The full raw string. Stripped of tags.
- */
- CStrW m_RawString;
-
- /**
- * The original string value passed to SetValue.
- */
- CStrW m_OriginalString;
-};
-
#endif // INCLUDED_GUITEXT
Index: ps/trunk/source/gui/GUItext.cpp
===================================================================
--- ps/trunk/source/gui/GUItext.cpp
+++ ps/trunk/source/gui/GUItext.cpp
@@ -1,472 +0,0 @@
-/* Copyright (C) 2019 Wildfire Games.
- * 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 .
- */
-
-#include "precompiled.h"
-
-#include
-
-#include "gui/GUI.h"
-#include "lib/utf8.h"
-#include "graphics/FontMetrics.h"
-#include "ps/CLogger.h"
-
-
-// List of word delimiter bounds
-// The list contains ranges of word delimiters. 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_DELIMITERS = 4*2;
-static const u16 WordDelimiters[NUM_WORD_DELIMITERS] = {
- ' ' , ' ', // spaces
- '-' , '-', // hyphens
- 0x3000, 0x31FF, // ideographic symbols
- 0x3400, 0x9FFF
-// TODO add unicode blocks of other languages that don't use spaces
-};
-
-void CGUIString::SFeedback::Reset()
-{
- m_Images[Left].clear();
- m_Images[Right].clear();
- m_TextCalls.clear();
- m_SpriteCalls.clear();
- m_Size = CSize();
- m_NewLine = false;
-}
-
-void CGUIString::GenerateTextCall(const CGUI* pGUI, SFeedback& Feedback, CStrIntern DefaultFont, const int& from, const int& to, const bool FirstLine, const IGUIObject* pObject) const
-{
- // Reset width and height, because they will be determined with incrementation
- // or comparisons.
- Feedback.Reset();
-
- // Check out which text chunk this is within.
- for (const TextChunk& textChunk : m_TextChunks)
- {
- // Get the area that is overlapped by both the TextChunk and
- // by the from/to inputted.
- int _from = std::max(from, textChunk.m_From);
- int _to = std::min(to, textChunk.m_To);
-
- // If from is larger than to, then they are not overlapping
- if (_to == _from && textChunk.m_From == textChunk.m_To)
- {
- // These should never be able to have more than one tag.
- ENSURE(textChunk.m_Tags.size() == 1);
-
- // Icons and images are placed on exactly one position
- // in the words-list, and they 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.
-
- // it's in the end of one word, and the icon
- // should really belong to the beginning of the next one
- if (_to == to && to >= 1 && to < (int)m_RawString.length())
- {
- if (m_RawString[to-1] == ' ' ||
- m_RawString[to-1] == '-' ||
- m_RawString[to-1] == '\n')
- continue;
- }
- // This std::string is just a break
- if (_from == from && from >= 1)
- {
- if (m_RawString[from] == '\n' &&
- m_RawString[from-1] != '\n' &&
- m_RawString[from-1] != ' ' &&
- m_RawString[from-1] != '-')
- continue;
- }
-
- const TextChunk::Tag& tag = textChunk.m_Tags[0];
- ENSURE(tag.m_TagType == TextChunk::Tag::TAG_IMGLEFT ||
- tag.m_TagType == TextChunk::Tag::TAG_IMGRIGHT ||
- tag.m_TagType == TextChunk::Tag::TAG_ICON);
-
- const std::string& path = utf8_from_wstring(tag.m_TagValue);
- if (!pGUI->HasIcon(path))
- {
- if (pObject)
- LOGERROR("Trying to use an icon, imgleft or imgright-tag with an undefined icon (\"%s\").", path.c_str());
- continue;
- }
-
- switch (tag.m_TagType)
- {
- case TextChunk::Tag::TAG_IMGLEFT:
- Feedback.m_Images[SFeedback::Left].push_back(path);
- break;
- case TextChunk::Tag::TAG_IMGRIGHT:
- Feedback.m_Images[SFeedback::Right].push_back(path);
- break;
- case 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;
-
- // Also add it to the sprites being rendered.
- SGUIText::SSpriteCall SpriteCall;
-
- // Get Icon from icon database in pGUI
- const SGUIIcon& icon = pGUI->GetIcon(path);
-
- const CSize& size = icon.m_Size;
-
- // 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);
-
- // These are also needed later
- TextCall.m_Size = size;
- SpriteCall.m_Area = size;
-
- // Handle additional attributes
- for (const TextChunk::Tag::TagAttribute& tagAttrib : tag.m_TagAttributes)
- {
- if (tagAttrib.attrib == L"displace" && !tagAttrib.value.empty())
- {
- // Displace the sprite
- CSize displacement;
- // Parse the value
- if (!GUI::ParseString(tagAttrib.value, displacement))
- LOGERROR("Error parsing 'displace' value for tag [ICON]");
- else
- SpriteCall.m_Area += displacement;
- }
- else if (tagAttrib.attrib == L"tooltip")
- SpriteCall.m_Tooltip = tagAttrib.value;
- else if (tagAttrib.attrib == L"tooltip_style")
- SpriteCall.m_TooltipStyle = tagAttrib.value;
- }
-
- SpriteCall.m_Sprite = icon.m_SpriteName;
- SpriteCall.m_CellID = icon.m_CellID;
-
- // Add sprite call
- Feedback.m_SpriteCalls.push_back(std::move(SpriteCall));
-
- // Finalize text call
- TextCall.m_pSpriteCall = &Feedback.m_SpriteCalls.back();
-
- // Add text call
- Feedback.m_TextCalls.emplace_back(std::move(TextCall));
-
- break;
- }
- NODEFAULT;
- }
- }
- else if (_to > _from && !Feedback.m_NewLine)
- {
- SGUIText::STextCall TextCall;
-
- // Set defaults
- TextCall.m_Font = DefaultFont;
- TextCall.m_UseCustomColor = false;
-
- TextCall.m_String = m_RawString.substr(_from, _to-_from);
-
- // Go through tags and apply changes.
- for (const TextChunk::Tag& tag : textChunk.m_Tags)
- {
- switch (tag.m_TagType)
- {
- case TextChunk::Tag::TAG_COLOR:
- TextCall.m_UseCustomColor = true;
-
- if (!GUI::ParseString(tag.m_TagValue, TextCall.m_Color) && pObject)
- LOGERROR("Error parsing the value of a [color]-tag in GUI text when reading object \"%s\".", pObject->GetPresentableName().c_str());
- break;
- case TextChunk::Tag::TAG_FONT:
- // TODO Gee: (2004-08-15) Check if Font exists?
- TextCall.m_Font = CStrIntern(utf8_from_wstring(tag.m_TagValue));
- break;
- default:
- LOGERROR("Encountered unexpected tag applied to text");
- break;
- }
- }
-
- // Calculate the size of the font
- CSize size;
- int cx, cy;
- CFontMetrics font (TextCall.m_Font);
- font.CalculateStringSize(TextCall.m_String.c_str(), cx, cy);
- // For anything other than the first line, the line spacing
- // needs to be considered rather than just the height of the text
- if (!FirstLine)
- cy = font.GetLineSpacing();
-
- size.cx = (float)cx;
- size.cy = (float)cy;
-
- // 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);
-
- // These are also needed later
- TextCall.m_Size = size;
-
- if (!TextCall.m_String.empty() && TextCall.m_String[0] == '\n')
- Feedback.m_NewLine = true;
-
- // Add text-chunk
- Feedback.m_TextCalls.emplace_back(std::move(TextCall));
- }
- }
-}
-
-bool CGUIString::TextChunk::Tag::SetTagType(const CStrW& tagtype)
-{
- TagType t = GetTagType(tagtype);
- if (t == TAG_INVALID)
- return false;
-
- m_TagType = t;
- return true;
-}
-
-CGUIString::TextChunk::Tag::TagType CGUIString::TextChunk::Tag::GetTagType(const CStrW& tagtype) const
-{
- 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;
-}
-
-void CGUIString::SetValue(const CStrW& str)
-{
- m_OriginalString = str;
-
- m_TextChunks.clear();
- m_Words.clear();
- m_RawString.clear();
-
- // Current Text Chunk
- CGUIString::TextChunk CurrentTextChunk;
- CurrentTextChunk.m_From = 0;
-
- int l = str.length();
- int rawpos = 0;
- CStrW tag;
- std::vector tags;
- bool closing = false;
- for (int p = 0; p < l; ++p)
- {
- TextChunk::Tag tag_;
- switch (str[p])
- {
- case L'[':
- CurrentTextChunk.m_To = rawpos;
- // Add the current chunks if it is not empty
- if (CurrentTextChunk.m_From != rawpos)
- m_TextChunks.push_back(CurrentTextChunk);
- CurrentTextChunk.m_From = rawpos;
-
- closing = false;
- if (++p == l)
- {
- LOGERROR("Partial tag at end of string '%s'", utf8_from_wstring(str));
- break;
- }
- if (str[p] == L'/')
- {
- closing = true;
- if (tags.empty())
- {
- LOGERROR("Encountered closing tag without having any open tags. At %d in '%s'", p, utf8_from_wstring(str));
- break;
- }
- if (++p == l)
- {
- LOGERROR("Partial closing tag at end of string '%s'", utf8_from_wstring(str));
- break;
- }
- }
- tag.clear();
- // Parse tag
- for (; p < l && str[p] != L']'; ++p)
- {
- CStrW name, param;
- switch (str[p])
- {
- case L' ':
- if (closing) // We still parse them to make error handling cleaner
- LOGERROR("Closing tags do not support parameters (at pos %d '%s')", p, utf8_from_wstring(str));
-
- // parse something="something else"
- for (++p; p < l && str[p] != L'='; ++p)
- name.push_back(str[p]);
-
- if (p == l)
- {
- LOGERROR("Parameter without value at pos %d '%s'", p, utf8_from_wstring(str));
- break;
- }
- FALLTHROUGH;
- case L'=':
- // parse a quoted parameter
- if (closing) // We still parse them to make error handling cleaner
- LOGERROR("Closing tags do not support parameters (at pos %d '%s')", p, utf8_from_wstring(str));
-
- if (++p == l)
- {
- LOGERROR("Expected parameter, got end of string '%s'", utf8_from_wstring(str));
- break;
- }
- if (str[p] != L'"')
- {
- LOGERROR("Unquoted parameters are not supported (at pos %d '%s')", p, utf8_from_wstring(str));
- break;
- }
- for (++p; p < l && str[p] != L'"'; ++p)
- {
- switch (str[p])
- {
- case L'\\':
- if (++p == l)
- {
- LOGERROR("Escape character at end of string '%s'", utf8_from_wstring(str));
- break;
- }
- // NOTE: We do not support \n in tag parameters
- FALLTHROUGH;
- default:
- param.push_back(str[p]);
- }
- }
-
- 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;
- }
- }
-
- if (!tag_.SetTagType(tag))
- {
- LOGERROR("Invalid tag '%s' at %d in '%s'", utf8_from_wstring(tag), p, utf8_from_wstring(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)
- {
- 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("Closing tag '%s' does not match last opened tag '%s' at %d in '%s'", utf8_from_wstring(tag), utf8_from_wstring(tags.back()), p, utf8_from_wstring(str));
- break;
- }
-
- tags.pop_back();
- CurrentTextChunk.m_Tags.pop_back();
- }
- break;
- case L'\\':
- if (++p == l)
- {
- LOGERROR("Escape character at end of string '%s'", utf8_from_wstring(str));
- break;
- }
- if (str[p] == L'n')
- {
- ++rawpos;
- m_RawString.push_back(L'\n');
- break;
- }
- FALLTHROUGH;
- default:
- ++rawpos;
- m_RawString.push_back(str[p]);
- break;
- }
- }
-
- // Add the chunk after the last tag
- if (CurrentTextChunk.m_From != rawpos)
- {
- CurrentTextChunk.m_To = rawpos;
- m_TextChunks.push_back(CurrentTextChunk);
- }
-
-
- // 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);
-
- // Add word boundaries in increasing order
- for (u32 i = 0; i < m_RawString.length(); ++i)
- {
- 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_DELIMITERS; n += 2)
- {
- if (c <= WordDelimiters[n+1])
- {
- if (c >= WordDelimiters[n])
- m_Words.push_back((int)i+1);
- // assume the WordDelimiters list is stored in increasing order
- break;
- }
- }
- }
-
- m_Words.push_back((int)m_RawString.length());
-
- // Remove duplicates (only if larger than 2)
- if (m_Words.size() <= 2)
- return;
-
- m_Words.erase(std::unique(m_Words.begin(), m_Words.end()), m_Words.end());
-}