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