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#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
35namespace love
36{
37namespace graphics
38{
39
40static inline uint16 normToUint16(double n)
41{
42 return (uint16) (n * LOVE_UINT16_MAX);
43}
44
45love::Type Font::type("Font", &Object::type);
46int Font::fontCount = 0;
47
48const vertex::CommonFormat Font::vertexFormat = vertex::CommonFormat::XYf_STus_RGBAub;
49
50Font::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
90Font::~Font()
91{
92 --fontCount;
93}
94
95Font::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
122bool Font::loadVolatile()
123{
124 textureCacheID++;
125 glyphs.clear();
126 images.clear();
127 createTexture();
128 return true;
129}
130
131void 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
198void Font::unloadVolatile()
199{
200 glyphs.clear();
201 images.clear();
202}
203
204love::font::GlyphData *Font::getRasterizerGlyphData(uint32 glyph, float &dpiscale)
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 dpiscale = rasterizers[0]->getDPIScale();
220 return new love::font::GlyphData(glyph, gm, fmt);
221 }
222
223 for (const StrongRef<love::font::Rasterizer> &r : rasterizers)
224 {
225 if (r->hasGlyph(glyph))
226 {
227 dpiscale = r->getDPIScale();
228 return r->getGlyphData(glyph);
229 }
230 }
231
232 dpiscale = rasterizers[0]->getDPIScale();
233 return rasterizers[0]->getGlyphData(glyph);
234}
235
236const Font::Glyph &Font::addGlyph(uint32 glyph)
237{
238 float glyphdpiscale = getDPIScale();
239 StrongRef<love::font::GlyphData> gd(getRasterizerGlyphData(glyph, glyphdpiscale), Acquire::NORETAIN);
240
241 int w = gd->getWidth();
242 int h = gd->getHeight();
243
244 if (w + TEXTURE_PADDING * 2 < textureWidth && h + TEXTURE_PADDING * 2 < textureHeight)
245 {
246 if (textureX + w + TEXTURE_PADDING > textureWidth)
247 {
248 // Out of space - new row!
249 textureX = TEXTURE_PADDING;
250 textureY += rowHeight;
251 rowHeight = TEXTURE_PADDING;
252 }
253
254 if (textureY + h + TEXTURE_PADDING > textureHeight)
255 {
256 // Totally out of space - new texture!
257 createTexture();
258
259 // Makes sure the above code for checking if the glyph can fit at
260 // the current position in the texture is run again for this glyph.
261 return addGlyph(glyph);
262 }
263 }
264
265 Glyph g;
266
267 g.texture = 0;
268 g.spacing = floorf(gd->getAdvance() / glyphdpiscale + 0.5f);
269
270 memset(g.vertices, 0, sizeof(GlyphVertex) * 4);
271
272 // Don't waste space for empty glyphs.
273 if (w > 0 && h > 0)
274 {
275 Image *image = images.back();
276 g.texture = image;
277
278 Rect rect = {textureX, textureY, gd->getWidth(), gd->getHeight()};
279 image->replacePixels(gd->getData(), gd->getSize(), 0, 0, rect, false);
280
281 double tX = (double) textureX, tY = (double) textureY;
282 double tWidth = (double) textureWidth, tHeight = (double) textureHeight;
283
284 Color32 c(255, 255, 255, 255);
285
286 // Extrude the quad borders by 1 pixel. We have an extra pixel of
287 // transparent padding in the texture atlas, so the quad extrusion will
288 // add some antialiasing at the edges of the quad.
289 int o = 1;
290
291 // 0---2
292 // | / |
293 // 1---3
294 const GlyphVertex verts[4] =
295 {
296 {float(-o), float(-o), normToUint16((tX-o)/tWidth), normToUint16((tY-o)/tHeight), c},
297 {float(-o), (h+o)/glyphdpiscale, normToUint16((tX-o)/tWidth), normToUint16((tY+h+o)/tHeight), c},
298 {(w+o)/glyphdpiscale, float(-o), normToUint16((tX+w+o)/tWidth), normToUint16((tY-o)/tHeight), c},
299 {(w+o)/glyphdpiscale, (h+o)/glyphdpiscale, normToUint16((tX+w+o)/tWidth), normToUint16((tY+h+o)/tHeight), c}
300 };
301
302 // Copy vertex data to the glyph and set proper bearing.
303 for (int i = 0; i < 4; i++)
304 {
305 g.vertices[i] = verts[i];
306 g.vertices[i].x += gd->getBearingX() / glyphdpiscale;
307 g.vertices[i].y -= gd->getBearingY() / glyphdpiscale;
308 }
309
310 textureX += w + TEXTURE_PADDING;
311 rowHeight = std::max(rowHeight, h + TEXTURE_PADDING);
312 }
313
314 glyphs[glyph] = g;
315 return glyphs[glyph];
316}
317
318const Font::Glyph &Font::findGlyph(uint32 glyph)
319{
320 const auto it = glyphs.find(glyph);
321
322 if (it != glyphs.end())
323 return it->second;
324
325 return addGlyph(glyph);
326}
327
328float Font::getKerning(uint32 leftglyph, uint32 rightglyph)
329{
330 uint64 packedglyphs = ((uint64) leftglyph << 32) | (uint64) rightglyph;
331
332 const auto it = kerning.find(packedglyphs);
333 if (it != kerning.end())
334 return it->second;
335
336 float k = floorf(rasterizers[0]->getKerning(leftglyph, rightglyph) / dpiScale + 0.5f);
337
338 for (const auto &r : rasterizers)
339 {
340 if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph))
341 {
342 k = floorf(r->getKerning(leftglyph, rightglyph) / r->getDPIScale() + 0.5f);
343 break;
344 }
345 }
346
347 kerning[packedglyphs] = k;
348 return k;
349}
350
351float Font::getKerning(const std::string &leftchar, const std::string &rightchar)
352{
353 uint32 left = 0;
354 uint32 right = 0;
355
356 try
357 {
358 left = utf8::peek_next(leftchar.begin(), leftchar.end());
359 right = utf8::peek_next(rightchar.begin(), rightchar.end());
360 }
361 catch (utf8::exception &e)
362 {
363 throw love::Exception("UTF-8 decoding error: %s", e.what());
364 }
365
366 return getKerning(left, right);
367}
368
369void Font::getCodepointsFromString(const std::string &text, Codepoints &codepoints)
370{
371 codepoints.reserve(text.size());
372
373 try
374 {
375 utf8::iterator<std::string::const_iterator> i(text.begin(), text.begin(), text.end());
376 utf8::iterator<std::string::const_iterator> end(text.end(), text.begin(), text.end());
377
378 while (i != end)
379 {
380 uint32 g = *i++;
381 codepoints.push_back(g);
382 }
383 }
384 catch (utf8::exception &e)
385 {
386 throw love::Exception("UTF-8 decoding error: %s", e.what());
387 }
388}
389
390void Font::getCodepointsFromString(const std::vector<ColoredString> &strs, ColoredCodepoints &codepoints)
391{
392 if (strs.empty())
393 return;
394
395 codepoints.cps.reserve(strs[0].str.size());
396
397 for (const ColoredString &cstr : strs)
398 {
399 // No need to add the color if the string is empty anyway, and the code
400 // further on assumes no two colors share the same starting position.
401 if (cstr.str.size() == 0)
402 continue;
403
404 IndexedColor c = {cstr.color, (int) codepoints.cps.size()};
405 codepoints.colors.push_back(c);
406
407 getCodepointsFromString(cstr.str, codepoints.cps);
408 }
409
410 if (codepoints.colors.size() == 1)
411 {
412 IndexedColor c = codepoints.colors[0];
413
414 if (c.index == 0 && c.color == Colorf(1.0f, 1.0f, 1.0f, 1.0f))
415 codepoints.colors.pop_back();
416 }
417}
418
419float Font::getHeight() const
420{
421 return (float) floorf(height / dpiScale + 0.5f);
422}
423
424std::vector<Font::DrawCommand> Font::generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantcolor, std::vector<GlyphVertex> &vertices, float extra_spacing, Vector2 offset, TextInfo *info)
425{
426 // Spacing counter and newline handling.
427 float dx = offset.x;
428 float dy = offset.y;
429
430 float heightoffset = 0.0f;
431
432 if (rasterizers[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE)
433 heightoffset = getBaseline();
434
435 int maxwidth = 0;
436
437 // Keeps track of when we need to switch textures in our vertex array.
438 std::vector<DrawCommand> commands;
439
440 // Pre-allocate space for the maximum possible number of vertices.
441 size_t vertstartsize = vertices.size();
442 vertices.reserve(vertstartsize + codepoints.cps.size() * 4);
443
444 uint32 prevglyph = 0;
445
446 Colorf linearconstantcolor = gammaCorrectColor(constantcolor);
447
448 Color32 curcolor = toColor32(constantcolor);
449 int curcolori = -1;
450 int ncolors = (int) codepoints.colors.size();
451
452 for (int i = 0; i < (int) codepoints.cps.size(); i++)
453 {
454 uint32 g = codepoints.cps[i];
455
456 if (curcolori + 1 < ncolors && codepoints.colors[curcolori + 1].index == i)
457 {
458 Colorf c = codepoints.colors[++curcolori].color;
459
460 c.r = std::min(std::max(c.r, 0.0f), 1.0f);
461 c.g = std::min(std::max(c.g, 0.0f), 1.0f);
462 c.b = std::min(std::max(c.b, 0.0f), 1.0f);
463 c.a = std::min(std::max(c.a, 0.0f), 1.0f);
464
465 gammaCorrectColor(c);
466 c *= linearconstantcolor;
467 unGammaCorrectColor(c);
468
469 curcolor = toColor32(c);
470 }
471
472 if (g == '\n')
473 {
474 if (dx > maxwidth)
475 maxwidth = (int) dx;
476
477 // Wrap newline, but do not print it.
478 dy += floorf(getHeight() * getLineHeight() + 0.5f);
479 dx = offset.x;
480 prevglyph = 0;
481 continue;
482 }
483
484 // Ignore carriage returns
485 if (g == '\r')
486 continue;
487
488 uint32 cacheid = textureCacheID;
489
490 const Glyph &glyph = findGlyph(g);
491
492 // If findGlyph invalidates the texture cache, re-start the loop.
493 if (cacheid != textureCacheID)
494 {
495 i = -1; // The next iteration will increment this to 0.
496 maxwidth = 0;
497 dx = offset.x;
498 dy = offset.y;
499 commands.clear();
500 vertices.resize(vertstartsize);
501 prevglyph = 0;
502 curcolori = -1;
503 curcolor = toColor32(constantcolor);
504 continue;
505 }
506
507 // Add kerning to the current horizontal offset.
508 dx += getKerning(prevglyph, g);
509
510 if (glyph.texture != nullptr)
511 {
512 // Copy the vertices and set their colors and relative positions.
513 for (int j = 0; j < 4; j++)
514 {
515 vertices.push_back(glyph.vertices[j]);
516 vertices.back().x += dx;
517 vertices.back().y += dy + heightoffset;
518 vertices.back().color = curcolor;
519 }
520
521 // Check if glyph texture has changed since the last iteration.
522 if (commands.empty() || commands.back().texture != glyph.texture)
523 {
524 // Add a new draw command if the texture has changed.
525 DrawCommand cmd;
526 cmd.startvertex = (int) vertices.size() - 4;
527 cmd.vertexcount = 0;
528 cmd.texture = glyph.texture;
529 commands.push_back(cmd);
530 }
531
532 commands.back().vertexcount += 4;
533 }
534
535 // Advance the x position for the next glyph.
536 dx += glyph.spacing;
537
538 // Account for extra spacing given to space characters.
539 if (g == ' ' && extra_spacing != 0.0f)
540 dx = floorf(dx + extra_spacing);
541
542 prevglyph = g;
543 }
544
545 const auto drawsort = [](const DrawCommand &a, const DrawCommand &b) -> bool
546 {
547 // Texture binds are expensive, so we should sort by that first.
548 if (a.texture != b.texture)
549 return a.texture < b.texture;
550 else
551 return a.startvertex < b.startvertex;
552 };
553
554 std::sort(commands.begin(), commands.end(), drawsort);
555
556 if (dx > maxwidth)
557 maxwidth = (int) dx;
558
559 if (info != nullptr)
560 {
561 info->width = maxwidth - offset.x;
562 info->height = (int) dy + (dx > 0.0f ? floorf(getHeight() * getLineHeight() + 0.5f) : 0) - offset.y;
563 }
564
565 return commands;
566}
567
568std::vector<Font::DrawCommand> Font::generateVerticesFormatted(const ColoredCodepoints &text, const Colorf &constantcolor, float wrap, AlignMode align, std::vector<GlyphVertex> &vertices, TextInfo *info)
569{
570 wrap = std::max(wrap, 0.0f);
571
572 uint32 cacheid = textureCacheID;
573
574 std::vector<DrawCommand> drawcommands;
575 vertices.reserve(text.cps.size() * 4);
576
577 std::vector<int> widths;
578 std::vector<ColoredCodepoints> lines;
579
580 getWrap(text, wrap, lines, &widths);
581
582 float y = 0.0f;
583 float maxwidth = 0.0f;
584
585 for (int i = 0; i < (int) lines.size(); i++)
586 {
587 const auto &line = lines[i];
588
589 float width = (float) widths[i];
590 love::Vector2 offset(0.0f, floorf(y));
591 float extraspacing = 0.0f;
592
593 maxwidth = std::max(width, maxwidth);
594
595 switch (align)
596 {
597 case ALIGN_RIGHT:
598 offset.x = floorf(wrap - width);
599 break;
600 case ALIGN_CENTER:
601 offset.x = floorf((wrap - width) / 2.0f);
602 break;
603 case ALIGN_JUSTIFY:
604 {
605 float numspaces = (float) std::count(line.cps.begin(), line.cps.end(), ' ');
606 if (width < wrap && numspaces >= 1)
607 extraspacing = (wrap - width) / numspaces;
608 else
609 extraspacing = 0.0f;
610 break;
611 }
612 case ALIGN_LEFT:
613 default:
614 break;
615 }
616
617 std::vector<DrawCommand> newcommands = generateVertices(line, constantcolor, vertices, extraspacing, offset);
618
619 if (!newcommands.empty())
620 {
621 auto firstcmd = newcommands.begin();
622
623 // If the first draw command in the new list has the same texture
624 // as the last one in the existing list we're building and its
625 // vertices are in-order, we can combine them (saving a draw call.)
626 if (!drawcommands.empty())
627 {
628 auto prevcmd = drawcommands.back();
629 if (prevcmd.texture == firstcmd->texture && (prevcmd.startvertex + prevcmd.vertexcount) == firstcmd->startvertex)
630 {
631 drawcommands.back().vertexcount += firstcmd->vertexcount;
632 ++firstcmd;
633 }
634 }
635
636 // Append the new draw commands to the list we're building.
637 drawcommands.insert(drawcommands.end(), firstcmd, newcommands.end());
638 }
639
640 y += getHeight() * getLineHeight();
641 }
642
643 if (info != nullptr)
644 {
645 info->width = (int) maxwidth;
646 info->height = (int) y;
647 }
648
649 if (cacheid != textureCacheID)
650 {
651 vertices.clear();
652 drawcommands = generateVerticesFormatted(text, constantcolor, wrap, align, vertices);
653 }
654
655 return drawcommands;
656}
657
658void Font::printv(graphics::Graphics *gfx, const Matrix4 &t, const std::vector<DrawCommand> &drawcommands, const std::vector<GlyphVertex> &vertices)
659{
660 if (vertices.empty() || drawcommands.empty())
661 return;
662
663 Matrix4 m(gfx->getTransform(), t);
664
665 for (const DrawCommand &cmd : drawcommands)
666 {
667 Graphics::StreamDrawCommand streamcmd;
668 streamcmd.formats[0] = vertexFormat;
669 streamcmd.indexMode = vertex::TriangleIndexMode::QUADS;
670 streamcmd.vertexCount = cmd.vertexcount;
671 streamcmd.texture = cmd.texture;
672
673 Graphics::StreamVertexData data = gfx->requestStreamDraw(streamcmd);
674 GlyphVertex *vertexdata = (GlyphVertex *) data.stream[0];
675
676 memcpy(vertexdata, &vertices[cmd.startvertex], sizeof(GlyphVertex) * cmd.vertexcount);
677 m.transformXY(vertexdata, &vertices[cmd.startvertex], cmd.vertexcount);
678 }
679}
680
681void Font::print(graphics::Graphics *gfx, const std::vector<ColoredString> &text, const Matrix4 &m, const Colorf &constantcolor)
682{
683 ColoredCodepoints codepoints;
684 getCodepointsFromString(text, codepoints);
685
686 std::vector<GlyphVertex> vertices;
687 std::vector<DrawCommand> drawcommands = generateVertices(codepoints, constantcolor, vertices);
688
689 printv(gfx, m, drawcommands, vertices);
690}
691
692void Font::printf(graphics::Graphics *gfx, const std::vector<ColoredString> &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantcolor)
693{
694 ColoredCodepoints codepoints;
695 getCodepointsFromString(text, codepoints);
696
697 std::vector<GlyphVertex> vertices;
698 std::vector<DrawCommand> drawcommands = generateVerticesFormatted(codepoints, constantcolor, wrap, align, vertices);
699
700 printv(gfx, m, drawcommands, vertices);
701}
702
703int Font::getWidth(const std::string &str)
704{
705 if (str.size() == 0) return 0;
706
707 std::istringstream iss(str);
708 std::string line;
709 int max_width = 0;
710
711 while (getline(iss, line, '\n'))
712 {
713 int width = 0;
714 uint32 prevglyph = 0;
715 try
716 {
717 utf8::iterator<std::string::const_iterator> i(line.begin(), line.begin(), line.end());
718 utf8::iterator<std::string::const_iterator> end(line.end(), line.begin(), line.end());
719
720 while (i != end)
721 {
722 uint32 c = *i++;
723
724 // Ignore carriage returns
725 if (c == '\r')
726 continue;
727
728 const Glyph &g = findGlyph(c);
729 width += g.spacing + getKerning(prevglyph, c);
730
731 prevglyph = c;
732 }
733 }
734 catch (utf8::exception &e)
735 {
736 throw love::Exception("UTF-8 decoding error: %s", e.what());
737 }
738
739 max_width = std::max(max_width, width);
740 }
741
742 return max_width;
743}
744
745int Font::getWidth(uint32 glyph)
746{
747 const Glyph &g = findGlyph(glyph);
748 return g.spacing;
749}
750
751void Font::getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector<ColoredCodepoints> &lines, std::vector<int> *linewidths)
752{
753 // Per-line info.
754 float width = 0.0f;
755 float widthbeforelastspace = 0.0f;
756 float widthoftrailingspace = 0.0f;
757 uint32 prevglyph = 0;
758
759 int lastspaceindex = -1;
760
761 // Keeping the indexed colors "in sync" is a bit tricky, since we split
762 // things up and we might skip some glyphs but we don't want to skip any
763 // color which starts at those indices.
764 Colorf curcolor(1.0f, 1.0f, 1.0f, 1.0f);
765 bool addcurcolor = false;
766 int curcolori = -1;
767 int endcolori = (int) codepoints.colors.size() - 1;
768
769 // A wrapped line of text.
770 ColoredCodepoints wline;
771
772 int i = 0;
773 while (i < (int) codepoints.cps.size())
774 {
775 uint32 c = codepoints.cps[i];
776
777 // Determine the current color before doing anything else, to make sure
778 // it's still applied to future glyphs even if this one is skipped.
779 if (curcolori < endcolori && codepoints.colors[curcolori + 1].index == i)
780 {
781 curcolor = codepoints.colors[curcolori + 1].color;
782 curcolori++;
783 addcurcolor = true;
784 }
785
786 // Split text at newlines.
787 if (c == '\n')
788 {
789 lines.push_back(wline);
790
791 // Ignore the width of any trailing spaces, for individual lines.
792 if (linewidths)
793 linewidths->push_back(width - widthoftrailingspace);
794
795 // Make sure the new line keeps any color that was set previously.
796 addcurcolor = true;
797
798 width = widthbeforelastspace = widthoftrailingspace = 0.0f;
799 prevglyph = 0; // Reset kerning information.
800 lastspaceindex = -1;
801 wline.cps.clear();
802 wline.colors.clear();
803 i++;
804
805 continue;
806 }
807
808 // Ignore carriage returns
809 if (c == '\r')
810 {
811 i++;
812 continue;
813 }
814
815 const Glyph &g = findGlyph(c);
816 float charwidth = g.spacing + getKerning(prevglyph, c);
817 float newwidth = width + charwidth;
818
819 // Wrap the line if it exceeds the wrap limit. Don't wrap yet if we're
820 // processing a newline character, though.
821 if (c != ' ' && newwidth > wraplimit)
822 {
823 // If this is the first character in the line and it exceeds the
824 // limit, skip it completely.
825 if (wline.cps.empty())
826 i++;
827 else if (lastspaceindex != -1)
828 {
829 // 'Rewind' to the last seen space, if the line has one.
830 // FIXME: This could be more efficient...
831 while (!wline.cps.empty() && wline.cps.back() != ' ')
832 wline.cps.pop_back();
833
834 while (!wline.colors.empty() && wline.colors.back().index >= (int) wline.cps.size())
835 wline.colors.pop_back();
836
837 // Also 'rewind' to the color that the last character is using.
838 for (int colori = curcolori; colori >= 0; colori--)
839 {
840 if (codepoints.colors[colori].index <= lastspaceindex)
841 {
842 curcolor = codepoints.colors[colori].color;
843 curcolori = colori;
844 break;
845 }
846 }
847
848 // Ignore the width of trailing spaces in wrapped lines.
849 width = widthbeforelastspace;
850
851 i = lastspaceindex;
852 i++; // Start the next line after the space.
853 }
854
855 lines.push_back(wline);
856
857 if (linewidths)
858 linewidths->push_back(width);
859
860 addcurcolor = true;
861
862 prevglyph = 0;
863 width = widthbeforelastspace = widthoftrailingspace = 0.0f;
864 wline.cps.clear();
865 wline.colors.clear();
866 lastspaceindex = -1;
867
868 continue;
869 }
870
871 if (prevglyph != ' ' && c == ' ')
872 widthbeforelastspace = width;
873
874 width = newwidth;
875 prevglyph = c;
876
877 if (addcurcolor)
878 {
879 wline.colors.push_back({curcolor, (int) wline.cps.size()});
880 addcurcolor = false;
881 }
882
883 wline.cps.push_back(c);
884
885 // Keep track of the last seen space, so we can "rewind" to it when
886 // wrapping.
887 if (c == ' ')
888 {
889 lastspaceindex = i;
890 widthoftrailingspace += charwidth;
891 }
892 else if (c != '\n')
893 widthoftrailingspace = 0.0f;
894
895 i++;
896 }
897
898 // Push the last line.
899 lines.push_back(wline);
900
901 // Ignore the width of any trailing spaces, for individual lines.
902 if (linewidths)
903 linewidths->push_back(width - widthoftrailingspace);
904}
905
906void Font::getWrap(const std::vector<ColoredString> &text, float wraplimit, std::vector<std::string> &lines, std::vector<int> *linewidths)
907{
908 ColoredCodepoints cps;
909 getCodepointsFromString(text, cps);
910
911 std::vector<ColoredCodepoints> codepointlines;
912 getWrap(cps, wraplimit, codepointlines, linewidths);
913
914 std::string line;
915
916 for (const ColoredCodepoints &codepoints : codepointlines)
917 {
918 line.clear();
919 line.reserve(codepoints.cps.size());
920
921 for (uint32 codepoint : codepoints.cps)
922 {
923 char character[5] = {'\0'};
924 char *end = utf8::unchecked::append(codepoint, character);
925 line.append(character, end - character);
926 }
927
928 lines.push_back(line);
929 }
930}
931
932void Font::setLineHeight(float height)
933{
934 lineHeight = height;
935}
936
937float Font::getLineHeight() const
938{
939 return lineHeight;
940}
941
942void Font::setFilter(const Texture::Filter &f)
943{
944 for (const auto &image : images)
945 image->setFilter(f);
946
947 filter = f;
948}
949
950const Texture::Filter &Font::getFilter() const
951{
952 return filter;
953}
954
955int Font::getAscent() const
956{
957 return floorf(rasterizers[0]->getAscent() / dpiScale + 0.5f);
958}
959
960int Font::getDescent() const
961{
962 return floorf(rasterizers[0]->getDescent() / dpiScale + 0.5f);
963}
964
965float Font::getBaseline() const
966{
967 float ascent = getAscent();
968 if (ascent != 0.0f)
969 return ascent;
970 else if (rasterizers[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE)
971 return floorf(getHeight() / 1.25f + 0.5f); // 1.25 is magic line height for true type fonts
972 else
973 return 0.0f;
974}
975
976bool Font::hasGlyph(uint32 glyph) const
977{
978 for (const StrongRef<love::font::Rasterizer> &r : rasterizers)
979 {
980 if (r->hasGlyph(glyph))
981 return true;
982 }
983
984 return false;
985}
986
987bool Font::hasGlyphs(const std::string &text) const
988{
989 if (text.size() == 0)
990 return false;
991
992 try
993 {
994 utf8::iterator<std::string::const_iterator> i(text.begin(), text.begin(), text.end());
995 utf8::iterator<std::string::const_iterator> end(text.end(), text.begin(), text.end());
996
997 while (i != end)
998 {
999 uint32 codepoint = *i++;
1000
1001 if (!hasGlyph(codepoint))
1002 return false;
1003 }
1004 }
1005 catch (utf8::exception &e)
1006 {
1007 throw love::Exception("UTF-8 decoding error: %s", e.what());
1008 }
1009
1010 return true;
1011}
1012
1013void Font::setFallbacks(const std::vector<Font *> &fallbacks)
1014{
1015 for (const Font *f : fallbacks)
1016 {
1017 if (f->rasterizers[0]->getDataType() != this->rasterizers[0]->getDataType())
1018 throw love::Exception("Font fallbacks must be of the same font type.");
1019 }
1020
1021 rasterizers.resize(1);
1022
1023 // NOTE: this won't invalidate already-rasterized glyphs.
1024 for (const Font *f : fallbacks)
1025 rasterizers.push_back(f->rasterizers[0]);
1026}
1027
1028float Font::getDPIScale() const
1029{
1030 return dpiScale;
1031}
1032
1033uint32 Font::getTextureCacheID() const
1034{
1035 return textureCacheID;
1036}
1037
1038bool Font::getConstant(const char *in, AlignMode &out)
1039{
1040 return alignModes.find(in, out);
1041}
1042
1043bool Font::getConstant(AlignMode in, const char *&out)
1044{
1045 return alignModes.find(in, out);
1046}
1047
1048std::vector<std::string> Font::getConstants(AlignMode)
1049{
1050 return alignModes.getNames();
1051}
1052
1053StringMap<Font::AlignMode, Font::ALIGN_MAX_ENUM>::Entry Font::alignModeEntries[] =
1054{
1055 { "left", ALIGN_LEFT },
1056 { "right", ALIGN_RIGHT },
1057 { "center", ALIGN_CENTER },
1058 { "justify", ALIGN_JUSTIFY },
1059};
1060
1061StringMap<Font::AlignMode, Font::ALIGN_MAX_ENUM> Font::alignModes(Font::alignModeEntries, sizeof(Font::alignModeEntries));
1062
1063} // graphics
1064} // love
1065