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 #include "common/config.h"
21 #include "Font.h"
22 #include "font/GlyphData.h"
23 
24 #include "libraries/utf8/utf8.h"
25 
26 #include "common/math.h"
27 #include "common/Matrix.h"
28 #include "graphics/Graphics.h"
29 
30 #include <math.h>
31 #include <sstream>
32 #include <algorithm> // for max
33 #include <limits>
34 
35 namespace love
36 {
37 namespace graphics
38 {
39 namespace opengl
40 {
41 
normToUint16(double n)42 static inline uint16 normToUint16(double n)
43 {
44 	return (uint16) (n * LOVE_UINT16_MAX);
45 }
46 
47 int Font::fontCount = 0;
48 
Font(love::font::Rasterizer * r,const Texture::Filter & filter)49 Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter)
50 	: rasterizers({r})
51 	, height(r->getHeight())
52 	, lineHeight(1)
53 	, textureWidth(128)
54 	, textureHeight(128)
55 	, filter(filter)
56 	, useSpacesAsTab(false)
57 	, quadIndices(20) // We make this bigger at draw-time, if needed.
58 	, textureCacheID(0)
59 	, textureMemorySize(0)
60 {
61 	this->filter.mipmap = Texture::FILTER_NONE;
62 
63 	// Try to find the best texture size match for the font size. default to the
64 	// largest texture size if no rough match is found.
65 	while (true)
66 	{
67 		if ((height * 0.8) * height * 30 <= textureWidth * textureHeight)
68 			break;
69 
70 		TextureSize nextsize = getNextTextureSize();
71 
72 		if (nextsize.width <= textureWidth && nextsize.height <= textureHeight)
73 			break;
74 
75 		textureWidth = nextsize.width;
76 		textureHeight = nextsize.height;
77 	}
78 
79 	love::font::GlyphData *gd = r->getGlyphData(32); // Space character.
80 	type = (gd->getFormat() == font::GlyphData::FORMAT_LUMINANCE_ALPHA) ? FONT_TRUETYPE : FONT_IMAGE;
81 	gd->release();
82 
83 	if (!r->hasGlyph(9)) // No tab character in the Rasterizer.
84 		useSpacesAsTab = true;
85 
86 	loadVolatile();
87 
88 	++fontCount;
89 }
90 
~Font()91 Font::~Font()
92 {
93 	unloadVolatile();
94 
95 	--fontCount;
96 }
97 
getNextTextureSize() const98 Font::TextureSize Font::getNextTextureSize() const
99 {
100 	TextureSize size = {textureWidth, textureHeight};
101 
102 	int maxsize = std::min(4096, gl.getMaxTextureSize());
103 
104 	if (size.width * 2 <= maxsize || size.height * 2 <= maxsize)
105 	{
106 		// {128, 128} -> {256, 128} -> {256, 256} -> {512, 256} -> etc.
107 		if (size.width == size.height)
108 			size.width *= 2;
109 		else
110 			size.height *= 2;
111 	}
112 
113 	return size;
114 }
115 
getTextureFormat(FontType fontType,GLenum * internalformat) const116 GLenum Font::getTextureFormat(FontType fontType, GLenum *internalformat) const
117 {
118 	GLenum format = fontType == FONT_TRUETYPE ? GL_LUMINANCE_ALPHA : GL_RGBA;
119 	GLenum iformat = fontType == FONT_TRUETYPE ? GL_LUMINANCE8_ALPHA8 : GL_RGBA8;
120 
121 	if (format == GL_RGBA && isGammaCorrect())
122 	{
123 		// In ES2, the internalformat and format params of TexImage must match.
124 		// ES3 doesn't allow "GL_SRGB_ALPHA" as the internal format. But it also
125 		// requires GL_LUMINANCE_ALPHA rather than GL_LUMINANCE8_ALPHA8 as the
126 		// internal format, for LA.
127 		if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0)
128 			format = iformat = GL_SRGB_ALPHA;
129 		else
130 			iformat = GL_SRGB8_ALPHA8;
131 	}
132 	else if (GLAD_ES_VERSION_2_0)
133 		iformat = format;
134 
135 	if (internalformat != nullptr)
136 		*internalformat = iformat;
137 
138 	return format;
139 }
140 
createTexture()141 void Font::createTexture()
142 {
143 	OpenGL::TempDebugGroup debuggroup("Font create texture");
144 
145 	size_t bpp = type == FONT_TRUETYPE ? 2 : 4;
146 
147 	size_t prevmemsize = textureMemorySize;
148 	if (prevmemsize > 0)
149 	{
150 		textureMemorySize -= (textureWidth * textureHeight * bpp);
151 		gl.updateTextureMemorySize(prevmemsize, textureMemorySize);
152 	}
153 
154 	GLuint t = 0;
155 	TextureSize size = {textureWidth, textureHeight};
156 	TextureSize nextsize = getNextTextureSize();
157 	bool recreatetexture = false;
158 
159 	// If we have an existing texture already, we'll try replacing it with a
160 	// larger-sized one rather than creating a second one. Having a single
161 	// texture reduces texture switches and draw calls when rendering.
162 	if ((nextsize.width > size.width || nextsize.height > size.height)
163 		&& !textures.empty())
164 	{
165 		recreatetexture = true;
166 		size = nextsize;
167 		t = textures.back();
168 	}
169 	else
170 		glGenTextures(1, &t);
171 
172 	gl.bindTexture(t);
173 
174 	gl.setTextureFilter(filter);
175 
176 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
177 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
178 
179 	GLenum internalformat = GL_RGBA;
180 	GLenum format = getTextureFormat(type, &internalformat);
181 
182 	// Initialize the texture with transparent black.
183 	std::vector<GLubyte> emptydata(size.width * size.height * bpp, 0);
184 
185 	// Clear errors before initializing.
186 	while (glGetError() != GL_NO_ERROR);
187 
188 	glTexImage2D(GL_TEXTURE_2D, 0, internalformat, size.width, size.height, 0,
189 	             format, GL_UNSIGNED_BYTE, &emptydata[0]);
190 
191 	if (glGetError() != GL_NO_ERROR)
192 	{
193 		if (!recreatetexture)
194 			gl.deleteTexture(t);
195 		throw love::Exception("Could not create font texture!");
196 	}
197 
198 	textureWidth  = size.width;
199 	textureHeight = size.height;
200 
201 	rowHeight = textureX = textureY = TEXTURE_PADDING;
202 
203 	prevmemsize = textureMemorySize;
204 	textureMemorySize += emptydata.size();
205 	gl.updateTextureMemorySize(prevmemsize, textureMemorySize);
206 
207 	// Re-add the old glyphs if we re-created the existing texture object.
208 	if (recreatetexture)
209 	{
210 		textureCacheID++;
211 
212 		std::vector<uint32> glyphstoadd;
213 
214 		for (const auto &glyphpair : glyphs)
215 			glyphstoadd.push_back(glyphpair.first);
216 
217 		glyphs.clear();
218 
219 		for (uint32 g : glyphstoadd)
220 			addGlyph(g);
221 	}
222 	else
223 		textures.push_back(t);
224 }
225 
getRasterizerGlyphData(uint32 glyph)226 love::font::GlyphData *Font::getRasterizerGlyphData(uint32 glyph)
227 {
228 	// Use spaces for the tab 'glyph'.
229 	if (glyph == 9 && useSpacesAsTab)
230 	{
231 		love::font::GlyphData *spacegd = rasterizers[0]->getGlyphData(32);
232 		love::font::GlyphData::Format fmt = spacegd->getFormat();
233 
234 		love::font::GlyphMetrics gm = {};
235 		gm.advance = spacegd->getAdvance() * SPACES_PER_TAB;
236 		gm.bearingX = spacegd->getBearingX();
237 		gm.bearingY = spacegd->getBearingY();
238 
239 		spacegd->release();
240 
241 		return new love::font::GlyphData(glyph, gm, fmt);
242 	}
243 
244 	for (const StrongRef<love::font::Rasterizer> &r : rasterizers)
245 	{
246 		if (r->hasGlyph(glyph))
247 			return r->getGlyphData(glyph);
248 	}
249 
250 	return rasterizers[0]->getGlyphData(glyph);
251 }
252 
addGlyph(uint32 glyph)253 const Font::Glyph &Font::addGlyph(uint32 glyph)
254 {
255 	StrongRef<love::font::GlyphData> gd(getRasterizerGlyphData(glyph), Acquire::NORETAIN);
256 
257 	int w = gd->getWidth();
258 	int h = gd->getHeight();
259 
260 	if (textureX + w + TEXTURE_PADDING > textureWidth)
261 	{
262 		// out of space - new row!
263 		textureX = TEXTURE_PADDING;
264 		textureY += rowHeight;
265 		rowHeight = TEXTURE_PADDING;
266 	}
267 
268 	if (textureY + h + TEXTURE_PADDING > textureHeight)
269 	{
270 		// totally out of space - new texture!
271 		createTexture();
272 
273 		// Makes sure the above code for checking if the glyph can fit at
274 		// the current position in the texture is run again for this glyph.
275 		return addGlyph(glyph);
276 	}
277 
278 	Glyph g;
279 
280 	g.texture = 0;
281 	g.spacing = gd->getAdvance();
282 
283 	memset(g.vertices, 0, sizeof(GlyphVertex) * 4);
284 
285 	// don't waste space for empty glyphs. also fixes a divide by zero bug with ATI drivers
286 	if (w > 0 && h > 0)
287 	{
288 		GLenum format = getTextureFormat(type);
289 		g.texture = textures.back();
290 
291 		gl.bindTexture(g.texture);
292 		glTexSubImage2D(GL_TEXTURE_2D, 0, textureX, textureY, w, h,
293 		                format, GL_UNSIGNED_BYTE, gd->getData());
294 
295 		double tX     = (double) textureX,     tY      = (double) textureY;
296 		double tWidth = (double) textureWidth, tHeight = (double) textureHeight;
297 
298 		Color c(255, 255, 255, 255);
299 
300 		// 0----2
301 		// |  / |
302 		// | /  |
303 		// 1----3
304 		const GlyphVertex verts[4] = {
305 			{float(0), float(0), normToUint16((tX+0)/tWidth), normToUint16((tY+0)/tHeight), c},
306 			{float(0), float(h), normToUint16((tX+0)/tWidth), normToUint16((tY+h)/tHeight), c},
307 			{float(w), float(0), normToUint16((tX+w)/tWidth), normToUint16((tY+0)/tHeight), c},
308 			{float(w), float(h), normToUint16((tX+w)/tWidth), normToUint16((tY+h)/tHeight), c}
309 		};
310 
311 		// Copy vertex data to the glyph and set proper bearing.
312 		for (int i = 0; i < 4; i++)
313 		{
314 			g.vertices[i] = verts[i];
315 			g.vertices[i].x += gd->getBearingX();
316 			g.vertices[i].y -= gd->getBearingY();
317 		}
318 
319 		textureX += w + TEXTURE_PADDING;
320 		rowHeight = std::max(rowHeight, h + TEXTURE_PADDING);
321 	}
322 
323 	const auto p = glyphs.insert(std::make_pair(glyph, g));
324 	return p.first->second;
325 }
326 
findGlyph(uint32 glyph)327 const Font::Glyph &Font::findGlyph(uint32 glyph)
328 {
329 	const auto it = glyphs.find(glyph);
330 
331 	if (it != glyphs.end())
332 		return it->second;
333 
334 	return addGlyph(glyph);
335 }
336 
getKerning(uint32 leftglyph,uint32 rightglyph)337 float Font::getKerning(uint32 leftglyph, uint32 rightglyph)
338 {
339 	uint64 packedglyphs = ((uint64) leftglyph << 32) | (uint64) rightglyph;
340 
341 	const auto it = kerning.find(packedglyphs);
342 	if (it != kerning.end())
343 		return it->second;
344 
345 	float k = rasterizers[0]->getKerning(leftglyph, rightglyph);
346 
347 	for (const auto &r : rasterizers)
348 	{
349 		if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph))
350 		{
351 			k = r->getKerning(leftglyph, rightglyph);
352 			break;
353 		}
354 	}
355 
356 	kerning[packedglyphs] = k;
357 	return k;
358 }
359 
getCodepointsFromString(const std::string & text,Codepoints & codepoints)360 void Font::getCodepointsFromString(const std::string &text, Codepoints &codepoints)
361 {
362 	codepoints.reserve(text.size());
363 
364 	try
365 	{
366 		utf8::iterator<std::string::const_iterator> i(text.begin(), text.begin(), text.end());
367 		utf8::iterator<std::string::const_iterator> end(text.end(), text.begin(), text.end());
368 
369 		while (i != end)
370 		{
371 			uint32 g = *i++;
372 			codepoints.push_back(g);
373 		}
374 	}
375 	catch (utf8::exception &e)
376 	{
377 		throw love::Exception("UTF-8 decoding error: %s", e.what());
378 	}
379 }
380 
getCodepointsFromString(const std::vector<ColoredString> & strs,ColoredCodepoints & codepoints)381 void Font::getCodepointsFromString(const std::vector<ColoredString> &strs, ColoredCodepoints &codepoints)
382 {
383 	if (strs.empty())
384 		return;
385 
386 	codepoints.cps.reserve(strs[0].str.size());
387 
388 	for (const ColoredString &cstr : strs)
389 	{
390 		// No need to add the color if the string is empty anyway, and the code
391 		// further on assumes no two colors share the same starting position.
392 		if (cstr.str.size() == 0)
393 			continue;
394 
395 		IndexedColor c = {cstr.color, (int) codepoints.cps.size()};
396 		codepoints.colors.push_back(c);
397 
398 		getCodepointsFromString(cstr.str, codepoints.cps);
399 	}
400 
401 	if (codepoints.colors.size() == 1)
402 	{
403 		IndexedColor c = codepoints.colors[0];
404 
405 		if (c.index == 0 && c.color == Color(255, 255, 255, 255))
406 			codepoints.colors.pop_back();
407 	}
408 }
409 
getHeight() const410 float Font::getHeight() const
411 {
412 	return (float) height;
413 }
414 
generateVertices(const ColoredCodepoints & codepoints,std::vector<GlyphVertex> & vertices,float extra_spacing,Vector offset,TextInfo * info)415 std::vector<Font::DrawCommand> Font::generateVertices(const ColoredCodepoints &codepoints, std::vector<GlyphVertex> &vertices, float extra_spacing, Vector offset, TextInfo *info)
416 {
417 	// Spacing counter and newline handling.
418 	float dx = offset.x;
419 	float dy = offset.y;
420 
421 	float lineheight = getBaseline();
422 	int maxwidth = 0;
423 
424 	// Keeps track of when we need to switch textures in our vertex array.
425 	std::vector<DrawCommand> commands;
426 
427 	// Pre-allocate space for the maximum possible number of vertices.
428 	size_t vertstartsize = vertices.size();
429 	vertices.reserve(vertstartsize + codepoints.cps.size() * 4);
430 
431 	uint32 prevglyph = 0;
432 
433 	Color curcolor(255, 255, 255, 255);
434 	int curcolori = -1;
435 	int ncolors = (int) codepoints.colors.size();
436 
437 	for (int i = 0; i < (int) codepoints.cps.size(); i++)
438 	{
439 		uint32 g = codepoints.cps[i];
440 
441 		if (curcolori + 1 < ncolors && codepoints.colors[curcolori + 1].index == i)
442 			curcolor = codepoints.colors[++curcolori].color;
443 
444 		if (g == '\n')
445 		{
446 			if (dx > maxwidth)
447 				maxwidth = (int) dx;
448 
449 			// Wrap newline, but do not print it.
450 			dy += floorf(getHeight() * getLineHeight() + 0.5f);
451 			dx = offset.x;
452 			continue;
453 		}
454 
455 		uint32 cacheid = textureCacheID;
456 
457 		const Glyph &glyph = findGlyph(g);
458 
459 		// If findGlyph invalidates the texture cache, re-start the loop.
460 		if (cacheid != textureCacheID)
461 		{
462 			i = 0;
463 			maxwidth = 0;
464 			dx = offset.x;
465 			dy = offset.y;
466 			commands.clear();
467 			vertices.resize(vertstartsize);
468 			prevglyph = 0;
469 			curcolori = -1;
470 			curcolor = Color(255, 255, 255, 255);
471 			continue;
472 		}
473 
474 		// Add kerning to the current horizontal offset.
475 		dx += getKerning(prevglyph, g);
476 
477 		if (glyph.texture != 0)
478 		{
479 			// Copy the vertices and set their colors and relative positions.
480 			for (int j = 0; j < 4; j++)
481 			{
482 				vertices.push_back(glyph.vertices[j]);
483 				vertices.back().x += dx;
484 				vertices.back().y += dy + lineheight;
485 				vertices.back().color = curcolor;
486 			}
487 
488 			// Check if glyph texture has changed since the last iteration.
489 			if (commands.empty() || commands.back().texture != glyph.texture)
490 			{
491 				// Add a new draw command if the texture has changed.
492 				DrawCommand cmd;
493 				cmd.startvertex = (int) vertices.size() - 4;
494 				cmd.vertexcount = 0;
495 				cmd.texture = glyph.texture;
496 				commands.push_back(cmd);
497 			}
498 
499 			commands.back().vertexcount += 4;
500 		}
501 
502 		// Advance the x position for the next glyph.
503 		dx += glyph.spacing;
504 
505 		// Account for extra spacing given to space characters.
506 		if (g == ' ' && extra_spacing != 0.0f)
507 			dx = floorf(dx + extra_spacing);
508 
509 		prevglyph = g;
510 
511 	}
512 
513 	const auto drawsort = [](const DrawCommand &a, const DrawCommand &b) -> bool
514 	{
515 		// Texture binds are expensive, so we should sort by that first.
516 		if (a.texture != b.texture)
517 			return a.texture < b.texture;
518 		else
519 			return a.startvertex < b.startvertex;
520 	};
521 
522 	std::sort(commands.begin(), commands.end(), drawsort);
523 
524 	if (dx > maxwidth)
525 		maxwidth = (int) dx;
526 
527 	if (info != nullptr)
528 	{
529 		info->width = maxwidth - offset.x;;
530 		info->height = (int) dy + (dx > 0.0f ? floorf(getHeight() * getLineHeight() + 0.5f) : 0) - offset.y;
531 	}
532 
533 	return commands;
534 }
535 
generateVertices(const std::string & text,std::vector<GlyphVertex> & vertices,float extra_spacing,Vector offset,TextInfo * info)536 std::vector<Font::DrawCommand> Font::generateVertices(const std::string &text, std::vector<GlyphVertex> &vertices, float extra_spacing, Vector offset, TextInfo *info)
537 {
538 	ColoredCodepoints codepoints;
539 	getCodepointsFromString(text, codepoints.cps);
540 	return generateVertices(codepoints, vertices, extra_spacing, offset, info);
541 }
542 
generateVerticesFormatted(const ColoredCodepoints & text,float wrap,AlignMode align,std::vector<GlyphVertex> & vertices,TextInfo * info)543 std::vector<Font::DrawCommand> Font::generateVerticesFormatted(const ColoredCodepoints &text, float wrap, AlignMode align, std::vector<GlyphVertex> &vertices, TextInfo *info)
544 {
545 
546 	wrap = std::max(wrap, 0.0f);
547 
548 	uint32 cacheid = textureCacheID;
549 
550 	std::vector<DrawCommand> drawcommands;
551 	vertices.reserve(text.cps.size() * 4);
552 
553 	std::vector<int> widths;
554 	std::vector<ColoredCodepoints> lines;
555 
556 	getWrap(text, wrap, lines, &widths);
557 
558 	float y = 0.0f;
559 	float maxwidth = 0.0f;
560 
561 	for (int i = 0; i < (int) lines.size(); i++)
562 	{
563 		const auto &line = lines[i];
564 
565 		float width = (float) widths[i];
566 		love::Vector offset(0.0f, floorf(y));
567 		float extraspacing = 0.0f;
568 
569 		maxwidth = std::max(width, maxwidth);
570 
571 		switch (align)
572 		{
573 		case ALIGN_RIGHT:
574 			offset.x = floorf(wrap - width);
575 			break;
576 		case ALIGN_CENTER:
577 			offset.x = floorf((wrap - width) / 2.0f);
578 			break;
579 		case ALIGN_JUSTIFY:
580 		{
581 			float numspaces = (float) std::count(line.cps.begin(), line.cps.end(), ' ');
582 			if (width < wrap && numspaces >= 1)
583 				extraspacing = (wrap - width) / numspaces;
584 			else
585 				extraspacing = 0.0f;
586 			break;
587 		}
588 		case ALIGN_LEFT:
589 		default:
590 			break;
591 		}
592 
593 		std::vector<DrawCommand> newcommands = generateVertices(line, vertices, extraspacing, offset);
594 
595 		if (!newcommands.empty())
596 		{
597 			auto firstcmd = newcommands.begin();
598 
599 			// If the first draw command in the new list has the same texture
600 			// as the last one in the existing list we're building and its
601 			// vertices are in-order, we can combine them (saving a draw call.)
602 			if (!drawcommands.empty())
603 			{
604 				auto prevcmd = drawcommands.back();
605 				if (prevcmd.texture == firstcmd->texture && (prevcmd.startvertex + prevcmd.vertexcount) == firstcmd->startvertex)
606 				{
607 					drawcommands.back().vertexcount += firstcmd->vertexcount;
608 					++firstcmd;
609 				}
610 			}
611 
612 			// Append the new draw commands to the list we're building.
613 			drawcommands.insert(drawcommands.end(), firstcmd, newcommands.end());
614 		}
615 
616 		y += getHeight() * getLineHeight();
617 	}
618 
619 	if (info != nullptr)
620 	{
621 		info->width = (int) maxwidth;
622 		info->height = (int) y;
623 	}
624 
625 	if (cacheid != textureCacheID)
626 	{
627 		vertices.clear();
628 		drawcommands = generateVerticesFormatted(text, wrap, align, vertices);
629 	}
630 
631 	return drawcommands;
632 }
633 
drawVertices(const std::vector<DrawCommand> & drawcommands,bool bufferedvertices)634 void Font::drawVertices(const std::vector<DrawCommand> &drawcommands, bool bufferedvertices)
635 {
636 	// Vertex attribute pointers need to be set before calling this function.
637 	// This assumes that the attribute pointers are constant for all vertices.
638 
639 	int totalverts = 0;
640 	for (const DrawCommand &cmd : drawcommands)
641 		totalverts = std::max(cmd.startvertex + cmd.vertexcount, totalverts);
642 
643 	if ((size_t) totalverts / 4 > quadIndices.getSize())
644 		quadIndices = QuadIndices((size_t) totalverts / 4);
645 
646 	gl.prepareDraw();
647 
648 	const GLenum gltype = quadIndices.getType();
649 	const size_t elemsize = quadIndices.getElementSize();
650 
651 	// We only get indices from the index buffer if we're also using vertex
652 	// buffers, because at least one graphics driver (the one for Kepler nvidia
653 	// GPUs in OS X 10.11) fails to render geometry if an index buffer is used
654 	// with client-side vertex arrays.
655 	if (bufferedvertices)
656 		quadIndices.getBuffer()->bind();
657 
658 	// We need a separate draw call for every section of the text which uses a
659 	// different texture than the previous section.
660 	for (const DrawCommand &cmd : drawcommands)
661 	{
662 		GLsizei count = (cmd.vertexcount / 4) * 6;
663 		size_t offset = (cmd.startvertex / 4) * 6 * elemsize;
664 
665 		// TODO: Use glDrawElementsBaseVertex when supported?
666 		gl.bindTexture(cmd.texture);
667 
668 		if (bufferedvertices)
669 			gl.drawElements(GL_TRIANGLES, count, gltype, quadIndices.getPointer(offset));
670 		else
671 			gl.drawElements(GL_TRIANGLES, count, gltype, quadIndices.getIndices(offset));
672 	}
673 
674 	if (bufferedvertices)
675 		quadIndices.getBuffer()->unbind();
676 }
677 
printv(const Matrix4 & t,const std::vector<DrawCommand> & drawcommands,const std::vector<GlyphVertex> & vertices)678 void Font::printv(const Matrix4 &t, const std::vector<DrawCommand> &drawcommands, const std::vector<GlyphVertex> &vertices)
679 {
680 	if (vertices.empty() || drawcommands.empty())
681 		return;
682 
683 	OpenGL::TempDebugGroup debuggroup("Font print");
684 
685 	OpenGL::TempTransform transform(gl);
686 	transform.get() *= t;
687 
688 	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(GlyphVertex), &vertices[0].x);
689 	glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_UNSIGNED_SHORT, GL_TRUE, sizeof(GlyphVertex), &vertices[0].s);
690 	glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(GlyphVertex), &vertices[0].color.r);
691 
692 	gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD | ATTRIBFLAG_COLOR);
693 
694 	drawVertices(drawcommands, false);
695 }
696 
print(const std::vector<ColoredString> & text,float x,float y,float angle,float sx,float sy,float ox,float oy,float kx,float ky)697 void Font::print(const std::vector<ColoredString> &text, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
698 {
699 	ColoredCodepoints codepoints;
700 	getCodepointsFromString(text, codepoints);
701 
702 	std::vector<GlyphVertex> vertices;
703 	std::vector<DrawCommand> drawcommands = generateVertices(codepoints, vertices);
704 
705 	Matrix4 t(x, y, angle, sx, sy, ox, oy, kx, ky);
706 
707 	printv(t, drawcommands, vertices);
708 }
709 
printf(const std::vector<ColoredString> & text,float x,float y,float wrap,AlignMode align,float angle,float sx,float sy,float ox,float oy,float kx,float ky)710 void Font::printf(const std::vector<ColoredString> &text, float x, float y, float wrap, AlignMode align, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
711 {
712 	ColoredCodepoints codepoints;
713 	getCodepointsFromString(text, codepoints);
714 
715 	std::vector<GlyphVertex> vertices;
716 	std::vector<DrawCommand> drawcommands = generateVerticesFormatted(codepoints, wrap, align, vertices);
717 
718 	Matrix4 t(x, y, angle, sx, sy, ox, oy, kx, ky);
719 
720 	printv(t, drawcommands, vertices);
721 }
722 
getWidth(const std::string & str)723 int Font::getWidth(const std::string &str)
724 {
725 	if (str.size() == 0) return 0;
726 
727 	std::istringstream iss(str);
728 	std::string line;
729 	int max_width = 0;
730 
731 	while (getline(iss, line, '\n'))
732 	{
733 		int width = 0;
734 		uint32 prevglyph = 0;
735 		try
736 		{
737 			utf8::iterator<std::string::const_iterator> i(line.begin(), line.begin(), line.end());
738 			utf8::iterator<std::string::const_iterator> end(line.end(), line.begin(), line.end());
739 
740 			while (i != end)
741 			{
742 				uint32 c = *i++;
743 
744 				const Glyph &g = findGlyph(c);
745 				width += g.spacing + getKerning(prevglyph, c);
746 
747 				prevglyph = c;
748 			}
749 		}
750 		catch (utf8::exception &e)
751 		{
752 			throw love::Exception("UTF-8 decoding error: %s", e.what());
753 		}
754 
755 		max_width = std::max(max_width, width);
756 	}
757 
758 	return max_width;
759 }
760 
getWidth(char character)761 int Font::getWidth(char character)
762 {
763 	const Glyph &g = findGlyph(character);
764 	return g.spacing;
765 }
766 
getWrap(const ColoredCodepoints & codepoints,float wraplimit,std::vector<ColoredCodepoints> & lines,std::vector<int> * linewidths)767 void Font::getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector<ColoredCodepoints> &lines, std::vector<int> *linewidths)
768 {
769 	// Per-line info.
770 	float width = 0.0f;
771 	float widthbeforelastspace = 0.0f;
772 	float widthoftrailingspace = 0.0f;
773 	uint32 prevglyph = 0;
774 
775 	int lastspaceindex = -1;
776 
777 	// Keeping the indexed colors "in sync" is a bit tricky, since we split
778 	// things up and we might skip some glyphs but we don't want to skip any
779 	// color which starts at those indices.
780 	Color curcolor(255, 255, 255, 255);
781 	bool addcurcolor = false;
782 	int curcolori = -1;
783 	int endcolori = (int) codepoints.colors.size() - 1;
784 
785 	// A wrapped line of text.
786 	ColoredCodepoints wline;
787 
788 	int i = 0;
789 	while (i < (int) codepoints.cps.size())
790 	{
791 		uint32 c = codepoints.cps[i];
792 
793 		// Determine the current color before doing anything else, to make sure
794 		// it's still applied to future glyphs even if this one is skipped.
795 		if (curcolori < endcolori && codepoints.colors[curcolori + 1].index == i)
796 		{
797 			curcolor = codepoints.colors[curcolori + 1].color;
798 			curcolori++;
799 			addcurcolor = true;
800 		}
801 
802 		// Split text at newlines.
803 		if (c == '\n')
804 		{
805 			lines.push_back(wline);
806 
807 			// Ignore the width of any trailing spaces, for individual lines.
808 			if (linewidths)
809 				linewidths->push_back(width - widthoftrailingspace);
810 
811 			// Make sure the new line keeps any color that was set previously.
812 			addcurcolor = true;
813 
814 			width = widthbeforelastspace = widthoftrailingspace = 0.0f;
815 			prevglyph = 0; // Reset kerning information.
816 			lastspaceindex = -1;
817 			wline.cps.clear();
818 			wline.colors.clear();
819 			i++;
820 
821 			continue;
822 		}
823 
824 		const Glyph &g = findGlyph(c);
825 		float charwidth = g.spacing + getKerning(prevglyph, c);
826 		float newwidth = width + charwidth;
827 
828 		// Wrap the line if it exceeds the wrap limit. Don't wrap yet if we're
829 		// processing a newline character, though.
830 		if (c != ' ' && newwidth > wraplimit)
831 		{
832 			// If this is the first character in the line and it exceeds the
833 			// limit, skip it completely.
834 			if (wline.cps.empty())
835 				i++;
836 			else if (lastspaceindex != -1)
837 			{
838 				// 'Rewind' to the last seen space, if the line has one.
839 				// FIXME: This could be more efficient...
840 				while (!wline.cps.empty() && wline.cps.back() != ' ')
841 					wline.cps.pop_back();
842 
843 				while (!wline.colors.empty() && wline.colors.back().index >= (int) wline.cps.size())
844 					wline.colors.pop_back();
845 
846 				// Also 'rewind' to the color that the last character is using.
847 				for (int colori = curcolori; colori >= 0; colori--)
848 				{
849 					if (codepoints.colors[colori].index <= lastspaceindex)
850 					{
851 						curcolor = codepoints.colors[colori].color;
852 						curcolori = colori;
853 						break;
854 					}
855 				}
856 
857 				// Ignore the width of trailing spaces in wrapped lines.
858 				width = widthbeforelastspace;
859 
860 				i = lastspaceindex;
861 				i++; // Start the next line after the space.
862 			}
863 
864 			lines.push_back(wline);
865 
866 			if (linewidths)
867 				linewidths->push_back(width);
868 
869 			addcurcolor = true;
870 
871 			prevglyph = 0;
872 			width = widthbeforelastspace = widthoftrailingspace = 0.0f;
873 			wline.cps.clear();
874 			wline.colors.clear();
875 			lastspaceindex = -1;
876 
877 			continue;
878 		}
879 
880 		if (prevglyph != ' ' && c == ' ')
881 			widthbeforelastspace = width;
882 
883 		width = newwidth;
884 		prevglyph = c;
885 
886 		if (addcurcolor)
887 		{
888 			wline.colors.push_back({curcolor, (int) wline.cps.size()});
889 			addcurcolor = false;
890 		}
891 
892 		wline.cps.push_back(c);
893 
894 		// Keep track of the last seen space, so we can "rewind" to it when
895 		// wrapping.
896 		if (c == ' ')
897 		{
898 			lastspaceindex = i;
899 			widthoftrailingspace += charwidth;
900 		}
901 		else if (c != '\n')
902 			widthoftrailingspace = 0.0f;
903 
904 		i++;
905 	}
906 
907 	// Push the last line.
908 	if (!wline.cps.empty())
909 	{
910 		lines.push_back(wline);
911 
912 		// Ignore the width of any trailing spaces, for individual lines.
913 		if (linewidths)
914 			linewidths->push_back(width - widthoftrailingspace);
915 	}
916 }
917 
getWrap(const std::vector<ColoredString> & text,float wraplimit,std::vector<std::string> & lines,std::vector<int> * linewidths)918 void Font::getWrap(const std::vector<ColoredString> &text, float wraplimit, std::vector<std::string> &lines, std::vector<int> *linewidths)
919 {
920 	ColoredCodepoints cps;
921 	getCodepointsFromString(text, cps);
922 
923 	std::vector<ColoredCodepoints> codepointlines;
924 	getWrap(cps, wraplimit, codepointlines, linewidths);
925 
926 	std::string line;
927 
928 	for (const ColoredCodepoints &codepoints : codepointlines)
929 	{
930 		line.clear();
931 		line.reserve(codepoints.cps.size());
932 
933 		for (uint32 codepoint : codepoints.cps)
934 		{
935 			char character[5] = {'\0'};
936 			char *end = utf8::unchecked::append(codepoint, character);
937 			line.append(character, end - character);
938 		}
939 
940 		lines.push_back(line);
941 	}
942 }
943 
setLineHeight(float height)944 void Font::setLineHeight(float height)
945 {
946 	lineHeight = height;
947 }
948 
getLineHeight() const949 float Font::getLineHeight() const
950 {
951 	return lineHeight;
952 }
953 
setFilter(const Texture::Filter & f)954 void Font::setFilter(const Texture::Filter &f)
955 {
956 	if (!Texture::validateFilter(f, false))
957 		throw love::Exception("Invalid texture filter.");
958 
959 	filter = f;
960 
961 	for (GLuint texture : textures)
962 	{
963 		gl.bindTexture(texture);
964 		gl.setTextureFilter(filter);
965 	}
966 }
967 
getFilter()968 const Texture::Filter &Font::getFilter()
969 {
970 	return filter;
971 }
972 
loadVolatile()973 bool Font::loadVolatile()
974 {
975 	createTexture();
976 	textureCacheID++;
977 	return true;
978 }
979 
unloadVolatile()980 void Font::unloadVolatile()
981 {
982 	// nuke everything from orbit
983 
984 	glyphs.clear();
985 
986 	for (GLuint texture : textures)
987 		gl.deleteTexture(texture);
988 
989 	textures.clear();
990 
991 	gl.updateTextureMemorySize(textureMemorySize, 0);
992 	textureMemorySize = 0;
993 }
994 
getAscent() const995 int Font::getAscent() const
996 {
997 	return rasterizers[0]->getAscent();
998 }
999 
getDescent() const1000 int Font::getDescent() const
1001 {
1002 	return rasterizers[0]->getDescent();
1003 }
1004 
getBaseline() const1005 float Font::getBaseline() const
1006 {
1007 	// 1.25 is magic line height for true type fonts
1008 	return (type == FONT_TRUETYPE) ? floorf(getHeight() / 1.25f + 0.5f) : 0.0f;
1009 }
1010 
hasGlyph(uint32 glyph) const1011 bool Font::hasGlyph(uint32 glyph) const
1012 {
1013 	for (const StrongRef<love::font::Rasterizer> &r : rasterizers)
1014 	{
1015 		if (r->hasGlyph(glyph))
1016 			return true;
1017 	}
1018 
1019 	return false;
1020 }
1021 
hasGlyphs(const std::string & text) const1022 bool Font::hasGlyphs(const std::string &text) const
1023 {
1024 	if (text.size() == 0)
1025 		return false;
1026 
1027 	try
1028 	{
1029 		utf8::iterator<std::string::const_iterator> i(text.begin(), text.begin(), text.end());
1030 		utf8::iterator<std::string::const_iterator> end(text.end(), text.begin(), text.end());
1031 
1032 		while (i != end)
1033 		{
1034 			uint32 codepoint = *i++;
1035 
1036 			if (!hasGlyph(codepoint))
1037 				return false;
1038 		}
1039 	}
1040 	catch (utf8::exception &e)
1041 	{
1042 		throw love::Exception("UTF-8 decoding error: %s", e.what());
1043 	}
1044 
1045 	return true;
1046 }
1047 
setFallbacks(const std::vector<Font * > & fallbacks)1048 void Font::setFallbacks(const std::vector<Font *> &fallbacks)
1049 {
1050 	for (const Font *f : fallbacks)
1051 	{
1052 		if (f->type != this->type)
1053 			throw love::Exception("Font fallbacks must be of the same font type.");
1054 	}
1055 
1056 	rasterizers.resize(1);
1057 
1058 	// NOTE: this won't invalidate already-rasterized glyphs.
1059 	for (const Font *f : fallbacks)
1060 		rasterizers.push_back(f->rasterizers[0]);
1061 }
1062 
getTextureCacheID() const1063 uint32 Font::getTextureCacheID() const
1064 {
1065 	return textureCacheID;
1066 }
1067 
getConstant(const char * in,AlignMode & out)1068 bool Font::getConstant(const char *in, AlignMode &out)
1069 {
1070 	return alignModes.find(in, out);
1071 }
1072 
getConstant(AlignMode in,const char * & out)1073 bool Font::getConstant(AlignMode in, const char  *&out)
1074 {
1075 	return alignModes.find(in, out);
1076 }
1077 
1078 StringMap<Font::AlignMode, Font::ALIGN_MAX_ENUM>::Entry Font::alignModeEntries[] =
1079 {
1080 	{ "left", ALIGN_LEFT },
1081 	{ "right", ALIGN_RIGHT },
1082 	{ "center", ALIGN_CENTER },
1083 	{ "justify", ALIGN_JUSTIFY },
1084 };
1085 
1086 StringMap<Font::AlignMode, Font::ALIGN_MAX_ENUM> Font::alignModes(Font::alignModeEntries, sizeof(Font::alignModeEntries));
1087 
1088 } // opengl
1089 } // graphics
1090 } // love
1091