GCC Code Coverage Report


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

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