1 // Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
2 // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
3 
4 #include "Gui.h"
5 
6 #include "graphics/Renderer.h"
7 #include "text/TextSupport.h"
8 #include "utils.h"
9 
10 static const float PARAGRAPH_SPACING = 1.5f;
11 
12 namespace Gui {
13 
TextLayout(const char * _str,RefCountedPtr<Text::TextureFont> font,ColourMarkupMode markup)14 	TextLayout::TextLayout(const char *_str, RefCountedPtr<Text::TextureFont> font, ColourMarkupMode markup)
15 	{
16 		// XXX ColourMarkupSkip not correctly implemented yet
17 		assert(markup != ColourMarkupSkip);
18 
19 		m_colourMarkup = markup;
20 		m_font = font ? font : Gui::Screen::GetFont();
21 
22 		SetText(_str);
23 
24 		prevWidth = -1.0f;
25 		prevColor = Color::WHITE;
26 	}
27 
SetText(const char * _str)28 	void TextLayout::SetText(const char *_str)
29 	{
30 		str = std::string(_str);
31 
32 		m_justify = false;
33 		float wordWidth = 0;
34 		const char *wordstart = str.c_str();
35 		words.clear();
36 
37 		int i = 0;
38 		while (str[i]) {
39 			wordWidth = 0;
40 			wordstart = &str[i];
41 
42 			while (str[i] && str[i] != ' ' && str[i] != '\r' && str[i] != '\n') {
43 				/* skip color control code things! */
44 				if ((m_colourMarkup != ColourMarkupNone) && (str[i] == '#')) {
45 					unsigned int hexcol;
46 					if (sscanf(&str[i], "#%3x", &hexcol) == 1) {
47 						i += 4;
48 						continue;
49 					}
50 				}
51 
52 				Uint32 chr;
53 				int n = Text::utf8_decode_char(&chr, &str[i]);
54 				assert(n);
55 				i += n;
56 
57 				const Text::TextureFont::Glyph &glyph = m_font->GetGlyph(chr);
58 				wordWidth += glyph.advX;
59 
60 				// XXX this should do kerning
61 			}
62 
63 			words.push_back(word_t(wordstart, wordWidth));
64 
65 			if (str[i]) {
66 				if (str[i] == '\n') words.push_back(word_t(0, 0));
67 				str[i++] = 0;
68 			}
69 		}
70 
71 		prevWidth = -1.0f;
72 		prevColor = Color::WHITE;
73 	}
74 
MeasureSize(const float width,float outSize[2]) const75 	void TextLayout::MeasureSize(const float width, float outSize[2]) const
76 	{
77 		float fontScale[2];
78 		Gui::Screen::GetCoords2Pixels(fontScale);
79 		_MeasureSizeRaw(width / fontScale[0], outSize);
80 		outSize[0] = ceil(outSize[0] * fontScale[0]);
81 		outSize[1] = ceil(outSize[1] * fontScale[1]);
82 	}
83 
Render(const float width,const Color & color) const84 	void TextLayout::Render(const float width, const Color &color) const
85 	{
86 		PROFILE_SCOPED()
87 		if (words.empty())
88 			return;
89 
90 		float fontScale[2];
91 		Gui::Screen::GetCoords2Pixels(fontScale);
92 
93 		Graphics::Renderer *r = Gui::Screen::GetRenderer();
94 
95 		const matrix4x4f &modelMatrix = r->GetTransform();
96 		Graphics::Renderer::MatrixTicket ticket(r);
97 		{
98 			const float x = modelMatrix[12];
99 			const float y = modelMatrix[13];
100 			matrix4x4f modelView = matrix4x4f::Identity();
101 			modelView.Translate(floor(x / fontScale[0]) * fontScale[0], floor(y / fontScale[1]) * fontScale[1], 0);
102 			modelView.Scale(fontScale[0], fontScale[1], 1);
103 			r->SetTransform(modelView);
104 			m_font->RenderBuffer(m_vbuffer.Get(), color);
105 		}
106 	}
107 
Update(const float width,const Color & color)108 	void TextLayout::Update(const float width, const Color &color)
109 	{
110 		PROFILE_SCOPED()
111 		if (words.empty()) {
112 			m_vbuffer.Reset();
113 			return;
114 		}
115 
116 		// see if anything has changed
117 		if (is_equal_exact(prevWidth, width) && (prevColor == color)) {
118 			return;
119 		}
120 
121 		prevWidth = width;
122 		prevColor = color;
123 
124 		float fontScale[2];
125 		Gui::Screen::GetCoords2Pixels(fontScale);
126 
127 		Graphics::Renderer *r = Gui::Screen::GetRenderer();
128 		Graphics::Renderer::MatrixTicket ticket(r);
129 
130 		Graphics::VertexArray va(Graphics::ATTRIB_POSITION | Graphics::ATTRIB_DIFFUSE | Graphics::ATTRIB_UV0);
131 
132 		if (r) {
133 			const float maxWidth = width / fontScale[0];
134 
135 			float py = 0;
136 
137 			const float spaceWidth = m_font->GetGlyph(' ').advX;
138 
139 			Color c = color;
140 
141 			// vertex array pre-assignment, because TextureFont botches it
142 			// over-reserves for markup, but we don't care
143 			int numChars = 0;
144 			std::list<word_t>::const_iterator wpos = this->words.begin();
145 			for (; wpos != this->words.end(); ++wpos)
146 				if ((*wpos).word) numChars += strlen((*wpos).word);
147 
148 			va.position.reserve(6 * numChars);
149 			va.diffuse.reserve(6 * numChars);
150 			va.uv0.reserve(6 * numChars);
151 
152 			// build lines of text
153 			wpos = this->words.begin();
154 			while (wpos != this->words.end()) {
155 				float len = 0;
156 				int num = 0;
157 
158 				std::list<word_t>::const_iterator i = wpos;
159 				len += (*i).advx;
160 				num++;
161 				bool overflow = false;
162 				bool explicit_newline = false;
163 				if ((*i).word != 0) {
164 					++i;
165 					for (; i != this->words.end(); ++i) {
166 						if ((*i).word == 0) {
167 							// newline
168 							explicit_newline = true;
169 							num++;
170 							break;
171 						}
172 						if (len + spaceWidth + (*i).advx > maxWidth) {
173 							overflow = true;
174 							break;
175 						}
176 						len += (*i).advx + spaceWidth;
177 						num++;
178 					}
179 				}
180 
181 				float _spaceWidth;
182 				if ((m_justify) && (num > 1) && overflow) {
183 					float spaceleft = maxWidth - len;
184 					_spaceWidth = spaceWidth + (spaceleft / float(num - 1));
185 				} else {
186 					_spaceWidth = spaceWidth;
187 				}
188 
189 				float px = 0;
190 				for (int j = 0; j < num; j++) {
191 					if ((*wpos).word) {
192 						const std::string word((*wpos).word);
193 						if (m_colourMarkup == ColourMarkupUse) {
194 							Color newColor = m_font->PopulateMarkup(va, word, round(px), round(py), c);
195 							if (!word.empty())
196 								c = std::move(newColor);
197 						} else
198 							m_font->PopulateString(va, word, round(px), round(py), c);
199 					}
200 					px += (*wpos).advx + _spaceWidth;
201 					++wpos;
202 				}
203 				py += m_font->GetHeight() * (explicit_newline ? PARAGRAPH_SPACING : 1.0f);
204 			}
205 		}
206 
207 		if (va.GetNumVerts() > 0) {
208 			if (!m_vbuffer.Valid() || m_vbuffer->GetCapacity() < va.GetNumVerts()) {
209 				//create buffer and upload data
210 				Graphics::VertexBufferDesc vbd;
211 				vbd.attrib[0].semantic = Graphics::ATTRIB_POSITION;
212 				vbd.attrib[0].format = Graphics::ATTRIB_FORMAT_FLOAT3;
213 				vbd.attrib[1].semantic = Graphics::ATTRIB_DIFFUSE;
214 				vbd.attrib[1].format = Graphics::ATTRIB_FORMAT_UBYTE4;
215 				vbd.attrib[2].semantic = Graphics::ATTRIB_UV0;
216 				vbd.attrib[2].format = Graphics::ATTRIB_FORMAT_FLOAT2;
217 				vbd.numVertices = va.GetNumVerts();
218 				vbd.usage = Graphics::BUFFER_USAGE_DYNAMIC; // we could be updating this per-frame
219 				m_vbuffer.Reset(r->CreateVertexBuffer(vbd));
220 			}
221 
222 			m_vbuffer->Populate(va);
223 		} else {
224 			m_vbuffer.Reset();
225 		}
226 	}
227 
_MeasureSizeRaw(const float layoutWidth,float outSize[2]) const228 	void TextLayout::_MeasureSizeRaw(const float layoutWidth, float outSize[2]) const
229 	{
230 		outSize[0] = 0;
231 		outSize[1] = 0;
232 
233 		const float spaceWidth = m_font->GetGlyph(' ').advX;
234 
235 		// build lines of text
236 		for (std::list<word_t>::const_iterator wpos = words.begin(); wpos != words.end();) {
237 			float len = 0;
238 			int num = 0;
239 			bool explicit_newline = false;
240 
241 			std::list<word_t>::const_iterator i = wpos;
242 			len += (*i).advx;
243 			num++;
244 			bool overflow = false;
245 			if ((*i).word != 0) {
246 				++i;
247 				for (; i != words.end(); ++i) {
248 					if ((*i).word == 0) {
249 						// newline
250 						explicit_newline = true;
251 						num++;
252 						break;
253 					}
254 					if (len + spaceWidth + (*i).advx > layoutWidth) {
255 						overflow = true;
256 						break;
257 					}
258 					len += (*i).advx + spaceWidth;
259 					num++;
260 				}
261 			}
262 
263 			float _spaceWidth;
264 			if ((m_justify) && (num > 1) && overflow) {
265 				float spaceleft = layoutWidth - len;
266 				_spaceWidth = spaceWidth + (spaceleft / float(num - 1));
267 			} else {
268 				_spaceWidth = spaceWidth;
269 			}
270 
271 			float lineLen = 0;
272 			for (int j = 0; j < num; j++) {
273 				word_t word = (*wpos);
274 				lineLen += word.advx;
275 				if (j < num - 1) lineLen += _spaceWidth;
276 				++wpos;
277 			}
278 			if (lineLen > outSize[0]) outSize[0] = lineLen;
279 			outSize[1] += m_font->GetHeight() * (explicit_newline ? PARAGRAPH_SPACING : 1.0f);
280 		}
281 		if (outSize[1] > 0.0f)
282 			outSize[1] += m_font->GetDescender();
283 	}
284 
285 } // namespace Gui
286