GCC Code Coverage Report


./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
0 of 221, 0 excluded
0.0%
Functions:
0 of 26, 0 excluded
0.0%
Branches:
0 of 210, 0 excluded
0.0%

libs/render/src/eu/render/font.cc
Line Branch Exec Source
1 #include "eu/render/font.h"
2
3
4 #include <memory>
5 #include <map>
6 #include <algorithm>
7 #include <iostream>
8
9 #include "eustb_rect_pack.h"
10
11 #include "eu/base/cint.h"
12 #include "eu/log/log.h"
13 // #include "eu/core/image_draw.h"
14 #include "eu/core/utf8.h"
15 #include "eu/assert/assert.h"
16 #include "eu/core/image.h"
17 // #include "eu/render/stdutils.h"
18 // #include "eu/base/stringmerger.h"
19
20 // #include "files/font.h"
21
22 #include "eu/render/texture.h"
23 // #include "eu/render/spriterender.h"
24 // #include "eu/render/texturecache.h"
25
26 #include "canvas.h"
27 #include "dependency_glad.h"
28 #include "eu/base/memorychunk.h"
29
30
31 namespace eu::render
32 {
33 ✗ Glyph::Glyph
34 (
35 const Rect& sprite,
36 const Rect& texture,
37 int ch,
38 float ad
39 ✗ )
40 ✗ : sprite_rect(sprite)
41 ✗ , texture_rect(texture)
42 ✗ , code_point(ch)
43 ✗ , advance(ad)
44 {
45 ✗ }
46
47
48 std::pair<Rect, Rect>
49 ✗ construct_character_rects
50 (
51 const stbrp_rect& packed_rect,
52 const core::LoadedGlyph& glyph,
53 int image_width,
54 int image_height,
55 int half_margin
56 )
57 {
58 ✗ const int vert_left = glyph.bearing_x;
59 ✗ const int vert_right = vert_left + glyph.image.width;
60 ✗ const int vert_top = glyph.bearing_y;
61 ✗ const int vert_bottom = vert_top - std::max(1, glyph.image.height);
62
63 ✗ const stbrp_coord uv_left = packed_rect.x + half_margin;
64 ✗ const stbrp_coord uv_right = uv_left + packed_rect.w - half_margin * 2;
65 ✗ const stbrp_coord uv_bottom = packed_rect.y + half_margin;
66 ✗ const stbrp_coord uv_top = uv_bottom + std::max<int>(1, packed_rect.h - half_margin * 2);
67
68 // todo(Gustav): add ability to be a quad for tighter fit
69 ✗ ASSERTX(vert_top > vert_bottom, vert_top, vert_bottom, glyph.code_point);
70 ✗ ASSERTX(uv_top > uv_bottom, uv_top, uv_bottom, glyph.code_point);
71
72 ✗ const auto iw = float_from_int(image_width);
73 ✗ const auto ih = float_from_int(image_height);
74
75 const auto sprite = Rect::from_left_right_top_bottom
76 ✗ (
77 float_from_int(vert_left),
78 float_from_int(vert_right),
79 float_from_int(vert_top),
80 float_from_int(vert_bottom)
81 );
82 const auto texture = Rect::from_left_right_top_bottom
83 ✗ (
84 ✗ float_from_int(uv_left) / iw,
85 ✗ float_from_int(uv_right) / iw,
86 ✗ float_from_int(uv_top) / ih,
87 ✗ float_from_int(uv_bottom) / ih
88 );
89
90 // scale to make sure bake resolution is separate from rendering size
91 ✗ const float scale = 1.0f / glyph.size;
92 ✗ return std::make_pair(sprite.with_scale(scale, scale), texture);
93 ✗ }
94
95
96 ////////////////////////////////////////////////////////////////////////////////
97
98
99 ✗ DrawableFont::DrawableFont
100 (
101 const MemoryChunk& file_memory
102 ✗ )
103 {
104 // todo(Gustav): too long, break up
105 ✗ const int texture_width = 512;
106 ✗ const int texture_height = 512;
107
108 ✗ core::LoadedFont fontchars;
109
110 fontchars.load_characters_from_font
111 ✗ (
112 file_memory,
113 48,
114 // todo(Gustav): use a range instead
115 ✗ "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-=_+[]{}|;:'\",.<>/?`~ "
116 );
117
118
119 // const auto image_font = get_characters_from_single_image(fs, image);
120 // fontchars.combine_with(image_font);
121
122 // todo(Gustav): add more sources, built in image font or images
123
124 // the half margin between glyphs in the final texture
125 ✗ const int half_margin = 1;
126
127 // todo(Gustav): use core/pack.h instead
128
129 // pack char textures to a single texture
130 ✗ const int num_rects = int_from_sizet(fontchars.codepoint_to_glyph.size());
131 ✗ std::vector<stbrp_rect> packed_rects(num_rects);
132 ✗ std::map<int, int> id_to_codepoint;
133 {
134 ✗ int index = 0;
135 ✗ for(const auto& [codepoint, glyph]: fontchars.codepoint_to_glyph)
136 {
137 ✗ stbrp_rect& r = packed_rects[index];
138 ✗ r.id = index;
139 ✗ r.w = glyph.image.width + half_margin * 2;
140 ✗ r.h = glyph.image.height + half_margin * 2;
141 ✗ id_to_codepoint[index] = codepoint;
142 ✗ index +=1;
143 }
144 }
145 ✗ stbrp_context context {};
146 ✗ const int num_nodes = texture_width;
147 ✗ std::vector<stbrp_node> nodes(num_nodes);
148 ✗ stbrp_init_target(&context, texture_width, texture_height, nodes.data(), num_nodes);
149 ✗ stbrp_pack_rects(&context, packed_rects.data(), num_rects);
150
151 ✗ CharToGlyphMap map;
152 ✗ core::Image image;
153 ✗ image.setup_with_alpha_support(texture_width, texture_height);
154 ✗ for(int rect_index = 0; rect_index < num_rects; rect_index += 1)
155 {
156 ✗ const stbrp_rect& src_rect = packed_rects[rect_index];
157 ✗ if(src_rect.was_packed == 0)
158 {
159 ✗ LOG_ERR("Failed to pack");
160 ✗ continue;
161 }
162 ✗ const auto& src_char = fontchars.codepoint_to_glyph[id_to_codepoint[src_rect.id]];
163 ✗ if (src_char.image.is_valid() == false)
164 {
165 ✗ LOG_WARN("Glyph for code point {0} has an invalid image, skipping", src_char.code_point);
166 }
167 else
168 {
169 paste_image
170 ✗ (
171 &image,
172 ✗ src_rect.x + half_margin,
173 ✗ src_rect.y + half_margin,
174 ✗ src_char.image
175 );
176 }
177 const auto sprite_and_texture_rects = construct_character_rects
178 ✗ (
179 src_rect,
180 src_char,
181 texture_width,
182 texture_height,
183 half_margin
184 );
185
186 std::shared_ptr<Glyph> dest
187 (
188 new Glyph
189 (
190 sprite_and_texture_rects.first,
191 sprite_and_texture_rects.second,
192 ✗ src_char.code_point,
193 ✗ static_cast<float>(src_char.advance) / src_char.size
194 ✗ )
195 ✗ );
196 ✗ map.insert(CharToGlyphMap::value_type(dest->code_point, dest));
197 ✗ }
198
199 // for debug
200 // fs->WriteFile("charmap.png", image.Write(ImageWriteFormat::PNG));
201
202 // load pixels into texture
203 ✗ private_use_aliases = fontchars.private_use_aliases;
204 ✗ kernings = fontchars.kerning;
205 ✗ char_to_glyph = map;
206 ✗ texture = std::make_unique<Texture2d>(SEND_DEBUG_LABEL_MANY("FONT TEXTURE") image.data.data(), GL_RGBA, texture_width, texture_height,
207 ✗ TextureEdge::clamp, TextureRenderStyle::linear, Transparency::include, ColorData::color_data);
208 ✗ ascent = fontchars.ascent;
209 ✗ descent = fontchars.descent;
210 ✗ line_gap = fontchars.line_gap;
211 ✗ }
212
213
214 void
215 ✗ DrawableFont::draw_background
216 (
217 SpriteBatch* renderer,
218 float alpha,
219 const Rect& where
220 ) const
221 {
222 ✗ Quad{ .tint = Color{eu::colors::black, alpha} }.draw(renderer, where);
223 ✗ }
224
225
226 ✗ TextDrawCommand::TextDrawCommand
227 (
228 const Texture2d* atexture,
229 const Rect& asprite_rect,
230 const Rect& atexture_rect,
231 bool ahi
232 ✗ )
233 ✗ : texture(atexture)
234 ✗ , sprite_rect(asprite_rect)
235 ✗ , texture_rect(atexture_rect)
236 ✗ , hi(ahi)
237 {
238 ✗ }
239
240
241 void
242 ✗ ListOfTextDrawCommands::add
243 (
244 const Texture2d* texture,
245 const Rect& sprite_rect,
246 const Rect& texture_rect,
247 bool hi
248 )
249 {
250 ✗ commands.emplace_back(texture, sprite_rect, texture_rect, hi);
251 ✗ }
252
253
254 void
255 ✗ ListOfTextDrawCommands::draw
256 (
257 SpriteBatch* renderer,
258 const v2& start_position,
259 const Rgb& base_color,
260 const Rgb& hi_color
261 )
262 {
263 ✗ for(const auto& cmd: commands)
264 {
265 ✗ const auto tint = cmd.hi ? hi_color : base_color;
266 ✗ Quad{
267 ✗ .texture = cmd.texture,
268 ✗ .texturecoord = cmd.texture_rect,
269 .tint = Color{tint}
270 ✗ }.draw(renderer, cmd.sprite_rect.with_translate(start_position));
271 }
272 ✗ }
273
274
275 struct UiTextCompileVisitor// : public textparser::Visitor
276 {
277 const DrawableFont& font;
278 float size;
279 bool apply_highlight = false;
280 v2 position = v2{0, 0}; // todo(Gustav): rename to offset
281 int last_char_index = 0;
282
283 // return value
284 ListOfTextDrawCommands* list;
285
286 ✗ UiTextCompileVisitor
287 (
288 const DrawableFont& f,
289 float s,
290 ListOfTextDrawCommands* li
291 )
292 ✗ : font{f}
293 ✗ , size{s}
294 ✗ , list{li}
295 {
296 ✗ }
297
298
299 void
300 ✗ on_text(const std::string& text)// override
301 {
302 core::calc_utf8_to_codepoints
303 ✗ (
304 text,
305 ✗ [this](int cp)
306 {
307 ✗ add_char_index(cp);
308 ✗ }
309 );
310 ✗ }
311
312 #if 0
313 void
314 on_image(const std::string& image) override
315 {
316 // todo(Gustav): handle invalid font alias
317 auto found = font.private_use_aliases.find(image);
318 if(found == font.private_use_aliases.end())
319 {
320 LOG_ERR
321 (
322 "Unable to find image {0}, could be {1}",
323 image,
324 string_mergers::english_or.merge(get_keys(font.private_use_aliases))
325 );
326 return;
327 }
328 add_char_index(found->second);
329 }
330 #endif
331
332
333 void
334 ✗ on_begin()// override
335 {
336 ✗ apply_highlight = true;
337 ✗ }
338
339
340 void
341 ✗ on_end()// override
342 {
343 ✗ apply_highlight = false;
344 ✗ }
345
346
347 void
348 ✗ add_char_index(int code_point)
349 {
350 ✗ if(code_point == '\n')
351 {
352 ✗ position.x = 0;
353 ✗ position.y -= size*font.get_line_height();
354 }
355 else
356 {
357 ✗ auto it = font.char_to_glyph.find(code_point);
358 ✗ if(it == font.char_to_glyph.end())
359 {
360 ✗ LOG_ERR("Failed to print '{0}'", code_point);
361 ✗ return;
362 }
363 ✗ std::shared_ptr<Glyph> ch = it->second;
364
365 ✗ list->add
366 ✗ (
367 ✗ font.texture.get(),
368 ✗ ch->sprite_rect.with_scale(size, size).with_translate(position),
369 ✗ ch->texture_rect,
370 ✗ apply_highlight
371 );
372
373 ✗ const auto kerning = font.kernings.find
374 ✗ (
375 std::make_pair
376 ✗ (
377 ✗ last_char_index,
378 code_point
379 )
380 );
381 ✗ const float the_kerning = kerning == font.kernings.end()
382 ✗ ? 0.0f
383 ✗ : kerning->second
384 ;
385 ✗ position.x += (ch->advance + the_kerning) * size;
386 ✗ }
387 ✗ last_char_index = code_point;
388 }
389 };
390
391
392 ListOfTextDrawCommands
393 ✗ DrawableFont::compile_list(const std::string& text, float size) const
394 {
395 ✗ ListOfTextDrawCommands list;
396
397 ✗ UiTextCompileVisitor vis {*this, size, &list};
398 // text.accept(&vis);
399 ✗ vis.on_begin();
400 ✗ vis.on_text(text);
401 ✗ vis.on_end();
402
403 ✗ return list;
404 ✗ }
405
406
407 Rect
408 ✗ ListOfTextDrawCommands::get_extents() const
409 {
410 ✗ Rect ret;
411 ✗ for(const auto& cmd: commands)
412 {
413 ✗ ret.extend(cmd.sprite_rect);
414 }
415 ✗ return ret;
416 }
417
418
419 ✗ DrawableText::DrawableText(const DrawableFont* the_font)
420 ✗ : font(the_font)
421 {
422 ✗ }
423
424
425 ✗ DrawableText::~DrawableText() = default;
426
427
428 void
429 ✗ DrawableText::set_text(const std::string& new_text)
430 {
431 ✗ text = new_text;
432 ✗ is_dirty = true;
433 ✗ }
434
435
436 void
437 ✗ DrawableText::set_background(bool new_use_background, float new_alpha)
438 {
439 ✗ use_background = new_use_background;
440 ✗ background_alpha = new_alpha;
441 ✗ }
442
443
444 void
445 ✗ DrawableText::set_alignment(Align new_alignment)
446 {
447 ✗ alignment = new_alignment;
448 ✗ }
449
450
451 void
452 ✗ DrawableText::set_size(float new_size)
453 {
454 ✗ if(size != new_size)
455 {
456 ✗ size = new_size;
457 ✗ is_dirty = true;
458 }
459 ✗ }
460
461
462 v2
463 ✗ get_offset(Align alignment, const Rect& extent)
464 {
465 // todo(Gustav): test this more
466 ✗ const auto middle = -(extent.left + extent.right) / 2;
467 ✗ const auto right = -extent.right;
468 ✗ const auto top = extent.top;
469 ✗ const auto bottom = -extent.bottom;
470
471 ✗ switch(alignment)
472 {
473 ✗ case Align::top_left: return {0.0f, top};
474 ✗ case Align::top_center: return {middle, top};
475 ✗ case Align::top_right: return {right, top};
476 ✗ case Align::baseline_left: return {0.0f, 0.0f};
477 ✗ case Align::baseline_center: return {middle, 0.0f};
478 ✗ case Align::baseline_right: return {right, 0.0f};
479 ✗ case Align::bottom_left: return {0.0f, bottom};
480 ✗ case Align::bottom_center: return {middle, bottom};
481 ✗ case Align::bottom_right: return {right, bottom};
482 ✗ default: DIE("Unhandled case"); return {0.0f, 0.0f};
483 }
484 }
485
486
487 void
488 ✗ DrawableText::draw
489 (
490 SpriteBatch* renderer,
491 const v2& p,
492 const Rgb& base_hi_color
493 ) const
494 {
495 ✗ draw(renderer, p, base_hi_color, base_hi_color);
496 ✗ }
497
498
499 void
500 ✗ DrawableText::draw
501 (
502 SpriteBatch* renderer,
503 const v2& p,
504 const Rgb& base_color,
505 const Rgb& hi_color
506 ) const
507 {
508 ✗ compile();
509 ✗ ASSERT(!is_dirty);
510
511 ✗ if(font == nullptr)
512 {
513 ✗ return;
514 }
515 ✗ const auto e = get_extents();
516 ✗ const auto off = get_offset(alignment, e);
517 ✗ if(use_background)
518 {
519 ✗ font->draw_background
520 ✗ (
521 renderer,
522 ✗ background_alpha,
523 ✗ e.with_inset(Lrtb{ -5.0f }).with_offset(p + off)
524 );
525 }
526
527 ✗ commands.draw(renderer, p + off, base_color, hi_color);
528 }
529
530
531 void
532 ✗ DrawableText::compile() const
533 {
534 ✗ if(is_dirty)
535 {
536 ✗ is_dirty = false;
537 ✗ commands = font->compile_list(text, size);
538 }
539 ✗ }
540
541
542 Rect
543 ✗ DrawableText::get_extents() const
544 {
545 ✗ compile();
546 ✗ ASSERT(!is_dirty);
547 ✗ return commands.get_extents();
548 }
549 }
550