1 /**
2  * Copyright (c) 2006-2019 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 
26 namespace love
27 {
28 namespace graphics
29 {
30 
31 love::Type Text::type("Text", &Drawable::type);
32 
Text(Font * font,const std::vector<Font::ColoredString> & text)33 Text::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 
~Text()43 Text::~Text()
44 {
45 	delete vertex_buffer;
46 }
47 
uploadVertices(const std::vector<Font::GlyphVertex> & vertices,size_t vertoffset)48 void 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 
regenerateVertices()82 void 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 
addTextData(const TextData & t)99 void 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 	// Must be before the early exit below.
117 	if (!t.append_vertices)
118 	{
119 		voffset = 0;
120 		vert_offset = 0;
121 		draw_commands.clear();
122 		text_data.clear();
123 	}
124 
125 	if (vertices.empty())
126 		return;
127 
128 	if (t.use_matrix)
129 		t.matrix.transformXY(&vertices[0], &vertices[0], (int) vertices.size());
130 
131 	uploadVertices(vertices, voffset);
132 
133 	if (!new_commands.empty())
134 	{
135 		// The start vertex should be adjusted to account for the vertex offset.
136 		for (Font::DrawCommand &cmd : new_commands)
137 			cmd.startvertex += (int) voffset;
138 
139 		auto firstcmd = new_commands.begin();
140 
141 		// If the first draw command in the new list has the same texture as the
142 		// last one in the existing list we're building and its vertices are
143 		// in-order, we can combine them (saving a draw call.)
144 		if (!draw_commands.empty())
145 		{
146 			auto prevcmd = draw_commands.back();
147 			if (prevcmd.texture == firstcmd->texture && (prevcmd.startvertex + prevcmd.vertexcount) == firstcmd->startvertex)
148 			{
149 				draw_commands.back().vertexcount += firstcmd->vertexcount;
150 				++firstcmd;
151 			}
152 		}
153 
154 		// Append the new draw commands to the list we're building.
155 		draw_commands.insert(draw_commands.end(), firstcmd, new_commands.end());
156 	}
157 
158 	vert_offset = voffset + vertices.size();
159 
160 	text_data.push_back(t);
161 	text_data.back().text_info = text_info;
162 
163 	// Font::generateVertices can invalidate the font's texture cache.
164 	if (font->getTextureCacheID() != texture_cache_id)
165 		regenerateVertices();
166 }
167 
set(const std::vector<Font::ColoredString> & text)168 void Text::set(const std::vector<Font::ColoredString> &text)
169 {
170 	return set(text, -1.0f, Font::ALIGN_MAX_ENUM);
171 }
172 
set(const std::vector<Font::ColoredString> & text,float wrap,Font::AlignMode align)173 void Text::set(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align)
174 {
175 	if (text.empty() || (text.size() == 1 && text[0].str.empty()))
176 		return clear();
177 
178 	Font::ColoredCodepoints codepoints;
179 	Font::getCodepointsFromString(text, codepoints);
180 
181 	addTextData({codepoints, wrap, align, {}, false, false, Matrix4()});
182 }
183 
add(const std::vector<Font::ColoredString> & text,const Matrix4 & m)184 int Text::add(const std::vector<Font::ColoredString> &text, const Matrix4 &m)
185 {
186 	return addf(text, -1.0f, Font::ALIGN_MAX_ENUM, m);
187 }
188 
addf(const std::vector<Font::ColoredString> & text,float wrap,Font::AlignMode align,const Matrix4 & m)189 int Text::addf(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align, const Matrix4 &m)
190 {
191 	Font::ColoredCodepoints codepoints;
192 	Font::getCodepointsFromString(text, codepoints);
193 
194 	addTextData({codepoints, wrap, align, {}, true, true, m});
195 
196 	return (int) text_data.size() - 1;
197 }
198 
clear()199 void Text::clear()
200 {
201 	text_data.clear();
202 	draw_commands.clear();
203 	texture_cache_id = font->getTextureCacheID();
204 	vert_offset = 0;
205 }
206 
setFont(Font * f)207 void Text::setFont(Font *f)
208 {
209 	font.set(f);
210 
211 	// Invalidate the texture cache ID since the font is different. We also have
212 	// to re-upload all the vertices based on the new font's textures.
213 	texture_cache_id = (uint32) -1;
214 	regenerateVertices();
215 }
216 
getFont() const217 Font *Text::getFont() const
218 {
219 	return font.get();
220 }
221 
getWidth(int index) const222 int Text::getWidth(int index) const
223 {
224 	if (index < 0)
225 		index = std::max((int) text_data.size() - 1, 0);
226 
227 	if (index >= (int) text_data.size())
228 		return 0;
229 
230 	return text_data[index].text_info.width;
231 }
232 
getHeight(int index) const233 int Text::getHeight(int index) const
234 {
235 	if (index < 0)
236 		index = std::max((int) text_data.size() - 1, 0);
237 
238 	if (index >= (int) text_data.size())
239 		return 0;
240 
241 	return text_data[index].text_info.height;
242 }
243 
draw(Graphics * gfx,const Matrix4 & m)244 void Text::draw(Graphics *gfx, const Matrix4 &m)
245 {
246 	if (vertex_buffer == nullptr || draw_commands.empty())
247 		return;
248 
249 	gfx->flushStreamDraws();
250 
251 	if (Shader::isDefaultActive())
252 		Shader::attachDefault(Shader::STANDARD_DEFAULT);
253 
254 	if (Shader::current)
255 		Shader::current->checkMainTextureType(TEXTURE_2D, false);
256 
257 	// Re-generate the text if the Font's texture cache was invalidated.
258 	if (font->getTextureCacheID() != texture_cache_id)
259 		regenerateVertices();
260 
261 	int totalverts = 0;
262 	for (const Font::DrawCommand &cmd : draw_commands)
263 		totalverts = std::max(cmd.startvertex + cmd.vertexcount, totalverts);
264 
265 	vertex_buffer->unmap(); // Make sure all pending data is flushed to the GPU.
266 
267 	Graphics::TempTransform transform(gfx, m);
268 
269 	for (const Font::DrawCommand &cmd : draw_commands)
270 		gfx->drawQuads(cmd.startvertex / 4, cmd.vertexcount / 4, vertexAttributes, vertexBuffers, cmd.texture);
271 }
272 
273 } // graphics
274 } // love
275