Euphoria
loadedfont.cc
Go to the documentation of this file.
1 #include "core/loadedfont.h"
2 
3 #include <set>
4 #include <optional>
5 
6 #include <map>
7 
8 #include "stb_truetype.h"
9 
10 #include "font8x8/font8x8_basic.h"
11 #include "font8x13.h"
12 
13 #include "log/log.h"
14 #include "assert/assert.h"
15 #include "core/image_draw.h"
16 #include "core/utf8.h"
17 #include "io/vfs.h"
18 #include "base/cint.h"
19 #include "base/memorychunk.h"
20 #include "io/vfs_path.h"
21 
22 
23 namespace eu::core
24 {
25  struct FontData
26  {
27  stbtt_fontinfo font;
28  float size;
29  bool was_loaded;
30  int line_height = 0;
31  std::vector<stbtt_kerningentry> kernings;
32  std::map<int, int> index_to_unicode;
33 
34  FontData(unsigned char* ttf_buffer, float s)
35  : font{}
36  , size{s}
37  , was_loaded{stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)) == 1}
38  {
39  if(was_loaded == false) {return;}
40 
41  const int count = stbtt_GetKerningTableLength(&font);
42  kernings.reserve(count);
43  stbtt_GetKerningTable(&font, kernings.data(), count);
44 
45  int ascent = 0;
46  int descent = 0;
47  int line_gap = 0;
48  stbtt_GetFontVMetrics(&font, &ascent, &descent, &line_gap);
49  line_height = ascent - descent + line_gap;
50  }
51 
52  void define_codepoint(int cp)
53  {
54  const int index = stbtt_FindGlyphIndex(&font, cp);
55  index_to_unicode[index] = cp;
56  }
57 
58  [[nodiscard]] std::optional<int> from_glyph_index_to_unicode(int glyph) const
59  {
60  auto found = index_to_unicode.find(glyph);
61  if(found == index_to_unicode.end())
62  {
63  return {};
64  }
65  else
66  {
67  return found->second;
68  }
69  }
70 
71  [[nodiscard]] LoadedGlyph
72  load_glyph(int code_point) const
73  {
74  int width=0; int height=0;
75  int xoffset=0; int yoffset=0;
76  const auto scale = stbtt_ScaleForPixelHeight(&font, size);
77  unsigned char *bitmap = stbtt_GetCodepointBitmap
78  (
79  &font,
80  scale,
81  scale,
82  code_point,
83  &width,
84  &height,
85  &xoffset,
86  &yoffset
87  );
88 
90  ch.code_point= code_point;
91  ch.size = size;
92  ch.bearing_x = xoffset;
93  ch.bearing_y = yoffset;
94  ch.valid = true;
95  {
96  int advance_width = 0;
97  stbtt_GetCodepointHMetrics(&font, code_point, &advance_width, nullptr);
98  ch.advance = c_float_to_int(c_int_to_float(advance_width) * scale);
99  }
100  if(width == 0 && height == 0)
101  {
102  ch.image.make_invalid();
103  }
104  else
105  {
106  ch.image.setup_with_alpha_support(width, height);
107 
108  for(int y = 0; y < ch.image.height; y += 1)
109  {
110  for(int x = 0; x < ch.image.width; x += 1)
111  {
112  ch.image.set_pixel
113  (
114  x, ch.image.height-y-1,
115  255, 255, 255,
116  bitmap[ch.image.width * y + x]
117  );
118  }
119  }
120  }
121 
122  if(bitmap != nullptr)
123  {
124  stbtt_FreeBitmap(bitmap, nullptr);
125  }
126 
127  return ch;
128  }
129  };
130 
131 
132  int
134  {
135  // detect existing private use alias!
136  const auto pu = next_private_use;
137  next_private_use += 1;
138  private_use_aliases[alias] = pu;
139  return pu;
140  }
141 
142 
143  void
145  {
146  std::map<int, int> pus;
147  for(const auto& [alias, id]: fc.private_use_aliases)
148  {
149  pus[id] = generate_new_index_from_private_use(alias);
150  }
151 
152  for(const auto& glyph_iterator: fc.codepoint_to_glyph)
153  {
154  auto code_point = glyph_iterator.first;
155  auto found_pus = pus.find(code_point);
156  if(found_pus != pus.end())
157  {
158  // private use: move to new private use to avoid collisions
159  code_point = found_pus->second;
160  }
161 
162  const auto found = codepoint_to_glyph.find(code_point);
163  if(found == codepoint_to_glyph.end())
164  {
165  codepoint_to_glyph[code_point] = glyph_iterator.second;
166  }
167  else
168  {
169  LOG_ERROR("Multiple codepoints for {0} found when trying to combine", code_point);
170  }
171  }
172 
173  for(const auto& e: fc.kerning)
174  {
175  const auto found = kerning.find(e.first);
176  if(found == kerning.end())
177  {
178  kerning.insert(e);
179  }
180  else
181  {
182  LOG_ERROR("Multiple kernings found when trying to combine");
183  }
184  }
185 
187  }
188 
189 
190  template<typename TGlyphs>
191  LoadedFont
193  (
194  const int start_codepoint,
195  int end_codepoint,
196  TGlyphs glyphs
197  )
198  {
199  ASSERTX(start_codepoint < end_codepoint, start_codepoint, end_codepoint);
200  const auto number_of_glyphs = (end_codepoint+1) - start_codepoint;
201  LoadedFont font;
202  font.line_height = 8;
203 
204  for
205  (
206  int glyph_index=0;
207  glyph_index < number_of_glyphs;
208  glyph_index+=1
209  )
210  {
211  const auto code_point = glyph_index + start_codepoint;
212  LoadedGlyph glyph;
213  glyph.image.setup_with_alpha_support(8, 8, 0);
214 
215  for(int y = 0; y < 8; y += 1)
216  {
217  for(int x = 0; x < 8; x += 1)
218  {
219  // extract pixel from character
220  // glyph is defined in "y down" order, fix by inverting sample point on y
221  bool pixel = 0 != (glyphs[glyph_index][7 - y] & 1 << x);
222  if(pixel)
223  {
224  // glyph.image.SetPixel(x, y, Color::White);
225  draw_square(&glyph.image, {NamedColor::white}, x, y, 1);
226  }
227  }
228  }
229 
230  glyph.size = static_cast<float>(glyph.image.height);
231  glyph.bearing_y = glyph.image.height + 0;
232  glyph.bearing_x = 0;
233  glyph.advance = glyph.image.width + 0;
234  glyph.code_point= code_point;
235  glyph.valid = true;
236  font.codepoint_to_glyph[code_point] = glyph;
237  }
238 
239  return font;
240  }
241 
242 
243  LoadedFont
245  {
246  LoadedFont font;
247  font.line_height = 13;
248 
249  for(int codepoint=32; codepoint < 127; codepoint+=1)
250  {
251  LoadedGlyph glyph;
252  glyph.image.setup_with_alpha_support(8, 13, 0);
253 
254  const auto glyph_index = codepoint - 32;
255 
256  for(int y = 0; y < 13; y += 1)
257  {
258  for(int x = 0; x < 8; x += 1)
259  {
260  const bool pixel = 0 !=
261  (
262  FONT8X13_RASTERS[glyph_index * 13 + y] & (1 << (8-x))
263  );
264  if(pixel)
265  {
266  draw_square(&glyph.image, {NamedColor::white}, x, y, 1);
267  }
268  }
269  }
270 
271  glyph.size = static_cast<float>(glyph.image.height);
272  glyph.bearing_y = glyph.image.height + 0;
273  glyph.bearing_x = 0;
274  glyph.advance = glyph.image.width + 0;
275  glyph.code_point= codepoint;
276  glyph.valid = true;
277  font.codepoint_to_glyph[codepoint] = glyph;
278  }
279 
280  return font;
281  }
282 
283 
284  LoadedFont
286  {
287  LoadedFont font;
288  // todo(Gustav): Add more characters
289  font.combine_with
290  (
291  get_character_from_builtin8(0x0000, 0x007F, font8x8_basic)
292  );
293  return font;
294  }
295 
296 
297  LoadedFont
299  (
300  io::FileSystem* file_system,
301  const io::FilePath& font_file,
302  int font_size,
303  const std::string& chars
304  )
305  {
306  auto file_memory = file_system->read_file(font_file);
307 
308  if(file_memory == nullptr)
309  {
310  LOG_ERROR("Unable to open {0}", font_file);
311  return LoadedFont{};
312  }
313 
315  (
316  file_memory,
317  font_size,
318  chars
319  );
320  }
321 
322 
323  LoadedFont
325  (
326  std::shared_ptr<MemoryChunk> file_memory,
327  int font_size,
328  const std::string& chars
329  )
330  {
331  auto f = FontData
332  {
333  reinterpret_cast<unsigned char*>(file_memory->get_data()),
334  c_int_to_float(font_size)
335  };
336 
337  if(f.was_loaded == false) { return LoadedFont{}; }
338 
339  std::vector<int> code_points;
341  (
342  chars,
343  [&](int cp){code_points.emplace_back(cp);}
344  );
345 
346  LoadedFont fontchars {};
347  for(int code_point: code_points)
348  {
349  LoadedGlyph cc = f.load_glyph(code_point);
350  if(!cc.valid)
351  {
352  LOG_INFO("Invalid codepoint {0}", code_point);
353  continue;
354  }
355  fontchars.codepoint_to_glyph[code_point] = cc;
356  }
357 
358  fontchars.line_height = f.line_height;
359 
360  const float scale = 1 / static_cast<float>(font_size);
361 
362  for(int cp: code_points)
363  {
364  f.define_codepoint(cp);
365  }
366 
367  for(const auto& info: f.kernings)
368  {
369  const auto previous = f.from_glyph_index_to_unicode(info.glyph1);
370  const auto current = f.from_glyph_index_to_unicode(info.glyph2);
371  const int dx = info.advance;
372  if(previous && current)
373  {
374  fontchars.kerning.insert
375  (
376  KerningMap::value_type
377  (
378  KerningMap::key_type(*previous, *current),
379  static_cast<float>(dx) * scale
380  )
381  );
382  }
383  }
384 
385  return fontchars;
386  }
387 
388 
389  LoadedFont
391  (
392  io::FileSystem* fs,
393  const io::FilePath& image_file,
394  const std::string& image_alias,
395  float image_scale,
396  float image_bearing_x,
397  float image_bearing_y,
398  float image_advance
399  )
400  {
401  const auto loaded = load_image
402  (
403  fs,
404  image_file,
406  );
407 
408  if(loaded.error.empty() == false)
409  {
410  LOG_ERROR("Failed to load font image {}", image_file);
411  return LoadedFont{};
412  }
413 
415  (
416  loaded.image,
417  image_alias,
418  image_scale,
419  image_bearing_x,
420  image_bearing_y,
421  image_advance
422  );
423  }
424 
425 
426  LoadedFont
428  (
429  const Image& image,
430  const std::string& image_alias,
431  float image_scale,
432  float image_bearing_x,
433  float image_bearing_y,
434  float image_advance
435  )
436  {
437  LoadedFont font;
438 
439  const auto s = 1 / image_scale;
440  LoadedGlyph glyph;
441  glyph.size = s * static_cast<float>(image.height);
442  glyph.bearing_y = c_float_to_int(s * static_cast<float>(image.height) + image_bearing_y);
443  glyph.bearing_x = c_float_to_int(image_bearing_x);
444  glyph.advance = c_float_to_int(s * static_cast<float>(image.width) + image_advance);
445  glyph.code_point= font.generate_new_index_from_private_use(image_alias);
446  // todo(Gustav): add ability to clip image
447  glyph.image = image;
448  font.codepoint_to_glyph[glyph.code_point] = glyph;
449 
450  return font;
451  }
452 
453 }
454 
#define ASSERTX(x,...)
Definition: assert.h:48
#define LOG_INFO(...)
Definition: log.h:7
#define LOG_ERROR(...)
Definition: log.h:9
LoadedFont get_character_from_builtin8(const int start_codepoint, int end_codepoint, TGlyphs glyphs)
Definition: loadedfont.cc:193
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
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
ImageLoadResult load_image(io::FileSystem *fs, const io::FilePath &path, AlphaLoad alpha)
Definition: image.cc:295
void draw_square(Image *image, const Rgbai &color, int x, int y, int size)
Definition: image_draw.cc:67
LoadedFont load_characters_from_builtin8()
Definition: loadedfont.cc:285
constexpr int c_float_to_int(float f)
Definition: cint.h:36
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
stbtt_fontinfo font
Definition: loadedfont.cc:27
FontData(unsigned char *ttf_buffer, float s)
Definition: loadedfont.cc:34
std::optional< int > from_glyph_index_to_unicode(int glyph) const
Definition: loadedfont.cc:58
void define_codepoint(int cp)
Definition: loadedfont.cc:52
LoadedGlyph load_glyph(int code_point) const
Definition: loadedfont.cc:72
std::map< int, int > index_to_unicode
Definition: loadedfont.cc:32
std::vector< stbtt_kerningentry > kernings
Definition: loadedfont.cc:31
void setup_with_alpha_support(int image_width, int image_height, int default_value=0)
if default value is negative, default value is ignored, otherwise its the default value for both R,...
Definition: image.cc:40
int height
Definition: image.h:33
int generate_new_index_from_private_use(const std::string &alias)
Definition: loadedfont.cc:133
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
std::shared_ptr< MemoryChunk > read_file(const FilePath &path)
Definition: vfs.cc:86