GCC Code Coverage Report


./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
0 of 225, 0 excluded
0.0%
Functions:
0 of 26, 0 excluded
0.0%
Branches:
0 of 208, 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(DrawableFont* the_font)
420 : font(the_font)
421 , size(12.0f)
422 , alignment(Align::baseline_left)
423 , use_background(false)
424 , background_alpha(0.0f)
425 , is_dirty(true)
426 {
427 }
428
429
430 DrawableText::~DrawableText() = default;
431
432
433 void
434 DrawableText::set_text(const std::string& new_text)
435 {
436 text = new_text;
437 is_dirty = true;
438 }
439
440
441 void
442 DrawableText::set_background(bool new_use_background, float new_alpha)
443 {
444 use_background = new_use_background;
445 background_alpha = new_alpha;
446 }
447
448
449 void
450 DrawableText::set_alignment(Align new_alignment)
451 {
452 alignment = new_alignment;
453 }
454
455
456 void
457 DrawableText::set_size(float new_size)
458 {
459 if(size != new_size)
460 {
461 size = new_size;
462 is_dirty = true;
463 }
464 }
465
466
467 v2
468 get_offset(Align alignment, const Rect& extent)
469 {
470 // todo(Gustav): test this more
471 const auto middle = -(extent.left + extent.right) / 2;
472 const auto right = -extent.right;
473 const auto top = extent.top;
474 const auto bottom = -extent.bottom;
475
476 switch(alignment)
477 {
478 case Align::top_left: return {0.0f, top};
479 case Align::top_center: return {middle, top};
480 case Align::top_right: return {right, top};
481 case Align::baseline_left: return {0.0f, 0.0f};
482 case Align::baseline_center: return {middle, 0.0f};
483 case Align::baseline_right: return {right, 0.0f};
484 case Align::bottom_left: return {0.0f, bottom};
485 case Align::bottom_center: return {middle, bottom};
486 case Align::bottom_right: return {right, bottom};
487 default: DIE("Unhandled case"); return {0.0f, 0.0f};
488 }
489 }
490
491
492 void
493 DrawableText::draw
494 (
495 SpriteBatch* renderer,
496 const v2& p,
497 const Rgb& base_hi_color
498 ) const
499 {
500 draw(renderer, p, base_hi_color, base_hi_color);
501 }
502
503
504 void
505 DrawableText::draw
506 (
507 SpriteBatch* renderer,
508 const v2& p,
509 const Rgb& base_color,
510 const Rgb& hi_color
511 ) const
512 {
513 compile();
514 ASSERT(!is_dirty);
515
516 if(font == nullptr)
517 {
518 return;
519 }
520 const auto e = get_extents();
521 const auto off = get_offset(alignment, e);
522 if(use_background)
523 {
524 font->draw_background
525 (
526 renderer,
527 background_alpha,
528 e.with_inset(Lrtb{ -5.0f }).with_offset(p + off)
529 );
530 }
531
532 commands.draw(renderer, p + off, base_color, hi_color);
533 }
534
535
536 void
537 DrawableText::compile() const
538 {
539 if(is_dirty)
540 {
541 is_dirty = false;
542 commands = font->compile_list(text, size);
543 }
544 }
545
546
547 Rect
548 DrawableText::get_extents() const
549 {
550 compile();
551 ASSERT(!is_dirty);
552 return commands.get_extents();
553 }
554 }
555