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 #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.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 
normToUint16(double n)40 static inline uint16 normToUint16(double n)
41 {
42 	return (uint16) (n * LOVE_UINT16_MAX);
43 }
44 
45 love::Type Font::type("Font", &Object::type);
46 int Font::fontCount = 0;
47 
48 const vertex::CommonFormat Font::vertexFormat = vertex::CommonFormat::XYf_STus_RGBAub;
49 
Font(love::font::Rasterizer * r,const Texture::Filter & f)50 Font::Font(love::font::Rasterizer *r, const Texture::Filter &f)
51 	: rasterizers({r})
52 	, height(r->getHeight())
53 	, lineHeight(1)
54 	, textureWidth(128)
55 	, textureHeight(128)
56 	, filter(f)
57 	, dpiScale(r->getDPIScale())
58 	, useSpacesAsTab(false)
59 	, textureCacheID(0)
60 {
61 	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 	pixelFormat = gd->getFormat();
81 	gd->release();
82 
83 	if (!r->hasGlyph(9)) // No tab character in the Rasterizer.
84 		useSpacesAsTab = true;
85 
86 	loadVolatile();
87 	++fontCount;
88 }
89 
~Font()90 Font::~Font()
91 {
92 	--fontCount;
93 }
94 
getNextTextureSize() const95 Font::TextureSize Font::getNextTextureSize() const
96 {
97 	TextureSize size = {textureWidth, textureHeight};
98 
99 	int maxsize = 2048;
100 	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
101 	if (gfx != nullptr)
102 	{
103 		const auto &caps = gfx->getCapabilities();
104 		maxsize = (int) caps.limits[Graphics::LIMIT_TEXTURE_SIZE];
105 	}
106 
107 	int maxwidth  = std::min(8192, maxsize);
108 	int maxheight = std::min(4096, maxsize);
109 
110 	if (size.width * 2 <= maxwidth || size.height * 2 <= maxheight)
111 	{
112 		// {128, 128} -> {256, 128} -> {256, 256} -> {512, 256} -> etc.
113 		if (size.width == size.height)
114 			size.width *= 2;
115 		else
116 			size.height *= 2;
117 	}
118 
119 	return size;
120 }
121 
loadVolatile()122 bool Font::loadVolatile()
123 {
124 	textureCacheID++;
125 	glyphs.clear();
126 	images.clear();
127 	createTexture();
128 	return true;
129 }
130 
createTexture()131 void Font::createTexture()
132 {
133 	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
134 	gfx->flushStreamDraws();
135 
136 	Image *image = nullptr;
137 	TextureSize size = {textureWidth, textureHeight};
138 	TextureSize nextsize = getNextTextureSize();
139 	bool recreatetexture = false;
140 
141 	// If we have an existing texture already, we'll try replacing it with a
142 	// larger-sized one rather than creating a second one. Having a single
143 	// texture reduces texture switches and draw calls when rendering.
144 	if ((nextsize.width > size.width || nextsize.height > size.height) && !images.empty())
145 	{
146 		recreatetexture = true;
147 		size = nextsize;
148 		images.pop_back();
149 	}
150 
151 	Image::Settings settings;
152 	image = gfx->newImage(TEXTURE_2D, pixelFormat, size.width, size.height, 1, settings);
153 	image->setFilter(filter);
154 
155 	{
156 		size_t bpp = getPixelFormatSize(pixelFormat);
157 		size_t pixelcount = size.width * size.height;
158 
159 		// Initialize the texture with transparent white for Luminance-Alpha
160 		// formats (since we keep luminance constant and vary alpha in those
161 		// glyphs), and transparent black otherwise.
162 		std::vector<uint8> emptydata(pixelcount * bpp, 0);
163 
164 		if (pixelFormat == PIXELFORMAT_LA8)
165 		{
166 			for (size_t i = 0; i < pixelcount; i++)
167 				emptydata[i * 2 + 0] = 255;
168 		}
169 
170 		Rect rect = {0, 0, size.width, size.height};
171 		image->replacePixels(emptydata.data(), emptydata.size(), 0, 0, rect, false);
172 	}
173 
174 	images.emplace_back(image, Acquire::NORETAIN);
175 
176 	textureWidth  = size.width;
177 	textureHeight = size.height;
178 
179 	rowHeight = textureX = textureY = TEXTURE_PADDING;
180 
181 	// Re-add the old glyphs if we re-created the existing texture object.
182 	if (recreatetexture)
183 	{
184 		textureCacheID++;
185 
186 		std::vector<uint32> glyphstoadd;
187 
188 		for (const auto &glyphpair : glyphs)
189 			glyphstoadd.push_back(glyphpair.first);
190 
191 		glyphs.clear();
192 
193 		for (uint32 g : glyphstoadd)
194 			addGlyph(g);
195 	}
196 }
197 
unloadVolatile()198 void Font::unloadVolatile()
199 {
200 	glyphs.clear();
201 	images.clear();
202 }
203 
getRasterizerGlyphData(uint32 glyph)204 love::font::GlyphData *Font::getRasterizerGlyphData(uint32 glyph)
205 {
206 	// Use spaces for the tab 'glyph'.
207 	if (glyph == 9 && useSpacesAsTab)
208 	{
209 		love::font::GlyphData *spacegd = rasterizers[0]->getGlyphData(32);
210 		PixelFormat fmt = spacegd->getFormat();
211 
212 		love::font::GlyphMetrics gm = {};
213 		gm.advance = spacegd->getAdvance() * SPACES_PER_TAB;
214 		gm.bearingX = spacegd->getBearingX();
215 		gm.bearingY = spacegd->getBearingY();
216 
217 		spacegd->release();
218 
219 		return new love::font::GlyphData(glyph, gm, fmt);
220 	}
221 
222 	for (const StrongRef<love::font::Rasterizer> &r : rasterizers)
223 	{
224 		if (r->hasGlyph(glyph))
225 			return r->getGlyphData(glyph);
226 	}
227 
228 	return rasterizers[0]->getGlyphData(glyph);
229 }
230 
addGlyph(uint32 glyph)231 const Font::Glyph &Font::addGlyph(uint32 glyph)
232 {
233 	StrongRef<love::font::GlyphData> gd(getRasterizerGlyphData(glyph), Acquire::NORETAIN);
234 
235 	int w = gd->getWidth();
236 	int h = gd->getHeight();
237 
238 	if (w + TEXTURE_PADDING * 2 < textureWidth && h + TEXTURE_PADDING * 2 < textureHeight)
239 	{
240 		if (textureX + w + TEXTURE_PADDING > textureWidth)
241 		{
242 			// Out of space - new row!
243 			textureX = TEXTURE_PADDING;
244 			textureY += rowHeight;
245 			rowHeight = TEXTURE_PADDING;
246 		}
247 
248 		if (textureY + h + TEXTURE_PADDING > textureHeight)
249 		{
250 			// Totally out of space - new texture!
251 			createTexture();
252 
253 			// Makes sure the above code for checking if the glyph can fit at
254 			// the current position in the texture is run again for this glyph.
255 			return addGlyph(glyph);
256 		}
257 	}
258 
259 	Glyph g;
260 
261 	g.texture = 0;
262 	g.spacing = floorf(gd->getAdvance() / dpiScale + 0.5f);
263 
264 	memset(g.vertices, 0, sizeof(GlyphVertex) * 4);
265 
266 	// Don't waste space for empty glyphs.
267 	if (w > 0 && h > 0)
268 	{
269 		Image *image = images.back();
270 		g.texture = image;
271 
272 		Rect rect = {textureX, textureY, gd->getWidth(), gd->getHeight()};
273 		image->replacePixels(gd->getData(), gd->getSize(), 0, 0, rect, false);
274 
275 		double tX     = (double) textureX,     tY      = (double) textureY;
276 		double tWidth = (double) textureWidth, tHeight = (double) textureHeight;
277 
278 		Color32 c(255, 255, 255, 255);
279 
280 		// Extrude the quad borders by 1 pixel. We have an extra pixel of
281 		// transparent padding in the texture atlas, so the quad extrusion will
282 		// add some antialiasing at the edges of the quad.
283 		int o = 1;
284 
285 		// 0---2
286 		// | / |
287 		// 1---3
288 		const GlyphVertex verts[4] =
289 		{
290 			{float(-o),      float(-o),      normToUint16((tX-o)/tWidth),   normToUint16((tY-o)/tHeight),   c},
291 			{float(-o),      (h+o)/dpiScale, normToUint16((tX-o)/tWidth),   normToUint16((tY+h+o)/tHeight), c},
292 			{(w+o)/dpiScale, float(-o),      normToUint16((tX+w+o)/tWidth), normToUint16((tY-o)/tHeight),   c},
293 			{(w+o)/dpiScale, (h+o)/dpiScale, normToUint16((tX+w+o)/tWidth), normToUint16((tY+h+o)/tHeight), c}
294 		};
295 
296 		// Copy vertex data to the glyph and set proper bearing.
297 		for (int i = 0; i < 4; i++)
298 		{
299 			g.vertices[i] = verts[i];
300 			g.vertices[i].x += gd->getBearingX() / dpiScale;
301 			g.vertices[i].y -= gd->getBearingY() / dpiScale;
302 		}
303 
304 		textureX += w + TEXTURE_PADDING;
305 		rowHeight = std::max(rowHeight, h + TEXTURE_PADDING);
306 	}
307 
308 	glyphs[glyph] = g;
309 	return glyphs[glyph];
310 }
311 
findGlyph(uint32 glyph)312 const Font::Glyph &Font::findGlyph(uint32 glyph)
313 {
314 	const auto it = glyphs.find(glyph);
315 
316 	if (it != glyphs.end())
317 		return it->second;
318 
319 	return addGlyph(glyph);
320 }
321 
getKerning(uint32 leftglyph,uint32 rightglyph)322 float Font::getKerning(uint32 leftglyph, uint32 rightglyph)
323 {
324 	uint64 packedglyphs = ((uint64) leftglyph << 32) | (uint64) rightglyph;
325 
326 	const auto it = kerning.find(packedglyphs);
327 	if (it != kerning.end())
328 		return it->second;
329 
330 	float k = rasterizers[0]->getKerning(leftglyph, rightglyph);
331 
332 	for (const auto &r : rasterizers)
333 	{
334 		if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph))
335 		{
336 			k = floorf(r->getKerning(leftglyph, rightglyph) / dpiScale + 0.5f);
337 			break;
338 		}
339 	}
340 
341 	kerning[packedglyphs] = k;
342 	return k;
343 }
344 
getCodepointsFromString(const std::string & text,Codepoints & codepoints)345 void Font::getCodepointsFromString(const std::string &text, Codepoints &codepoints)
346 {
347 	codepoints.reserve(text.size());
348 
349 	try
350 	{
351 		utf8::iterator<std::string::const_iterator> i(text.begin(), text.begin(), text.end());
352 		utf8::iterator<std::string::const_iterator> end(text.end(), text.begin(), text.end());
353 
354 		while (i != end)
355 		{
356 			uint32 g = *i++;
357 			codepoints.push_back(g);
358 		}
359 	}
360 	catch (utf8::exception &e)
361 	{
362 		throw love::Exception("UTF-8 decoding error: %s", e.what());
363 	}
364 }
365 
getCodepointsFromString(const std::vector<ColoredString> & strs,ColoredCodepoints & codepoints)366 void Font::getCodepointsFromString(const std::vector<ColoredString> &strs, ColoredCodepoints &codepoints)
367 {
368 	if (strs.empty())
369 		return;
370 
371 	codepoints.cps.reserve(strs[0].str.size());
372 
373 	for (const ColoredString &cstr : strs)
374 	{
375 		// No need to add the color if the string is empty anyway, and the code
376 		// further on assumes no two colors share the same starting position.
377 		if (cstr.str.size() == 0)
378 			continue;
379 
380 		IndexedColor c = {cstr.color, (int) codepoints.cps.size()};
381 		codepoints.colors.push_back(c);
382 
383 		getCodepointsFromString(cstr.str, codepoints.cps);
384 	}
385 
386 	if (codepoints.colors.size() == 1)
387 	{
388 		IndexedColor c = codepoints.colors[0];
389 
390 		if (c.index == 0 && c.color == Colorf(1.0f, 1.0f, 1.0f, 1.0f))
391 			codepoints.colors.pop_back();
392 	}
393 }
394 
getHeight() const395 float Font::getHeight() const
396 {
397 	return (float) floorf(height / dpiScale + 0.5f);
398 }
399 
generateVertices(const ColoredCodepoints & codepoints,const Colorf & constantcolor,std::vector<GlyphVertex> & vertices,float extra_spacing,Vector2 offset,TextInfo * info)400 std::vector<Font::DrawCommand> Font::generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantcolor, std::vector<GlyphVertex> &vertices, float extra_spacing, Vector2 offset, TextInfo *info)
401 {
402 	// Spacing counter and newline handling.
403 	float dx = offset.x;
404 	float dy = offset.y;
405 
406 	float heightoffset = 0.0f;
407 
408 	if (rasterizers[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE)
409 		heightoffset = getBaseline();
410 
411 	int maxwidth = 0;
412 
413 	// Keeps track of when we need to switch textures in our vertex array.
414 	std::vector<DrawCommand> commands;
415 
416 	// Pre-allocate space for the maximum possible number of vertices.
417 	size_t vertstartsize = vertices.size();
418 	vertices.reserve(vertstartsize + codepoints.cps.size() * 4);
419 
420 	uint32 prevglyph = 0;
421 
422 	Colorf linearconstantcolor = gammaCorrectColor(constantcolor);
423 
424 	Color32 curcolor = toColor32(constantcolor);
425 	int curcolori = -1;
426 	int ncolors = (int) codepoints.colors.size();
427 
428 	for (int i = 0; i < (int) codepoints.cps.size(); i++)
429 	{
430 		uint32 g = codepoints.cps[i];
431 
432 		if (curcolori + 1 < ncolors && codepoints.colors[curcolori + 1].index == i)
433 		{
434 			Colorf c = codepoints.colors[++curcolori].color;
435 
436 			c.r = std::min(std::max(c.r, 0.0f), 1.0f);
437 			c.g = std::min(std::max(c.g, 0.0f), 1.0f);
438 			c.b = std::min(std::max(c.b, 0.0f), 1.0f);
439 			c.a = std::min(std::max(c.a, 0.0f), 1.0f);
440 
441 			gammaCorrectColor(c);
442 			c *= linearconstantcolor;
443 			unGammaCorrectColor(c);
444 
445 			curcolor = toColor32(c);
446 		}
447 
448 		if (g == '\n')
449 		{
450 			if (dx > maxwidth)
451 				maxwidth = (int) dx;
452 
453 			// Wrap newline, but do not print it.
454 			dy += floorf(getHeight() * getLineHeight() + 0.5f);
455 			dx = offset.x;
456 			prevglyph = 0;
457 			continue;
458 		}
459 
460 		// Ignore carriage returns
461 		if (g == '\r')
462 			continue;
463 
464 		uint32 cacheid = textureCacheID;
465 
466 		const Glyph &glyph = findGlyph(g);
467 
468 		// If findGlyph invalidates the texture cache, re-start the loop.
469 		if (cacheid != textureCacheID)
470 		{
471 			i = -1; // The next iteration will increment this to 0.
472 			maxwidth = 0;
473 			dx = offset.x;
474 			dy = offset.y;
475 			commands.clear();
476 			vertices.resize(vertstartsize);
477 			prevglyph = 0;
478 			curcolori = -1;
479 			curcolor = toColor32(constantcolor);
480 			continue;
481 		}
482 
483 		// Add kerning to the current horizontal offset.
484 		dx += getKerning(prevglyph, g);
485 
486 		if (glyph.texture != nullptr)
487 		{
488 			// Copy the vertices and set their colors and relative positions.
489 			for (int j = 0; j < 4; j++)
490 			{
491 				vertices.push_back(glyph.vertices[j]);
492 				vertices.back().x += dx;
493 				vertices.back().y += dy + heightoffset;
494 				vertices.back().color = curcolor;
495 			}
496 
497 			// Check if glyph texture has changed since the last iteration.
498 			if (commands.empty() || commands.back().texture != glyph.texture)
499 			{
500 				// Add a new draw command if the texture has changed.
501 				DrawCommand cmd;
502 				cmd.startvertex = (int) vertices.size() - 4;
503 				cmd.vertexcount = 0;
504 				cmd.texture = glyph.texture;
505 				commands.push_back(cmd);
506 			}
507 
508 			commands.back().vertexcount += 4;
509 		}
510 
511 		// Advance the x position for the next glyph.
512 		dx += glyph.spacing;
513 
514 		// Account for extra spacing given to space characters.
515 		if (g == ' ' && extra_spacing != 0.0f)
516 			dx = floorf(dx + extra_spacing);
517 
518 		prevglyph = g;
519 	}
520 
521 	const auto drawsort = [](const DrawCommand &a, const DrawCommand &b) -> bool
522 	{
523 		// Texture binds are expensive, so we should sort by that first.
524 		if (a.texture != b.texture)
525 			return a.texture < b.texture;
526 		else
527 			return a.startvertex < b.startvertex;
528 	};
529 
530 	std::sort(commands.begin(), commands.end(), drawsort);
531 
532 	if (dx > maxwidth)
533 		maxwidth = (int) dx;
534 
535 	if (info != nullptr)
536 	{
537 		info->width = maxwidth - offset.x;
538 		info->height = (int) dy + (dx > 0.0f ? floorf(getHeight() * getLineHeight() + 0.5f) : 0) - offset.y;
539 	}
540 
541 	return commands;
542 }
543 
generateVerticesFormatted(const ColoredCodepoints & text,const Colorf & constantcolor,float wrap,AlignMode align,std::vector<GlyphVertex> & vertices,TextInfo * info)544 std::vector<Font::DrawCommand> Font::generateVerticesFormatted(const ColoredCodepoints &text, const Colorf &constantcolor, float wrap, AlignMode align, std::vector<GlyphVertex> &vertices, TextInfo *info)
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::Vector2 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, constantcolor, 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, constantcolor, wrap, align, vertices);
629 	}
630 
631 	return drawcommands;
632 }
633 
printv(graphics::Graphics * gfx,const Matrix4 & t,const std::vector<DrawCommand> & drawcommands,const std::vector<GlyphVertex> & vertices)634 void Font::printv(graphics::Graphics *gfx, const Matrix4 &t, const std::vector<DrawCommand> &drawcommands, const std::vector<GlyphVertex> &vertices)
635 {
636 	if (vertices.empty() || drawcommands.empty())
637 		return;
638 
639 	Matrix4 m(gfx->getTransform(), t);
640 
641 	for (const DrawCommand &cmd : drawcommands)
642 	{
643 		Graphics::StreamDrawCommand streamcmd;
644 		streamcmd.formats[0] = vertexFormat;
645 		streamcmd.indexMode = vertex::TriangleIndexMode::QUADS;
646 		streamcmd.vertexCount = cmd.vertexcount;
647 		streamcmd.texture = cmd.texture;
648 
649 		Graphics::StreamVertexData data = gfx->requestStreamDraw(streamcmd);
650 		GlyphVertex *vertexdata = (GlyphVertex *) data.stream[0];
651 
652 		memcpy(vertexdata, &vertices[cmd.startvertex], sizeof(GlyphVertex) * cmd.vertexcount);
653 		m.transformXY(vertexdata, &vertices[cmd.startvertex], cmd.vertexcount);
654 	}
655 }
656 
print(graphics::Graphics * gfx,const std::vector<ColoredString> & text,const Matrix4 & m,const Colorf & constantcolor)657 void Font::print(graphics::Graphics *gfx, const std::vector<ColoredString> &text, const Matrix4 &m, const Colorf &constantcolor)
658 {
659 	ColoredCodepoints codepoints;
660 	getCodepointsFromString(text, codepoints);
661 
662 	std::vector<GlyphVertex> vertices;
663 	std::vector<DrawCommand> drawcommands = generateVertices(codepoints, constantcolor, vertices);
664 
665 	printv(gfx, m, drawcommands, vertices);
666 }
667 
printf(graphics::Graphics * gfx,const std::vector<ColoredString> & text,float wrap,AlignMode align,const Matrix4 & m,const Colorf & constantcolor)668 void Font::printf(graphics::Graphics *gfx, const std::vector<ColoredString> &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantcolor)
669 {
670 	ColoredCodepoints codepoints;
671 	getCodepointsFromString(text, codepoints);
672 
673 	std::vector<GlyphVertex> vertices;
674 	std::vector<DrawCommand> drawcommands = generateVerticesFormatted(codepoints, constantcolor, wrap, align, vertices);
675 
676 	printv(gfx, m, drawcommands, vertices);
677 }
678 
getWidth(const std::string & str)679 int Font::getWidth(const std::string &str)
680 {
681 	if (str.size() == 0) return 0;
682 
683 	std::istringstream iss(str);
684 	std::string line;
685 	int max_width = 0;
686 
687 	while (getline(iss, line, '\n'))
688 	{
689 		int width = 0;
690 		uint32 prevglyph = 0;
691 		try
692 		{
693 			utf8::iterator<std::string::const_iterator> i(line.begin(), line.begin(), line.end());
694 			utf8::iterator<std::string::const_iterator> end(line.end(), line.begin(), line.end());
695 
696 			while (i != end)
697 			{
698 				uint32 c = *i++;
699 
700 				// Ignore carriage returns
701 				if (c == '\r')
702 					continue;
703 
704 				const Glyph &g = findGlyph(c);
705 				width += g.spacing + getKerning(prevglyph, c);
706 
707 				prevglyph = c;
708 			}
709 		}
710 		catch (utf8::exception &e)
711 		{
712 			throw love::Exception("UTF-8 decoding error: %s", e.what());
713 		}
714 
715 		max_width = std::max(max_width, width);
716 	}
717 
718 	return max_width;
719 }
720 
getWidth(char character)721 int Font::getWidth(char character)
722 {
723 	const Glyph &g = findGlyph(character);
724 	return g.spacing;
725 }
726 
getWrap(const ColoredCodepoints & codepoints,float wraplimit,std::vector<ColoredCodepoints> & lines,std::vector<int> * linewidths)727 void Font::getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector<ColoredCodepoints> &lines, std::vector<int> *linewidths)
728 {
729 	// Per-line info.
730 	float width = 0.0f;
731 	float widthbeforelastspace = 0.0f;
732 	float widthoftrailingspace = 0.0f;
733 	uint32 prevglyph = 0;
734 
735 	int lastspaceindex = -1;
736 
737 	// Keeping the indexed colors "in sync" is a bit tricky, since we split
738 	// things up and we might skip some glyphs but we don't want to skip any
739 	// color which starts at those indices.
740 	Colorf curcolor(1.0f, 1.0f, 1.0f, 1.0f);
741 	bool addcurcolor = false;
742 	int curcolori = -1;
743 	int endcolori = (int) codepoints.colors.size() - 1;
744 
745 	// A wrapped line of text.
746 	ColoredCodepoints wline;
747 
748 	int i = 0;
749 	while (i < (int) codepoints.cps.size())
750 	{
751 		uint32 c = codepoints.cps[i];
752 
753 		// Determine the current color before doing anything else, to make sure
754 		// it's still applied to future glyphs even if this one is skipped.
755 		if (curcolori < endcolori && codepoints.colors[curcolori + 1].index == i)
756 		{
757 			curcolor = codepoints.colors[curcolori + 1].color;
758 			curcolori++;
759 			addcurcolor = true;
760 		}
761 
762 		// Split text at newlines.
763 		if (c == '\n')
764 		{
765 			lines.push_back(wline);
766 
767 			// Ignore the width of any trailing spaces, for individual lines.
768 			if (linewidths)
769 				linewidths->push_back(width - widthoftrailingspace);
770 
771 			// Make sure the new line keeps any color that was set previously.
772 			addcurcolor = true;
773 
774 			width = widthbeforelastspace = widthoftrailingspace = 0.0f;
775 			prevglyph = 0; // Reset kerning information.
776 			lastspaceindex = -1;
777 			wline.cps.clear();
778 			wline.colors.clear();
779 			i++;
780 
781 			continue;
782 		}
783 
784 		// Ignore carriage returns
785 		if (c == '\r')
786 		{
787 			i++;
788 			continue;
789 		}
790 
791 		const Glyph &g = findGlyph(c);
792 		float charwidth = g.spacing + getKerning(prevglyph, c);
793 		float newwidth = width + charwidth;
794 
795 		// Wrap the line if it exceeds the wrap limit. Don't wrap yet if we're
796 		// processing a newline character, though.
797 		if (c != ' ' && newwidth > wraplimit)
798 		{
799 			// If this is the first character in the line and it exceeds the
800 			// limit, skip it completely.
801 			if (wline.cps.empty())
802 				i++;
803 			else if (lastspaceindex != -1)
804 			{
805 				// 'Rewind' to the last seen space, if the line has one.
806 				// FIXME: This could be more efficient...
807 				while (!wline.cps.empty() && wline.cps.back() != ' ')
808 					wline.cps.pop_back();
809 
810 				while (!wline.colors.empty() && wline.colors.back().index >= (int) wline.cps.size())
811 					wline.colors.pop_back();
812 
813 				// Also 'rewind' to the color that the last character is using.
814 				for (int colori = curcolori; colori >= 0; colori--)
815 				{
816 					if (codepoints.colors[colori].index <= lastspaceindex)
817 					{
818 						curcolor = codepoints.colors[colori].color;
819 						curcolori = colori;
820 						break;
821 					}
822 				}
823 
824 				// Ignore the width of trailing spaces in wrapped lines.
825 				width = widthbeforelastspace;
826 
827 				i = lastspaceindex;
828 				i++; // Start the next line after the space.
829 			}
830 
831 			lines.push_back(wline);
832 
833 			if (linewidths)
834 				linewidths->push_back(width);
835 
836 			addcurcolor = true;
837 
838 			prevglyph = 0;
839 			width = widthbeforelastspace = widthoftrailingspace = 0.0f;
840 			wline.cps.clear();
841 			wline.colors.clear();
842 			lastspaceindex = -1;
843 
844 			continue;
845 		}
846 
847 		if (prevglyph != ' ' && c == ' ')
848 			widthbeforelastspace = width;
849 
850 		width = newwidth;
851 		prevglyph = c;
852 
853 		if (addcurcolor)
854 		{
855 			wline.colors.push_back({curcolor, (int) wline.cps.size()});
856 			addcurcolor = false;
857 		}
858 
859 		wline.cps.push_back(c);
860 
861 		// Keep track of the last seen space, so we can "rewind" to it when
862 		// wrapping.
863 		if (c == ' ')
864 		{
865 			lastspaceindex = i;
866 			widthoftrailingspace += charwidth;
867 		}
868 		else if (c != '\n')
869 			widthoftrailingspace = 0.0f;
870 
871 		i++;
872 	}
873 
874 	// Push the last line.
875 	if (!wline.cps.empty())
876 	{
877 		lines.push_back(wline);
878 
879 		// Ignore the width of any trailing spaces, for individual lines.
880 		if (linewidths)
881 			linewidths->push_back(width - widthoftrailingspace);
882 	}
883 }
884 
getWrap(const std::vector<ColoredString> & text,float wraplimit,std::vector<std::string> & lines,std::vector<int> * linewidths)885 void Font::getWrap(const std::vector<ColoredString> &text, float wraplimit, std::vector<std::string> &lines, std::vector<int> *linewidths)
886 {
887 	ColoredCodepoints cps;
888 	getCodepointsFromString(text, cps);
889 
890 	std::vector<ColoredCodepoints> codepointlines;
891 	getWrap(cps, wraplimit, codepointlines, linewidths);
892 
893 	std::string line;
894 
895 	for (const ColoredCodepoints &codepoints : codepointlines)
896 	{
897 		line.clear();
898 		line.reserve(codepoints.cps.size());
899 
900 		for (uint32 codepoint : codepoints.cps)
901 		{
902 			char character[5] = {'\0'};
903 			char *end = utf8::unchecked::append(codepoint, character);
904 			line.append(character, end - character);
905 		}
906 
907 		lines.push_back(line);
908 	}
909 }
910 
setLineHeight(float height)911 void Font::setLineHeight(float height)
912 {
913 	lineHeight = height;
914 }
915 
getLineHeight() const916 float Font::getLineHeight() const
917 {
918 	return lineHeight;
919 }
920 
setFilter(const Texture::Filter & f)921 void Font::setFilter(const Texture::Filter &f)
922 {
923 	for (const auto &image : images)
924 		image->setFilter(f);
925 
926 	filter = f;
927 }
928 
getFilter() const929 const Texture::Filter &Font::getFilter() const
930 {
931 	return filter;
932 }
933 
getAscent() const934 int Font::getAscent() const
935 {
936 	return floorf(rasterizers[0]->getAscent() / dpiScale + 0.5f);
937 }
938 
getDescent() const939 int Font::getDescent() const
940 {
941 	return floorf(rasterizers[0]->getDescent() / dpiScale + 0.5f);
942 }
943 
getBaseline() const944 float Font::getBaseline() const
945 {
946 	float ascent = getAscent();
947 	if (ascent != 0.0f)
948 		return ascent;
949 	else if (rasterizers[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE)
950 		return floorf(getHeight() / 1.25f + 0.5f); // 1.25 is magic line height for true type fonts
951 	else
952 		return 0.0f;
953 }
954 
hasGlyph(uint32 glyph) const955 bool Font::hasGlyph(uint32 glyph) const
956 {
957 	for (const StrongRef<love::font::Rasterizer> &r : rasterizers)
958 	{
959 		if (r->hasGlyph(glyph))
960 			return true;
961 	}
962 
963 	return false;
964 }
965 
hasGlyphs(const std::string & text) const966 bool Font::hasGlyphs(const std::string &text) const
967 {
968 	if (text.size() == 0)
969 		return false;
970 
971 	try
972 	{
973 		utf8::iterator<std::string::const_iterator> i(text.begin(), text.begin(), text.end());
974 		utf8::iterator<std::string::const_iterator> end(text.end(), text.begin(), text.end());
975 
976 		while (i != end)
977 		{
978 			uint32 codepoint = *i++;
979 
980 			if (!hasGlyph(codepoint))
981 				return false;
982 		}
983 	}
984 	catch (utf8::exception &e)
985 	{
986 		throw love::Exception("UTF-8 decoding error: %s", e.what());
987 	}
988 
989 	return true;
990 }
991 
setFallbacks(const std::vector<Font * > & fallbacks)992 void Font::setFallbacks(const std::vector<Font *> &fallbacks)
993 {
994 	for (const Font *f : fallbacks)
995 	{
996 		if (f->rasterizers[0]->getDataType() != this->rasterizers[0]->getDataType())
997 			throw love::Exception("Font fallbacks must be of the same font type.");
998 	}
999 
1000 	rasterizers.resize(1);
1001 
1002 	// NOTE: this won't invalidate already-rasterized glyphs.
1003 	for (const Font *f : fallbacks)
1004 		rasterizers.push_back(f->rasterizers[0]);
1005 }
1006 
getDPIScale() const1007 float Font::getDPIScale() const
1008 {
1009 	return dpiScale;
1010 }
1011 
getTextureCacheID() const1012 uint32 Font::getTextureCacheID() const
1013 {
1014 	return textureCacheID;
1015 }
1016 
getConstant(const char * in,AlignMode & out)1017 bool Font::getConstant(const char *in, AlignMode &out)
1018 {
1019 	return alignModes.find(in, out);
1020 }
1021 
getConstant(AlignMode in,const char * & out)1022 bool Font::getConstant(AlignMode in, const char  *&out)
1023 {
1024 	return alignModes.find(in, out);
1025 }
1026 
getConstants(AlignMode)1027 std::vector<std::string> Font::getConstants(AlignMode)
1028 {
1029 	return alignModes.getNames();
1030 }
1031 
1032 StringMap<Font::AlignMode, Font::ALIGN_MAX_ENUM>::Entry Font::alignModeEntries[] =
1033 {
1034 	{ "left", ALIGN_LEFT },
1035 	{ "right", ALIGN_RIGHT },
1036 	{ "center", ALIGN_CENTER },
1037 	{ "justify", ALIGN_JUSTIFY },
1038 };
1039 
1040 StringMap<Font::AlignMode, Font::ALIGN_MAX_ENUM> Font::alignModes(Font::alignModeEntries, sizeof(Font::alignModeEntries));
1041 
1042 } // graphics
1043 } // love
1044