Euphoria
font.cc
Go to the documentation of this file.
1 #include "render/font.h"
2 
3 
4 #include <memory>
5 #include <map>
6 #include <algorithm>
7 #include <iostream>
8 
9 #include "stb_rect_pack.h"
10 
11 #include "log/log.h"
12 #include "core/image_draw.h"
13 #include "core/ui_text.h"
14 #include "core/utf8.h"
15 #include "assert/assert.h"
16 #include "core/image.h"
17 #include "io/vfs_path.h"
18 #include "core/stdutils.h"
19 #include "base/stringmerger.h"
20 
21 #include "files/font.h"
22 
23 #include "render/texture.h"
24 #include "render/spriterender.h"
25 #include "render/texturecache.h"
26 
27 
28 using namespace eu::convert;
29 
30 
31 namespace eu::render
32 {
33  Glyph::Glyph
34  (
35  const Rectf& sprite,
36  const Rectf& 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 
50  (
51  io::FileSystem* fs,
52  const files::font::SingleImage& img
53  )
54  {
55  const auto image_file = io::FilePath::from_script(img.file);
56 
57  if(image_file.has_value() == false)
58  {
59  LOG_WARN("Invalid path {0}", img.file);
60  return {};
61  }
62 
64  (
65  fs,
66  image_file.value(),
67  img.alias,
68  img.scale,
72  );
73  }
74 
75 
76  std::pair<Rectf, Rectf>
78  (
79  const stbrp_rect& src_rect,
80  const core::LoadedGlyph& src_char,
81  int image_width,
82  int image_height
83  )
84  {
85  const int vert_left = src_char.bearing_x;
86  const int vert_right = vert_left + src_char.image.width;
87  const int vert_top = src_char.bearing_y;
88  const int vert_bottom = vert_top - std::max(1, src_char.image.height);
89 
90  const stbrp_coord uv_left = src_rect.x;
91  const stbrp_coord uv_right = uv_left + src_rect.w;
92  const stbrp_coord uv_bottom = src_rect.y;
93  const stbrp_coord uv_top = uv_bottom + std::max(1, src_rect.h);
94 
95  // todo(Gustav): add ability to be a quad for tighter fit
96  ASSERTX(vert_top > vert_bottom, vert_top, vert_bottom, src_char.code_point);
97  ASSERTX(uv_top > uv_bottom, uv_top, uv_bottom, src_char.code_point);
98 
99  const auto iw = static_cast<float>(image_width);
100  const auto ih = static_cast<float>(image_height);
101 
102  const auto sprite = Rectf::from_left_right_top_bottom
103  (
104  static_cast<float>(vert_left),
105  static_cast<float>(vert_right),
106  static_cast<float>(vert_top),
107  static_cast<float>(vert_bottom)
108  );
109  const auto texture = Rectf::from_left_right_top_bottom
110  (
111  static_cast<float>(uv_left) / iw,
112  static_cast<float>(uv_right) / iw,
113  static_cast<float>(uv_top) / ih,
114  static_cast<float>(uv_bottom) / ih
115  );
116 
117  const float scale = 1.0f / src_char.size;
118  return std::make_pair(sprite.scale_copy(scale, scale), texture);
119  }
120 
121 
123 
124 
126  (
127  io::FileSystem* fs,
128  TextureCache* cache,
129  const io::FilePath& font_file
130  )
131  {
132  // todo(Gustav): too long, break up
133  const int texture_width = 512;
134  const int texture_height = 512;
135 
136  background = cache->get_texture
137  (
138  io::FilePath{"~/img-plain/white"}
139  );
140 
141  files::font::Root font_root;
142  if (const auto loaded = io::read_json_file(fs, font_file); loaded == false)
143  {
144  LOG_ERROR("Failed to load {}: {}", font_file, loaded.get_error().display);
145  return;
146  }
147  else
148  {
149  const auto& json = loaded.get_value();
150  const auto parsed = files::font::parse(log::get_global_logger(), &font_root, json.root, &json.doc);
151  if (!parsed)
152  {
153  return;
154  }
155  }
156 
157  core::LoadedFont fontchars;
158 
159  int sources = 0;
160 
161  for(const auto& font: font_root.fonts)
162  {
163  sources += 1;
164 
165  const auto p = io::FilePath::from_script(font.file);
166  if(p.has_value() == false)
167  {
168  LOG_ERROR("Invalid path {0}", font.file);
169  return;
170  }
171 
172  fontchars.combine_with
173  (
175  (
176  fs,
177  *p,
178  font_root.size,
179  font.characters
180  )
181  );
182  }
183 
184  for(const auto& image: font_root.images)
185  {
186  sources += 1;
187  const auto image_font = get_characters_from_single_image(fs, image);
188  fontchars.combine_with(image_font);
189  }
190 
191  if(font_root.builtin8)
192  {
193  sources += 1;
195  }
196 
197  if(font_root.builtin13)
198  {
199  sources += 1;
201  }
202 
203  if (sources == 0)
204  {
205  LOG_ERROR("Missing sources in {}", font_file);
206  return;
207  }
208 
209  // todo(Gustav): add more sources, built in image font or images
210 
211  // the half margin between glyphs in the final texture
212  const int half_margin = 1;
213 
214  // todo(Gustav): use core/pack.h instead
215 
216  // pack char textures to a single texture
217  const int num_rects = c_sizet_to_int(fontchars.codepoint_to_glyph.size());
218  std::vector<stbrp_rect> packed_rects(num_rects);
219  std::map<int, int> id_to_codepoint;
220  {
221  int index = 0;
222  for(const auto& [codepoint, glyph]: fontchars.codepoint_to_glyph)
223  {
224  stbrp_rect& r = packed_rects[index];
225  r.id = index;
226  r.w = glyph.image.width + half_margin * 2;
227  r.h = glyph.image.height + half_margin * 2;
228  id_to_codepoint[index] = codepoint;
229  index +=1;
230  }
231  }
232  stbrp_context context {};
233  const int num_nodes = texture_width;
234  std::vector<stbrp_node> nodes(num_nodes);
235  stbrp_init_target(&context, texture_width, texture_height, nodes.data(), num_nodes);
236  stbrp_pack_rects(&context, packed_rects.data(), num_rects);
237 
239  core::Image image;
240  image.setup_with_alpha_support(texture_width, texture_height);
241  for(int rect_index = 0; rect_index < num_rects; rect_index += 1)
242  {
243  const stbrp_rect& src_rect = packed_rects[rect_index];
244  if(src_rect.was_packed == 0)
245  {
246  LOG_ERROR("Failed to pack");
247  continue;
248  }
249  const auto& src_char = fontchars.codepoint_to_glyph[id_to_codepoint[src_rect.id]];
251  (
252  &image,
253  vec2i
254  {
255  src_rect.x + half_margin,
256  src_rect.y + half_margin
257  },
258  src_char.image
259  );
260  const auto sprite_and_texture_rects = construct_character_rects
261  (
262  src_rect,
263  src_char,
264  texture_width,
265  texture_height
266  );
267 
268  std::shared_ptr<Glyph> dest
269  (
270  new Glyph
271  (
272  sprite_and_texture_rects.first,
273  sprite_and_texture_rects.second,
274  src_char.code_point,
275  static_cast<float>(src_char.advance) / src_char.size
276  )
277  );
278  map.insert(CharToGlyphMap::value_type(dest->code_point, dest));
279  }
280 
281  // for debug
282  // fs->WriteFile("charmap.png", image.Write(ImageWriteFormat::PNG));
283 
284  // load pixels into texture
286  kernings = fontchars.kerning;
287  char_to_glyph = map;
288  Texture2dLoadData load_data;
289  texture = std::make_unique<Texture2>();
290  texture->load_from_image
291  (
292  image,
294  );
295  line_height = static_cast<float>(fontchars.line_height);
296  }
297 
298 
299  void
301  (
302  SpriteRenderer* renderer,
303  float alpha,
304  const Rectf& where
305  ) const
306  {
307  renderer->draw_rect
308  (
309  *background,
310  where,
312  0.0_rad,
313  Scale2f{0, 0},
314  Rgba{NamedColor::black, alpha}
315  );
316  }
317 
318 
320  (
321  const Texture2* atexture,
322  const Rectf& asprite_rect,
323  const Rectf& atexture_rect,
324  bool ahi
325  )
326  : texture(atexture)
327  , sprite_rect(asprite_rect)
328  , texture_rect(atexture_rect)
329  , hi(ahi)
330  {
331  }
332 
333 
334  void
336  (
337  const Texture2* texture,
338  const Rectf& sprite_rect,
339  const Rectf& texture_rect,
340  bool hi
341  )
342  {
343  commands.emplace_back(texture, sprite_rect, texture_rect, hi);
344  }
345 
346 
347  void
349  (
350  SpriteRenderer* renderer,
351  const vec2f& start_position,
352  const Rgb& base_color,
353  const Rgb& hi_color
354  )
355  {
356  for(const auto& cmd: commands)
357  {
358  const auto tint = cmd.hi ? hi_color : base_color;
359  renderer->draw_rect
360  (
361  *cmd.texture,
362  cmd.sprite_rect.translate_copy(start_position),
363  cmd.texture_rect,
364  0.0_rad,
365  Scale2f{0.5f, 0.5f},
366  Rgba{tint}
367  );
368  }
369  }
370 
371 
373  {
375  float size;
376  bool apply_highlight = false;
377  vec2f position = vec2f{0, 0}; // todo(Gustav): rename to offset
379 
380  // return value
382 
384  (
385  const DrawableFont& f,
386  float s,
388  )
389  : font{f}
390  , size{s}
391  , list{li}
392  {
393  }
394 
395 
396  void
397  on_text(const std::string& text) override
398  {
400  (
401  text,
402  [this](int cp)
403  {
404  add_char_index(cp);
405  }
406  );
407  }
408 
409 
410  void
411  on_image(const std::string& image) override
412  {
413  // todo(Gustav): handle invalid font alias
414  auto found = font.private_use_aliases.find(image);
415  if(found == font.private_use_aliases.end())
416  {
417  LOG_ERROR
418  (
419  "Unable to find image {0}, could be {1}",
420  image,
422  );
423  return;
424  }
425  add_char_index(found->second);
426  }
427 
428 
429  void
430  on_begin() override
431  {
432  apply_highlight = true;
433  }
434 
435 
436  void
437  on_end() override
438  {
439  apply_highlight = false;
440  }
441 
442 
443  void
444  add_char_index(int code_point)
445  {
446  if(code_point == '\n')
447  {
448  position.x = 0;
450  }
451  else
452  {
453  auto it = font.char_to_glyph.find(code_point);
454  if(it == font.char_to_glyph.end())
455  {
456  LOG_ERROR("Failed to print '{0}'", code_point);
457  return;
458  }
459  std::shared_ptr<Glyph> ch = it->second;
460 
461  list->add
462  (
463  font.texture.get(),
464  ch->sprite_rect.scale_copy(size, size).translate_copy(position),
465  ch->texture_rect,
467  );
468 
469  const auto kerning = font.kernings.find
470  (
471  std::make_pair
472  (
474  code_point
475  )
476  );
477  const float the_kerning = kerning == font.kernings.end()
478  ? 0.0f
479  : kerning->second
480  ;
481  position.x += (ch->advance + the_kerning) * size;
482  }
483  last_char_index = code_point;
484  }
485  };
486 
487 
488  ListOfTextDrawCommands
489  DrawableFont::compile_list(const core::UiText& text, float size) const
490  {
492 
493  UiTextCompileVisitor vis {*this, size, &list};
494  text.accept(&vis);
495 
496  return list;
497  }
498 
499 
500  Rectf
502  {
503  Rectf ret;
504  for(const auto& cmd: commands)
505  {
506  ret.include(cmd.sprite_rect);
507  }
508  return ret;
509  }
510 
511 
513  : font(the_font)
514  , size(12.0f)
515  , alignment(Align::baseline_left)
516  , use_background(false)
517  , background_alpha(0.0f)
518  , is_dirty(true)
519  {
520  }
521 
522 
523  DrawableText::~DrawableText() = default;
524 
525 
526  void
528  {
529  text = new_text;
530  is_dirty = true;
531  }
532 
533 
534  void
535  DrawableText::set_background(bool new_use_background, float new_alpha)
536  {
537  use_background = new_use_background;
538  background_alpha = new_alpha;
539  }
540 
541 
542  void
544  {
545  alignment = new_alignment;
546  }
547 
548 
549  void
550  DrawableText::set_size(float new_size)
551  {
552  if(size != new_size)
553  {
554  size = new_size;
555  is_dirty = true;
556  }
557  }
558 
559 
560  vec2f
561  get_offset(Align alignment, const Rectf& extent)
562  {
563  // todo(Gustav): test this more
564  const auto middle = -(extent.left + extent.right) / 2;
565  const auto right = -extent.right;
566  const auto top = extent.top;
567  const auto bottom = -extent.bottom;
568 
569  switch(alignment)
570  {
571  case Align::top_left: return {0.0f, top};
572  case Align::top_center: return {middle, top};
573  case Align::top_right: return {right, top};
574  case Align::baseline_left: return {0.0f, 0.0f};
575  case Align::baseline_center: return {middle, 0.0f};
576  case Align::baseline_right: return {right, 0.0f};
577  case Align::bottom_left: return {0.0f, bottom};
578  case Align::bottom_center: return {middle, bottom};
579  case Align::bottom_right: return {right, bottom};
580  default: DIE("Unhandled case"); return {0.0f, 0.0f};
581  }
582  }
583 
584 
585  void
587  (
588  SpriteRenderer* renderer,
589  const vec2f& p,
590  const Rgb& base_hi_color
591  ) const
592  {
593  draw(renderer, p, base_hi_color, base_hi_color);
594  }
595 
596 
597  void
599  (
600  SpriteRenderer* renderer,
601  const vec2f& p,
602  const Rgb& base_color,
603  const Rgb& hi_color
604  ) const
605  {
606  compile();
607  ASSERT(!is_dirty);
608 
609  if(font == nullptr)
610  {
611  return;
612  }
613  const auto e = get_extents();
614  const auto off = get_offset(alignment, e);
615  if(use_background)
616  {
617  font->draw_background
618  (
619  renderer,
620  background_alpha,
621  e.extend_copy(5.0f).translate_copy(p + off)
622  );
623  }
624 
625  commands.draw(renderer, p + off, base_color, hi_color);
626  }
627 
628 
629  void
631  {
632  if(is_dirty)
633  {
634  is_dirty = false;
635  commands = font->compile_list(text, size);
636  }
637  }
638 
639 
640  Rectf
642  {
643  compile();
644  ASSERT(!is_dirty);
645  return commands.get_extents();
646  }
647 }
#define ASSERTX(x,...)
Definition: assert.h:48
#define ASSERT(x)
Definition: assert.h:29
#define DIE(message)
Definition: assert.h:67
#define LOG_ERROR(...)
Definition: log.h:9
#define LOG_WARN(...)
Definition: log.h:8
bool calc_utf8_to_codepoints(const TString &string, TOnCodepointFunc on_codepoint)
Definition: utf8.h:11
LoadedFont get_characters_from_single_image(io::FileSystem *fs, const io::FilePath &image_file, const std::string &image_alias, float image_scale, float image_bearing_x, float image_bearing_y, float image_advance)
Definition: loadedfont.cc:391
std::vector< K > get_keys(const std::map< K, V > &m)
Definition: stdutils.h:13
LoadedFont get_characters_from_font(io::FileSystem *file_system, const io::FilePath &font_file, int font_size, const std::string &chars)
Definition: loadedfont.cc:299
LoadedFont load_characters_from_builtin13()
Definition: loadedfont.cc:244
void paste_image(Image *dest_image, const vec2i &position, const Image &source_image, BlendMode blend_mode, PixelsOutside clip)
Definition: image_draw.cc:553
LoadedFont load_characters_from_builtin8()
Definition: loadedfont.cc:285
JsonResult read_json_file(FileSystem *fs, const FilePath &file_name)
Definition: json.cc:58
Logger * get_global_logger()
Definition: log.cc:34
core::LoadedFont get_characters_from_single_image(io::FileSystem *fs, const files::font::SingleImage &img)
Definition: font.cc:50
std::map< int, std::shared_ptr< Glyph > > CharToGlyphMap
Definition: font.h:48
vec2f get_offset(Align alignment, const Rectf &extent)
Definition: font.cc:561
std::pair< Rectf, Rectf > construct_character_rects(const stbrp_rect &src_rect, const core::LoadedGlyph &src_char, int image_width, int image_height)
Definition: font.cc:78
constexpr StringMerger english_or
Definition: stringmerger.h:90
int c_sizet_to_int(size_t t)
Definition: cint.cc:11
std::vector< T > map(const std::vector< F > &fs, C convert)
Definition: functional.h:56
constexpr float c_int_to_float(int i)
Definition: cint.h:50
size2f max(const size2f lhs, const size2f rhs)
Definition: size2.cc:149
int ch
Definition: nlp_sentence.cc:92
Definition: rect.h:27
float top
Definition: rect.h:30
float right
Definition: rect.h:29
static Rectf from_left_right_top_bottom(float left_side, float right_side, float top_side, float bottom_side)
Definition: rect.cc:47
static Rectf from_width_height(float width, float height)
Definition: rect.cc:98
void include(const Rectf &o)
Definition: rect.cc:320
float left
Definition: rect.h:28
float bottom
Definition: rect.h:31
Definition: rgb.h:62
Definition: rgb.h:143
int height
Definition: image.h:33
void combine_with(const LoadedFont &fc)
Definition: loadedfont.cc:144
std::map< int, LoadedGlyph > codepoint_to_glyph
Definition: loadedfont.h:54
KerningMap kerning
Definition: loadedfont.h:55
std::map< std::string, int > private_use_aliases
Definition: loadedfont.h:56
core::Image image
Definition: loadedfont.h:42
Represents displayed text.
Definition: ui_text.h:107
std::vector< SingleImage > images
Definition: font.h:28
std::vector< FontFile > fonts
Definition: font.h:27
static std::optional< FilePath > from_script(const std::string &path)
apply only minor changes, return null on invalid
Definition: vfs_path.cc:46
core::KerningMap kernings
Definition: font.h:180
std::shared_ptr< Texture2 > background
Definition: font.h:178
CharToGlyphMap char_to_glyph
Definition: font.h:179
std::map< std::string, int > private_use_aliases
Definition: font.h:181
std::unique_ptr< Texture2 > texture
Definition: font.h:177
ListOfTextDrawCommands compile_list(const core::UiText &text, float size) const
Definition: font.cc:489
void draw_background(SpriteRenderer *renderer, float alpha, const Rectf &where) const
Definition: font.cc:301
DrawableFont(io::FileSystem *fs, TextureCache *cache, const io::FilePath &font_file)
Definition: font.cc:126
void set_text(const core::UiText &new_text)
Definition: font.cc:527
DrawableText(DrawableFont *the_font)
Definition: font.cc:512
Rectf get_extents() const
Definition: font.cc:641
void set_size(float new_size)
Definition: font.cc:550
void set_background(bool new_use_background, float new_alpha=0.5f)
Definition: font.cc:535
void draw(SpriteRenderer *renderer, const vec2f &p, const Rgb &base_hi_color) const
Definition: font.cc:587
void set_alignment(Align new_alignment)
Definition: font.cc:543
void compile() const
Definition: font.cc:630
void draw(SpriteRenderer *renderer, const vec2f &start_position, const Rgb &base_color, const Rgb &hi_color)
Definition: font.cc:349
std::vector< TextDrawCommand > commands
Definition: font.h:85
void add(const Texture2 *texture, const Rectf &sprite_rect, const Rectf &texture_rect, bool hi)
Definition: font.cc:336
void draw_rect(const Texture2 &texture, const Rectf &sprite_area, const Rectf &texture_region, const Angle &rotation_angle, const Scale2f &rotation_anchor, const Rgba &tint_color)
Definition: spriterender.cc:71
TextDrawCommand(const Texture2 *texture, const Rectf &sprite_rect, const Rectf &texture_rect, bool hi)
Definition: font.cc:320
std::shared_ptr< Texture2 > get_texture(const io::FilePath &path) const
Definition: texturecache.cc:47
void on_begin() override
Definition: font.cc:430
const DrawableFont & font
Definition: font.cc:374
ListOfTextDrawCommands * list
Definition: font.cc:381
UiTextCompileVisitor(const DrawableFont &f, float s, ListOfTextDrawCommands *li)
Definition: font.cc:384
void on_end() override
Definition: font.cc:437
void on_image(const std::string &image) override
Definition: font.cc:411
void add_char_index(int code_point)
Definition: font.cc:444
void on_text(const std::string &text) override
Definition: font.cc:397
Definition: vec2.h:33
float x
Definition: vec2.h:34
float y
Definition: vec2.h:35
Definition: vec2.h:72
int x
Definition: vec2.h:73
UiText * nodes
Definition: ui_text.cc:132