source: ps/trunk/source/gui/CGUIText.cpp@ 25645

Last change on this file since 25645 was 25645, checked in by Vladislav Belov, 3 years ago

Removes Z value from TextRenderer translate, renames TextRenderer methods to more explicit ones.

  • Property svn:eol-style set to native
File size: 14.1 KB
Line 
1/* Copyright (C) 2021 Wildfire Games.
2 * This file is part of 0 A.D.
3 *
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "precompiled.h"
19
20#include "CGUIText.h"
21
22#include "graphics/Canvas2D.h"
23#include "graphics/FontMetrics.h"
24#include "graphics/TextRenderer.h"
25#include "gui/CGUI.h"
26#include "gui/ObjectBases/IGUIObject.h"
27#include "gui/SettingTypes/CGUIString.h"
28#include "ps/CStrInternStatic.h"
29#include "renderer/Renderer.h"
30
31#include <math.h>
32
33extern int g_xres, g_yres;
34extern float g_GuiScale;
35
36// TODO Gee: CRect => CPoint ?
37void SGenerateTextImage::SetupSpriteCall(
38 const bool Left, CGUIText::SSpriteCall& SpriteCall, const float width, const float y,
39 const CSize2D& Size, const CStr& TextureName, const float BufferZone)
40{
41 // TODO Gee: Temp hardcoded values
42 SpriteCall.m_Area.top = y + BufferZone;
43 SpriteCall.m_Area.bottom = y + BufferZone + Size.Height;
44
45 if (Left)
46 {
47 SpriteCall.m_Area.left = BufferZone;
48 SpriteCall.m_Area.right = Size.Width + BufferZone;
49 }
50 else
51 {
52 SpriteCall.m_Area.left = width-BufferZone - Size.Width;
53 SpriteCall.m_Area.right = width-BufferZone;
54 }
55
56 SpriteCall.m_Sprite = TextureName;
57
58 m_YFrom = SpriteCall.m_Area.top - BufferZone;
59 m_YTo = SpriteCall.m_Area.bottom + BufferZone;
60 m_Indentation = Size.Width + BufferZone * 2;
61}
62
63CGUIText::CGUIText(const CGUI& pGUI, const CGUIString& string, const CStrW& FontW, const float Width, const float BufferZone, const EAlign align, const IGUIObject* pObject)
64{
65 if (string.m_Words.empty())
66 return;
67
68 CStrIntern Font(FontW.ToUTF8());
69 float x = BufferZone, y = BufferZone; // drawing pointer
70 int from = 0;
71
72 bool FirstLine = true; // Necessary because text in the first line is shorter
73 // (it doesn't count the line spacing)
74
75 // Images on the left or the right side.
76 SGenerateTextImages Images;
77 int pos_last_img = -1; // Position in the string where last img (either left or right) were encountered.
78 // in order to avoid duplicate processing.
79
80 // Go through string word by word
81 for (int i = 0; i < static_cast<int>(string.m_Words.size()) - 1; ++i)
82 {
83 // Pre-process each line one time, so we know which floating images
84 // will be added for that line.
85
86 // Generated stuff is stored in Feedback.
87 CGUIString::SFeedback Feedback;
88
89 // Preliminary line height, used for word-wrapping with floating images.
90 float prelim_line_height = 0.f;
91
92 // Width and height of all text calls generated.
93 string.GenerateTextCall(pGUI, Feedback, Font, string.m_Words[i], string.m_Words[i+1], FirstLine);
94
95 SetupSpriteCalls(pGUI, Feedback.m_Images, y, Width, BufferZone, i, pos_last_img, Images);
96
97 pos_last_img = std::max(pos_last_img, i);
98
99 x += Feedback.m_Size.Width;
100 prelim_line_height = std::max(prelim_line_height, Feedback.m_Size.Height);
101
102 // If Width is 0, then there's no word-wrapping, disable NewLine.
103 if (((Width != 0 && (x > Width - BufferZone || Feedback.m_NewLine)) || i == static_cast<int>(string.m_Words.size()) - 2) &&
104 ProcessLine(pGUI, string, Font, pObject, Images, align, prelim_line_height, Width, BufferZone, FirstLine, x, y, i, from))
105 return;
106 }
107}
108
109// Loop through our images queues, to see if images have been added.
110void CGUIText::SetupSpriteCalls(
111 const CGUI& pGUI,
112 const std::array<std::vector<CStr>, 2>& FeedbackImages,
113 const float y,
114 const float Width,
115 const float BufferZone,
116 const int i,
117 const int pos_last_img,
118 SGenerateTextImages& Images)
119{
120 // Check if this has already been processed.
121 // Also, floating images are only applicable if Word-Wrapping is on
122 if (Width == 0 || i <= pos_last_img)
123 return;
124
125 // Loop left/right
126 for (int j = 0; j < 2; ++j)
127 for (const CStr& imgname : FeedbackImages[j])
128 {
129 SSpriteCall SpriteCall;
130 SGenerateTextImage Image;
131
132 // Y is if no other floating images is above, y. Else it is placed
133 // after the last image, like a stack downwards.
134 float _y;
135 if (!Images[j].empty())
136 _y = std::max(y, Images[j].back().m_YTo);
137 else
138 _y = y;
139
140 const SGUIIcon& icon = pGUI.GetIcon(imgname);
141 Image.SetupSpriteCall(j == CGUIString::SFeedback::Left, SpriteCall, Width, _y, icon.m_Size, icon.m_SpriteName, BufferZone);
142
143 // Check if image is the lowest thing.
144 m_Size.Height = std::max(m_Size.Height, Image.m_YTo);
145
146 Images[j].emplace_back(Image);
147 m_SpriteCalls.emplace_back(std::move(SpriteCall));
148 }
149}
150
151// Now we'll do another loop to figure out the height and width of
152// the line (the height of the largest character and the width is
153// the sum of all of the individual widths). This
154// couldn't be determined in the first loop (main loop)
155// because it didn't regard images, so we don't know
156// if all characters processed, will actually be involved
157// in that line.
158void CGUIText::ComputeLineSize(
159 const CGUI& pGUI,
160 const CGUIString& string,
161 const CStrIntern& Font,
162 const bool FirstLine,
163 const float Width,
164 const float width_range_to,
165 const int i,
166 const int temp_from,
167 float& x,
168 CSize2D& line_size) const
169{
170 for (int j = temp_from; j <= i; ++j)
171 {
172 // We don't want to use Feedback now, so we'll have to use another one.
173 CGUIString::SFeedback Feedback2;
174
175 // Don't attach object, it'll suppress the errors
176 // we want them to be reported in the final GenerateTextCall()
177 // so that we don't get duplicates.
178 string.GenerateTextCall(pGUI, Feedback2, Font, string.m_Words[j], string.m_Words[j+1], FirstLine);
179
180 // Append X value.
181 x += Feedback2.m_Size.Width;
182
183 if (Width != 0 && x > width_range_to && j != temp_from && !Feedback2.m_NewLine)
184 {
185 // The calculated width of each word includes the space between the current
186 // word and the next. When we're wrapping, we need subtract the width of the
187 // space after the last word on the line before the wrap.
188 CFontMetrics currentFont(Font);
189 line_size.Width -= currentFont.GetCharacterWidth(*L" ");
190 break;
191 }
192
193 // Let line_size.cy be the maximum m_Height we encounter.
194 line_size.Height = std::max(line_size.Height, Feedback2.m_Size.Height);
195
196 // If the current word is an explicit new line ("\n"),
197 // break now before adding the width of this character.
198 // ("\n" doesn't have a glyph, thus is given the same width as
199 // the "missing glyph" character by CFont::GetCharacterWidth().)
200 if (Width != 0 && Feedback2.m_NewLine)
201 break;
202
203 line_size.Width += Feedback2.m_Size.Width;
204 }
205}
206
207bool CGUIText::ProcessLine(
208 const CGUI& pGUI,
209 const CGUIString& string,
210 const CStrIntern& Font,
211 const IGUIObject* pObject,
212 const SGenerateTextImages& Images,
213 const EAlign align,
214 const float prelim_line_height,
215 const float Width,
216 const float BufferZone,
217 bool& FirstLine,
218 float& x,
219 float& y,
220 int& i,
221 int& from)
222{
223 // Change 'from' to 'i', but first keep a copy of its value.
224 int temp_from = from;
225 from = i;
226
227 float width_range_from = BufferZone;
228 float width_range_to = Width - BufferZone;
229 ComputeLineRange(Images, y, Width, prelim_line_height, width_range_from, width_range_to);
230
231 // Reset X for the next loop
232 x = width_range_from;
233
234 CSize2D line_size;
235 ComputeLineSize(pGUI, string, Font, FirstLine, Width, width_range_to, i, temp_from, x, line_size);
236
237 // Reset x once more
238 x = width_range_from;
239
240 // Move down, because font drawing starts from the baseline
241 y += line_size.Height;
242
243 const float dx = GetLineOffset(align, width_range_from, width_range_to, line_size);
244
245 // Do the real processing now
246 const bool done = AssembleCalls(pGUI, string, Font, pObject, FirstLine, Width, width_range_to, dx, y, temp_from, i, x, from);
247
248 // Reset X
249 x = BufferZone;
250
251 // Update dimensions
252 m_Size.Width = std::max(m_Size.Width, line_size.Width + BufferZone * 2);
253 m_Size.Height = std::max(m_Size.Height, y + BufferZone);
254
255 FirstLine = false;
256
257 // Now if we entered as from = i, then we want
258 // i being one minus that, so that it will become
259 // the same i in the next loop. The difference is that
260 // we're on a new line now.
261 i = from - 1;
262
263 return done;
264}
265
266// Decide width of the line. We need to iterate our floating images.
267// this won't be exact because we're assuming the line_size.cy
268// will be as our preliminary calculation said. But that may change,
269// although we'd have to add a couple of more loops to try straightening
270// this problem out, and it is very unlikely to happen noticeably if one
271// structures his text in a stylistically pure fashion. Even if not, it
272// is still quite unlikely it will happen.
273// Loop through left and right side, from and to.
274void CGUIText::ComputeLineRange(
275 const SGenerateTextImages& Images,
276 const float y,
277 const float Width,
278 const float prelim_line_height,
279 float& width_range_from,
280 float& width_range_to) const
281{
282 // Floating images are only applicable if word-wrapping is enabled.
283 if (Width == 0)
284 return;
285
286 for (int j = 0; j < 2; ++j)
287 for (const SGenerateTextImage& img : Images[j])
288 {
289 // We're working with two intervals here, the image's and the line height's.
290 // let's find the union of these two.
291 float union_from, union_to;
292
293 union_from = std::max(y, img.m_YFrom);
294 union_to = std::min(y + prelim_line_height, img.m_YTo);
295
296 // The union is not empty
297 if (union_to > union_from)
298 {
299 if (j == 0)
300 width_range_from = std::max(width_range_from, img.m_Indentation);
301 else
302 width_range_to = std::min(width_range_to, Width - img.m_Indentation);
303 }
304 }
305}
306
307// compute offset based on what kind of alignment
308float CGUIText::GetLineOffset(
309 const EAlign align,
310 const float width_range_from,
311 const float width_range_to,
312 const CSize2D& line_size) const
313{
314 switch (align)
315 {
316 case EAlign::LEFT:
317 // don't add an offset
318 return 0.f;
319
320 case EAlign::CENTER:
321 return ((width_range_to - width_range_from) - line_size.Width) / 2;
322
323 case EAlign::RIGHT:
324 return width_range_to - line_size.Width;
325
326 default:
327 debug_warn(L"Broken EAlign in CGUIText()");
328 return 0.f;
329 }
330}
331
332bool CGUIText::AssembleCalls(
333 const CGUI& pGUI,
334 const CGUIString& string,
335 const CStrIntern& Font,
336 const IGUIObject* pObject,
337 const bool FirstLine,
338 const float Width,
339 const float width_range_to,
340 const float dx,
341 const float y,
342 const int temp_from,
343 const int i,
344 float& x,
345 int& from)
346{
347 bool done = false;
348
349 for (int j = temp_from; j <= i; ++j)
350 {
351 // We don't want to use Feedback now, so we'll have to use another one.
352 CGUIString::SFeedback Feedback2;
353
354 // Defaults
355 string.GenerateTextCall(pGUI, Feedback2, Font, string.m_Words[j], string.m_Words[j+1], FirstLine, pObject);
356
357 // Iterate all and set X/Y values
358 // Since X values are not set, we need to make an internal
359 // iteration with an increment that will append the internal
360 // x, that is what x_pointer is for.
361 float x_pointer = 0.f;
362
363 for (STextCall& tc : Feedback2.m_TextCalls)
364 {
365 tc.m_Pos = CVector2D(dx + x + x_pointer, y);
366
367 x_pointer += tc.m_Size.Width;
368
369 if (tc.m_pSpriteCall)
370 tc.m_pSpriteCall->m_Area += tc.m_Pos - CSize2D(0, tc.m_pSpriteCall->m_Area.GetHeight());
371 }
372
373 // Append X value.
374 x += Feedback2.m_Size.Width;
375
376 // The first word overrides the width limit, what we
377 // do, in those cases, are just drawing that word even
378 // though it'll extend the object.
379 if (Width != 0) // only if word-wrapping is applicable
380 {
381 if (Feedback2.m_NewLine)
382 {
383 from = j + 1;
384
385 // Sprite call can exist within only a newline segment,
386 // therefore we need this.
387 if (!Feedback2.m_SpriteCalls.empty())
388 {
389 auto newEnd = std::remove_if(Feedback2.m_TextCalls.begin(), Feedback2.m_TextCalls.end(), [](const STextCall& call) { return !call.m_pSpriteCall; });
390 m_TextCalls.insert(
391 m_TextCalls.end(),
392 std::make_move_iterator(Feedback2.m_TextCalls.begin()),
393 std::make_move_iterator(newEnd));
394 m_SpriteCalls.insert(
395 m_SpriteCalls.end(),
396 std::make_move_iterator(Feedback2.m_SpriteCalls.begin()),
397 std::make_move_iterator(Feedback2.m_SpriteCalls.end()));
398 }
399 break;
400 }
401 else if (x > width_range_to && j == temp_from)
402 {
403 from = j+1;
404 // do not break, since we want it to be added to m_TextCalls
405 }
406 else if (x > width_range_to)
407 {
408 from = j;
409 break;
410 }
411 }
412
413 // Add the whole Feedback2.m_TextCalls to our m_TextCalls.
414 m_TextCalls.insert(
415 m_TextCalls.end(),
416 std::make_move_iterator(Feedback2.m_TextCalls.begin()),
417 std::make_move_iterator(Feedback2.m_TextCalls.end()));
418
419 m_SpriteCalls.insert(
420 m_SpriteCalls.end(),
421 std::make_move_iterator(Feedback2.m_SpriteCalls.begin()),
422 std::make_move_iterator(Feedback2.m_SpriteCalls.end()));
423
424 if (j == static_cast<int>(string.m_Words.size()) - 2)
425 done = true;
426 }
427
428 return done;
429}
430
431void CGUIText::Draw(CGUI& pGUI, CCanvas2D& canvas, const CGUIColor& DefaultColor, const CVector2D& pos, CRect clipping) const
432{
433 bool isClipped = clipping != CRect();
434 if (isClipped)
435 {
436 // Make clipping rect as small as possible to prevent rounding errors
437 clipping.top = std::ceil(clipping.top);
438 clipping.bottom = std::floor(clipping.bottom);
439 clipping.left = std::ceil(clipping.left);
440 clipping.right = std::floor(clipping.right);
441
442 glEnable(GL_SCISSOR_TEST);
443 glScissor(
444 std::ceil(clipping.left * g_GuiScale),
445 std::ceil(g_yres - clipping.bottom * g_GuiScale),
446 std::floor(clipping.GetWidth() * g_GuiScale),
447 std::floor(clipping.GetHeight() * g_GuiScale));
448 }
449
450 CTextRenderer textRenderer;
451 textRenderer.SetClippingRect(clipping);
452 textRenderer.Translate(0.0f, 0.0f);
453
454 for (const STextCall& tc : m_TextCalls)
455 {
456 // If this is just a placeholder for a sprite call, continue
457 if (tc.m_pSpriteCall)
458 continue;
459
460 textRenderer.SetCurrentColor(tc.m_UseCustomColor ? tc.m_Color : DefaultColor);
461 textRenderer.SetCurrentFont(tc.m_Font);
462 textRenderer.Put(floorf(pos.X + tc.m_Pos.X), floorf(pos.Y + tc.m_Pos.Y), &tc.m_String);
463 }
464
465 canvas.DrawText(textRenderer);
466
467 for (const SSpriteCall& sc : m_SpriteCalls)
468 pGUI.DrawSprite(sc.m_Sprite, canvas, sc.m_Area + pos);
469
470 if (isClipped)
471 glDisable(GL_SCISSOR_TEST);
472}
Note: See TracBrowser for help on using the repository browser.