1 /**
2  * Copyright (c) 2006-2016 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 "common/Matrix.h"
23 
24 #include <algorithm>
25 
26 namespace love
27 {
28 namespace graphics
29 {
30 namespace opengl
31 {
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 	, vbo(nullptr)
36 	, vert_offset(0)
37 	, texture_cache_id((uint32) -1)
38 {
39 	set(text);
40 }
41 
~Text()42 Text::~Text()
43 {
44 	delete vbo;
45 }
46 
uploadVertices(const std::vector<Font::GlyphVertex> & vertices,size_t vertoffset)47 void Text::uploadVertices(const std::vector<Font::GlyphVertex> &vertices, size_t vertoffset)
48 {
49 	size_t offset = vertoffset * sizeof(Font::GlyphVertex);
50 	size_t datasize = vertices.size() * sizeof(Font::GlyphVertex);
51 	uint8 *vbodata = nullptr;
52 
53 	// If we haven't created a VBO or the vertices are too big, make a new one.
54 	if (datasize > 0 && (!vbo || (offset + datasize) > vbo->getSize()))
55 	{
56 		// Make it bigger than necessary to reduce potential future allocations.
57 		size_t newsize = size_t((offset + datasize) * 1.5);
58 		if (vbo != nullptr)
59 			newsize = std::max(size_t(vbo->getSize() * 1.5), newsize);
60 
61 		GLBuffer *new_vbo = new GLBuffer(newsize, nullptr, GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW);
62 
63 		if (vbo != nullptr)
64 		{
65 			try
66 			{
67 				GLBuffer::Bind bind(*vbo);
68 				vbodata = (uint8 *) vbo->map();
69 			}
70 			catch (love::Exception &)
71 			{
72 				delete new_vbo;
73 				throw;
74 			}
75 
76 			GLBuffer::Bind bind(*new_vbo);
77 			new_vbo->fill(0, vbo->getSize(), vbodata);
78 		}
79 
80 		delete vbo;
81 		vbo = new_vbo;
82 	}
83 
84 	if (vbo != nullptr && datasize > 0)
85 	{
86 		GLBuffer::Bind bind(*vbo);
87 		vbodata = (uint8 *) vbo->map();
88 		memcpy(vbodata + offset, &vertices[0], datasize);
89 		// We unmap when we draw, to avoid unnecessary full map()/unmap() calls.
90 	}
91 }
92 
regenerateVertices()93 void Text::regenerateVertices()
94 {
95 	// If the font's texture cache was invalidated then we need to recreate the
96 	// text's vertices, since glyph texcoords might have changed.
97 	if (font->getTextureCacheID() != texture_cache_id)
98 	{
99 		std::vector<TextData> textdata = text_data;
100 
101 		clear();
102 
103 		for (const TextData &t : textdata)
104 			addTextData(t);
105 
106 		texture_cache_id = font->getTextureCacheID();
107 	}
108 }
109 
addTextData(const TextData & t)110 void Text::addTextData(const TextData &t)
111 {
112 	std::vector<Font::GlyphVertex> vertices;
113 	std::vector<Font::DrawCommand> new_commands;
114 
115 	Font::TextInfo text_info;
116 
117 	// We only have formatted text if the align mode is valid.
118 	if (t.align == Font::ALIGN_MAX_ENUM)
119 		new_commands = font->generateVertices(t.codepoints, vertices, 0.0f, Vector(0.0f, 0.0f), &text_info);
120 	else
121 		new_commands = font->generateVerticesFormatted(t.codepoints, t.wrap, t.align, vertices, &text_info);
122 
123 	if (t.use_matrix)
124 		t.matrix.transform(&vertices[0], &vertices[0], (int) vertices.size());
125 
126 	size_t voffset = vert_offset;
127 
128 	if (!t.append_vertices)
129 	{
130 		voffset = 0;
131 		draw_commands.clear();
132 		text_data.clear();
133 	}
134 
135 	uploadVertices(vertices, voffset);
136 
137 	if (!new_commands.empty())
138 	{
139 		// The start vertex should be adjusted to account for the vertex offset.
140 		for (Font::DrawCommand &cmd : new_commands)
141 			cmd.startvertex += (int) voffset;
142 
143 		auto firstcmd = new_commands.begin();
144 
145 		// If the first draw command in the new list has the same texture as the
146 		// last one in the existing list we're building and its vertices are
147 		// in-order, we can combine them (saving a draw call.)
148 		if (!draw_commands.empty())
149 		{
150 			auto prevcmd = draw_commands.back();
151 			if (prevcmd.texture == firstcmd->texture && (prevcmd.startvertex + prevcmd.vertexcount) == firstcmd->startvertex)
152 			{
153 				draw_commands.back().vertexcount += firstcmd->vertexcount;
154 				++firstcmd;
155 			}
156 		}
157 
158 		// Append the new draw commands to the list we're building.
159 		draw_commands.insert(draw_commands.end(), firstcmd, new_commands.end());
160 	}
161 
162 	vert_offset = voffset + vertices.size();
163 
164 	text_data.push_back(t);
165 	text_data.back().text_info = text_info;
166 
167 	// Font::generateVertices can invalidate the font's texture cache.
168 	if (font->getTextureCacheID() != texture_cache_id)
169 		regenerateVertices();
170 }
171 
set(const std::vector<Font::ColoredString> & text)172 void Text::set(const std::vector<Font::ColoredString> &text)
173 {
174 	return set(text, -1.0f, Font::ALIGN_MAX_ENUM);
175 }
176 
set(const std::vector<Font::ColoredString> & text,float wrap,Font::AlignMode align)177 void Text::set(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align)
178 {
179 	if (text.empty() || (text.size() == 1 && text[0].str.empty()))
180 		return set();
181 
182 	Font::ColoredCodepoints codepoints;
183 	Font::getCodepointsFromString(text, codepoints);
184 
185 	addTextData({codepoints, wrap, align, {}, false, false, Matrix3()});
186 }
187 
set()188 void Text::set()
189 {
190 	clear();
191 }
192 
add(const std::vector<Font::ColoredString> & text,float x,float y,float angle,float sx,float sy,float ox,float oy,float kx,float ky)193 int Text::add(const std::vector<Font::ColoredString> &text, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
194 {
195 	return addf(text, -1.0f, Font::ALIGN_MAX_ENUM, x, y, angle, sx, sy, ox, oy, kx, ky);
196 }
197 
addf(const std::vector<Font::ColoredString> & text,float wrap,Font::AlignMode align,float x,float y,float angle,float sx,float sy,float ox,float oy,float kx,float ky)198 int Text::addf(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
199 {
200 	Font::ColoredCodepoints codepoints;
201 	Font::getCodepointsFromString(text, codepoints);
202 
203 	Matrix3 m(x, y, angle, sx, sy, ox, oy, kx, ky);
204 
205 	addTextData({codepoints, wrap, align, {}, true, true, m});
206 
207 	return (int) text_data.size() - 1;
208 }
209 
clear()210 void Text::clear()
211 {
212 	text_data.clear();
213 	draw_commands.clear();
214 	texture_cache_id = font->getTextureCacheID();
215 	vert_offset = 0;
216 }
217 
draw(float x,float y,float angle,float sx,float sy,float ox,float oy,float kx,float ky)218 void Text::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
219 {
220 	if (vbo == nullptr || draw_commands.empty())
221 		return;
222 
223 	OpenGL::TempDebugGroup debuggroup("Text object draw");
224 
225 	// Re-generate the text if the Font's texture cache was invalidated.
226 	if (font->getTextureCacheID() != texture_cache_id)
227 		regenerateVertices();
228 
229 	const size_t pos_offset   = offsetof(Font::GlyphVertex, x);
230 	const size_t tex_offset   = offsetof(Font::GlyphVertex, s);
231 	const size_t color_offset = offsetof(Font::GlyphVertex, color.r);
232 	const size_t stride = sizeof(Font::GlyphVertex);
233 
234 	OpenGL::TempTransform transform(gl);
235 	transform.get() *= Matrix4(x, y, angle, sx, sy, ox, oy, kx, ky);
236 
237 	{
238 		GLBuffer::Bind bind(*vbo);
239 		vbo->unmap(); // Make sure all pending data is flushed to the GPU.
240 
241 		// Font::drawVertices expects AttribPointer calls to be done already.
242 		glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, vbo->getPointer(pos_offset));
243 		glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_UNSIGNED_SHORT, GL_TRUE, stride, vbo->getPointer(tex_offset));
244 		glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, vbo->getPointer(color_offset));
245 	}
246 
247 	gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD | ATTRIBFLAG_COLOR);
248 
249 	font->drawVertices(draw_commands, true);
250 }
251 
setFont(Font * f)252 void Text::setFont(Font *f)
253 {
254 	font.set(f);
255 
256 	// Invalidate the texture cache ID since the font is different. We also have
257 	// to re-upload all the vertices based on the new font's textures.
258 	texture_cache_id = (uint32) -1;
259 	regenerateVertices();
260 }
261 
getFont() const262 Font *Text::getFont() const
263 {
264 	return font.get();
265 }
266 
getWidth(int index) const267 int Text::getWidth(int index) const
268 {
269 	if (index < 0)
270 		index = std::max((int) text_data.size() - 1, 0);
271 
272 	if (index >= (int) text_data.size())
273 		return 0;
274 
275 	return text_data[index].text_info.width;
276 }
277 
getHeight(int index) const278 int Text::getHeight(int index) const
279 {
280 	if (index < 0)
281 		index = std::max((int) text_data.size() - 1, 0);
282 
283 	if (index >= (int) text_data.size())
284 		return 0;
285 
286 	return text_data[index].text_info.height;
287 }
288 
289 } // opengl
290 } // graphics
291 } // love
292