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