1/**
2 * Copyright (c) 2006-2023 LOVE Development Team
3 *
4 * This software is provided 'as-is', without any express or implied
5 * warranty. In no event will the authors be held liable for any damages
6 * arising from the use of this software.
7 *
8 * Permission is granted to anyone to use this software for any purpose,
9 * including commercial applications, and to alter it and redistribute it
10 * freely, subject to the following restrictions:
11 *
12 * 1. The origin of this software must not be misrepresented; you must not
13 * claim that you wrote the original software. If you use this software
14 * in a product, an acknowledgment in the product documentation would be
15 * appreciated but is not required.
16 * 2. Altered source versions must be plainly marked as such, and must not be
17 * misrepresented as being the original software.
18 * 3. This notice may not be removed or altered from any source distribution.
19 **/
20
21#include "Text.h"
22#include "Graphics.h"
23
24#include <algorithm>
25
26namespace love
27{
28namespace graphics
29{
30
31love::Type Text::type("Text", &Drawable::type);
32
33Text::Text(Font *font, const std::vector<Font::ColoredString> &text)
34 : font(font)
35 , vertexAttributes(Font::vertexFormat, 0)
36 , vertex_buffer(nullptr)
37 , vert_offset(0)
38 , texture_cache_id((uint32) -1)
39{
40 set(text);
41}
42
43Text::~Text()
44{
45 delete vertex_buffer;
46}
47
48void Text::uploadVertices(const std::vector<Font::GlyphVertex> &vertices, size_t vertoffset)
49{
50 size_t offset = vertoffset * sizeof(Font::GlyphVertex);
51 size_t datasize = vertices.size() * sizeof(Font::GlyphVertex);
52
53 // If we haven't created a VBO or the vertices are too big, make a new one.
54 if (datasize > 0 && (!vertex_buffer || (offset + datasize) > vertex_buffer->getSize()))
55 {
56 // Make it bigger than necessary to reduce potential future allocations.
57 size_t newsize = size_t((offset + datasize) * 1.5);
58
59 if (vertex_buffer != nullptr)
60 newsize = std::max(size_t(vertex_buffer->getSize() * 1.5), newsize);
61
62 auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
63 Buffer *new_buffer = gfx->newBuffer(newsize, nullptr, BUFFER_VERTEX, vertex::USAGE_DYNAMIC, 0);
64
65 if (vertex_buffer != nullptr)
66 vertex_buffer->copyTo(0, vertex_buffer->getSize(), new_buffer, 0);
67
68 delete vertex_buffer;
69 vertex_buffer = new_buffer;
70
71 vertexBuffers.set(0, vertex_buffer, 0);
72 }
73
74 if (vertex_buffer != nullptr && datasize > 0)
75 {
76 uint8 *bufferdata = (uint8 *) vertex_buffer->map();
77 memcpy(bufferdata + offset, &vertices[0], datasize);
78 // We unmap when we draw, to avoid unnecessary full map()/unmap() calls.
79 }
80}
81
82void Text::regenerateVertices()
83{
84 // If the font's texture cache was invalidated then we need to recreate the
85 // text's vertices, since glyph texcoords might have changed.
86 if (font->getTextureCacheID() != texture_cache_id)
87 {
88 std::vector<TextData> textdata = text_data;
89
90 clear();
91
92 for (const TextData &t : textdata)
93 addTextData(t);
94
95 texture_cache_id = font->getTextureCacheID();
96 }
97}
98
99void Text::addTextData(const TextData &t)
100{
101 std::vector<Font::GlyphVertex> vertices;
102 std::vector<Font::DrawCommand> new_commands;
103
104 Font::TextInfo text_info;
105
106 Colorf constantcolor = Colorf(1.0f, 1.0f, 1.0f, 1.0f);
107
108 // We only have formatted text if the align mode is valid.
109 if (t.align == Font::ALIGN_MAX_ENUM)
110 new_commands = font->generateVertices(t.codepoints, constantcolor, vertices, 0.0f, Vector2(0.0f, 0.0f), &text_info);
111 else
112 new_commands = font->generateVerticesFormatted(t.codepoints, constantcolor, t.wrap, t.align, vertices, &text_info);
113
114 size_t voffset = vert_offset;
115
116 if (!t.append_vertices)
117 {
118 voffset = 0;
119 vert_offset = 0;
120 draw_commands.clear();
121 text_data.clear();
122 }
123
124 if (t.use_matrix && !vertices.empty())
125 t.matrix.transformXY(vertices.data(), vertices.data(), (int) vertices.size());
126
127 uploadVertices(vertices, voffset);
128
129 if (!new_commands.empty())
130 {
131 // The start vertex should be adjusted to account for the vertex offset.
132 for (Font::DrawCommand &cmd : new_commands)
133 cmd.startvertex += (int) voffset;
134
135 auto firstcmd = new_commands.begin();
136
137 // If the first draw command in the new list has the same texture as the
138 // last one in the existing list we're building and its vertices are
139 // in-order, we can combine them (saving a draw call.)
140 if (!draw_commands.empty())
141 {
142 auto prevcmd = draw_commands.back();
143 if (prevcmd.texture == firstcmd->texture && (prevcmd.startvertex + prevcmd.vertexcount) == firstcmd->startvertex)
144 {
145 draw_commands.back().vertexcount += firstcmd->vertexcount;
146 ++firstcmd;
147 }
148 }
149
150 // Append the new draw commands to the list we're building.
151 draw_commands.insert(draw_commands.end(), firstcmd, new_commands.end());
152 }
153
154 vert_offset = voffset + vertices.size();
155
156 text_data.push_back(t);
157 text_data.back().text_info = text_info;
158
159 // Font::generateVertices can invalidate the font's texture cache.
160 if (font->getTextureCacheID() != texture_cache_id)
161 regenerateVertices();
162}
163
164void Text::set(const std::vector<Font::ColoredString> &text)
165{
166 return set(text, -1.0f, Font::ALIGN_MAX_ENUM);
167}
168
169void Text::set(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align)
170{
171 if (text.empty() || (text.size() == 1 && text[0].str.empty()))
172 return clear();
173
174 Font::ColoredCodepoints codepoints;
175 Font::getCodepointsFromString(text, codepoints);
176
177 addTextData({codepoints, wrap, align, {}, false, false, Matrix4()});
178}
179
180int Text::add(const std::vector<Font::ColoredString> &text, const Matrix4 &m)
181{
182 return addf(text, -1.0f, Font::ALIGN_MAX_ENUM, m);
183}
184
185int Text::addf(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align, const Matrix4 &m)
186{
187 Font::ColoredCodepoints codepoints;
188 Font::getCodepointsFromString(text, codepoints);
189
190 addTextData({codepoints, wrap, align, {}, true, true, m});
191
192 return (int) text_data.size() - 1;
193}
194
195void Text::clear()
196{
197 text_data.clear();
198 draw_commands.clear();
199 texture_cache_id = font->getTextureCacheID();
200 vert_offset = 0;
201}
202
203void Text::setFont(Font *f)
204{
205 font.set(f);
206
207 // Invalidate the texture cache ID since the font is different. We also have
208 // to re-upload all the vertices based on the new font's textures.
209 texture_cache_id = (uint32) -1;
210 regenerateVertices();
211}
212
213Font *Text::getFont() const
214{
215 return font.get();
216}
217
218int Text::getWidth(int index) const
219{
220 if (index < 0)
221 index = std::max((int) text_data.size() - 1, 0);
222
223 if (index >= (int) text_data.size())
224 return 0;
225
226 return text_data[index].text_info.width;
227}
228
229int Text::getHeight(int index) const
230{
231 if (index < 0)
232 index = std::max((int) text_data.size() - 1, 0);
233
234 if (index >= (int) text_data.size())
235 return 0;
236
237 return text_data[index].text_info.height;
238}
239
240void Text::draw(Graphics *gfx, const Matrix4 &m)
241{
242 if (vertex_buffer == nullptr || draw_commands.empty())
243 return;
244
245 gfx->flushStreamDraws();
246
247 if (Shader::isDefaultActive())
248 Shader::attachDefault(Shader::STANDARD_DEFAULT);
249
250 if (Shader::current)
251 Shader::current->checkMainTextureType(TEXTURE_2D, false);
252
253 // Re-generate the text if the Font's texture cache was invalidated.
254 if (font->getTextureCacheID() != texture_cache_id)
255 regenerateVertices();
256
257 int totalverts = 0;
258 for (const Font::DrawCommand &cmd : draw_commands)
259 totalverts = std::max(cmd.startvertex + cmd.vertexcount, totalverts);
260
261 vertex_buffer->unmap(); // Make sure all pending data is flushed to the GPU.
262
263 Graphics::TempTransform transform(gfx, m);
264
265 for (const Font::DrawCommand &cmd : draw_commands)
266 gfx->drawQuads(cmd.startvertex / 4, cmd.vertexcount / 4, vertexAttributes, vertexBuffers, cmd.texture);
267}
268
269} // graphics
270} // love
271