libs/core/src/core/loadedfont.cc
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "core/loadedfont.h" | ||
| 2 | |||
| 3 | #include <set> | ||
| 4 | #include <optional> | ||
| 5 | #include <map> | ||
| 6 | |||
| 7 | #include "eustb_truetype.h" | ||
| 8 | |||
| 9 | #include "log/log.h" | ||
| 10 | |||
| 11 | #include "assert/assert.h" | ||
| 12 | |||
| 13 | #include "base/cint.h" | ||
| 14 | #include "base/memorychunk.h" | ||
| 15 | |||
| 16 | #include "core/utf8.h" | ||
| 17 | |||
| 18 | |||
| 19 | |||
| 20 | namespace eu::core | ||
| 21 | { | ||
| 22 | struct FontData | ||
| 23 | { | ||
| 24 | stbtt_fontinfo font; | ||
| 25 | float size; | ||
| 26 | bool was_loaded; | ||
| 27 | int line_height = 0; | ||
| 28 | std::vector<stbtt_kerningentry> kernings; | ||
| 29 | std::map<int, int> index_to_unicode; | ||
| 30 | |||
| 31 | ✗ | FontData(const unsigned char* ttf_buffer, float s) | |
| 32 | ✗ | : font{} | |
| 33 | ✗ | , size{s} | |
| 34 | ✗ | , was_loaded{stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)) == 1} | |
| 35 | { | ||
| 36 | ✗ | if(was_loaded == false) {return;} | |
| 37 | |||
| 38 | ✗ | const int count = stbtt_GetKerningTableLength(&font); | |
| 39 | ✗ | kernings.reserve(count); | |
| 40 | ✗ | stbtt_GetKerningTable(&font, kernings.data(), count); | |
| 41 | |||
| 42 | ✗ | int ascent = 0; | |
| 43 | ✗ | int descent = 0; | |
| 44 | ✗ | int line_gap = 0; | |
| 45 | ✗ | stbtt_GetFontVMetrics(&font, &ascent, &descent, &line_gap); | |
| 46 | ✗ | line_height = ascent - descent + line_gap; | |
| 47 | ✗ | } | |
| 48 | |||
| 49 | ✗ | void define_codepoint(int cp) | |
| 50 | { | ||
| 51 | ✗ | const int index = stbtt_FindGlyphIndex(&font, cp); | |
| 52 | ✗ | index_to_unicode[index] = cp; | |
| 53 | ✗ | } | |
| 54 | |||
| 55 | ✗ | [[nodiscard]] std::optional<int> from_glyph_index_to_unicode(int glyph) const | |
| 56 | { | ||
| 57 | ✗ | auto found = index_to_unicode.find(glyph); | |
| 58 | ✗ | if(found == index_to_unicode.end()) | |
| 59 | { | ||
| 60 | ✗ | return {}; | |
| 61 | } | ||
| 62 | else | ||
| 63 | { | ||
| 64 | ✗ | return found->second; | |
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | [[nodiscard]] LoadedGlyph | ||
| 69 | ✗ | load_glyph(int code_point) const | |
| 70 | { | ||
| 71 | ✗ | int width=0; int height=0; | |
| 72 | ✗ | int xoffset=0; int yoffset=0; | |
| 73 | ✗ | const auto scale = stbtt_ScaleForPixelHeight(&font, size); | |
| 74 | unsigned char *bitmap = stbtt_GetCodepointBitmap | ||
| 75 | ✗ | ( | |
| 76 | &font, | ||
| 77 | scale, | ||
| 78 | scale, | ||
| 79 | code_point, | ||
| 80 | &width, | ||
| 81 | &height, | ||
| 82 | &xoffset, | ||
| 83 | &yoffset | ||
| 84 | ); | ||
| 85 | |||
| 86 | ✗ | LoadedGlyph ch; | |
| 87 | ✗ | ch.code_point= code_point; | |
| 88 | ✗ | ch.size = size; | |
| 89 | ✗ | ch.bearing_x = xoffset; | |
| 90 | ✗ | ch.bearing_y = -yoffset; | |
| 91 | ✗ | ch.valid = true; | |
| 92 | { | ||
| 93 | ✗ | int advance_width = 0; | |
| 94 | ✗ | stbtt_GetCodepointHMetrics(&font, code_point, &advance_width, nullptr); | |
| 95 | ✗ | ch.advance = int_from_float(float_from_int(advance_width) * scale); | |
| 96 | } | ||
| 97 | ✗ | if(width == 0 && height == 0) | |
| 98 | { | ||
| 99 | ✗ | ch.image.make_invalid(); | |
| 100 | } | ||
| 101 | else | ||
| 102 | { | ||
| 103 | ✗ | ch.image.setup_with_alpha_support(width, height); | |
| 104 | |||
| 105 | ✗ | for(int y = 0; y < ch.image.height; y += 1) | |
| 106 | { | ||
| 107 | ✗ | for(int x = 0; x < ch.image.width; x += 1) | |
| 108 | { | ||
| 109 | ✗ | const auto alpha = bitmap[ch.image.width * y + x]; | |
| 110 | ch.image.set_pixel | ||
| 111 | ✗ | ( | |
| 112 | ✗ | x, ch.image.height-y-1, | |
| 113 | 255, 255, 255, | ||
| 114 | alpha | ||
| 115 | ); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | ✗ | if(bitmap != nullptr) | |
| 121 | { | ||
| 122 | ✗ | stbtt_FreeBitmap(bitmap, nullptr); | |
| 123 | } | ||
| 124 | |||
| 125 | ✗ | return ch; | |
| 126 | ✗ | } | |
| 127 | }; | ||
| 128 | |||
| 129 | |||
| 130 | int | ||
| 131 | ✗ | LoadedFont::generate_new_index_from_private_use(const std::string& alias) | |
| 132 | { | ||
| 133 | // detect existing private use alias! | ||
| 134 | ✗ | const auto pu = next_private_use; | |
| 135 | ✗ | next_private_use += 1; | |
| 136 | ✗ | private_use_aliases[alias] = pu; | |
| 137 | ✗ | return pu; | |
| 138 | } | ||
| 139 | |||
| 140 | |||
| 141 | void | ||
| 142 | ✗ | LoadedFont::combine_with(const LoadedFont& fc) | |
| 143 | { | ||
| 144 | ✗ | std::map<int, int> pus; | |
| 145 | ✗ | for(const auto& [alias, id]: fc.private_use_aliases) | |
| 146 | { | ||
| 147 | ✗ | pus[id] = generate_new_index_from_private_use(alias); | |
| 148 | } | ||
| 149 | |||
| 150 | ✗ | for(const auto& glyph_iterator: fc.codepoint_to_glyph) | |
| 151 | { | ||
| 152 | ✗ | auto code_point = glyph_iterator.first; | |
| 153 | ✗ | auto found_pus = pus.find(code_point); | |
| 154 | ✗ | if(found_pus != pus.end()) | |
| 155 | { | ||
| 156 | // private use: move to new private use to avoid collisions | ||
| 157 | ✗ | code_point = found_pus->second; | |
| 158 | } | ||
| 159 | |||
| 160 | ✗ | const auto found = codepoint_to_glyph.find(code_point); | |
| 161 | ✗ | if(found == codepoint_to_glyph.end()) | |
| 162 | { | ||
| 163 | ✗ | codepoint_to_glyph[code_point] = glyph_iterator.second; | |
| 164 | } | ||
| 165 | else | ||
| 166 | { | ||
| 167 | ✗ | LOG_ERR("Multiple codepoints for {0} found when trying to combine", code_point); | |
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | ✗ | for(const auto& e: fc.kerning) | |
| 172 | { | ||
| 173 | ✗ | const auto found = kerning.find(e.first); | |
| 174 | ✗ | if(found == kerning.end()) | |
| 175 | { | ||
| 176 | ✗ | kerning.insert(e); | |
| 177 | } | ||
| 178 | else | ||
| 179 | { | ||
| 180 | ✗ | LOG_ERR("Multiple kernings found when trying to combine"); | |
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | ✗ | line_height = std::max(line_height, fc.line_height); | |
| 185 | ✗ | } | |
| 186 | |||
| 187 | |||
| 188 | LoadedFont | ||
| 189 | ✗ | get_characters_from_font | |
| 190 | ( | ||
| 191 | const MemoryChunk& file_memory, | ||
| 192 | int font_size, | ||
| 193 | const std::string& chars | ||
| 194 | ) | ||
| 195 | { | ||
| 196 | auto f = FontData | ||
| 197 | { | ||
| 198 | ✗ | reinterpret_cast<const unsigned char*>(file_memory.bytes), | |
| 199 | float_from_int(font_size) | ||
| 200 | ✗ | }; | |
| 201 | |||
| 202 | ✗ | if(f.was_loaded == false) { return LoadedFont{}; } | |
| 203 | |||
| 204 | ✗ | std::vector<int> code_points; | |
| 205 | calc_utf8_to_codepoints | ||
| 206 | ✗ | ( | |
| 207 | chars, | ||
| 208 | ✗ | [&](int cp){code_points.emplace_back(cp);} | |
| 209 | ); | ||
| 210 | |||
| 211 | ✗ | LoadedFont fontchars {}; | |
| 212 | ✗ | for(int code_point: code_points) | |
| 213 | { | ||
| 214 | ✗ | LoadedGlyph cc = f.load_glyph(code_point); | |
| 215 | ✗ | if(!cc.valid) | |
| 216 | { | ||
| 217 | ✗ | LOG_INFO("Invalid codepoint {0}", code_point); | |
| 218 | ✗ | continue; | |
| 219 | } | ||
| 220 | ✗ | fontchars.codepoint_to_glyph[code_point] = cc; | |
| 221 | ✗ | } | |
| 222 | |||
| 223 | ✗ | fontchars.line_height = f.line_height; | |
| 224 | |||
| 225 | ✗ | const float scale = 1 / static_cast<float>(font_size); | |
| 226 | |||
| 227 | ✗ | for(int cp: code_points) | |
| 228 | { | ||
| 229 | ✗ | f.define_codepoint(cp); | |
| 230 | } | ||
| 231 | |||
| 232 | ✗ | for(const auto& info: f.kernings) | |
| 233 | { | ||
| 234 | ✗ | const auto previous = f.from_glyph_index_to_unicode(info.glyph1); | |
| 235 | ✗ | const auto current = f.from_glyph_index_to_unicode(info.glyph2); | |
| 236 | ✗ | const int dx = info.advance; | |
| 237 | ✗ | if(previous && current) | |
| 238 | { | ||
| 239 | fontchars.kerning.insert | ||
| 240 | ✗ | ( | |
| 241 | ✗ | KerningMap::value_type | |
| 242 | ( | ||
| 243 | ✗ | KerningMap::key_type(*previous, *current), | |
| 244 | ✗ | static_cast<float>(dx) * scale | |
| 245 | ✗ | ) | |
| 246 | ); | ||
| 247 | } | ||
| 248 | } | ||
| 249 | |||
| 250 | ✗ | return fontchars; | |
| 251 | ✗ | } | |
| 252 | |||
| 253 | #if 0 | ||
| 254 | LoadedFont | ||
| 255 | get_characters_from_single_image | ||
| 256 | ( | ||
| 257 | io::FileSystem* fs, | ||
| 258 | const io::FilePath& image_file, | ||
| 259 | const std::string& image_alias, | ||
| 260 | float image_scale, | ||
| 261 | float image_bearing_x, | ||
| 262 | float image_bearing_y, | ||
| 263 | float image_advance | ||
| 264 | ) | ||
| 265 | { | ||
| 266 | const auto loaded = load_image | ||
| 267 | ( | ||
| 268 | fs, | ||
| 269 | image_file, | ||
| 270 | AlphaLoad::keep | ||
| 271 | ); | ||
| 272 | |||
| 273 | if(loaded.error.empty() == false) | ||
| 274 | { | ||
| 275 | LOG_ERR("Failed to load font image {}", image_file); | ||
| 276 | return LoadedFont{}; | ||
| 277 | } | ||
| 278 | |||
| 279 | return get_characters_from_single_image | ||
| 280 | ( | ||
| 281 | loaded.image, | ||
| 282 | image_alias, | ||
| 283 | image_scale, | ||
| 284 | image_bearing_x, | ||
| 285 | image_bearing_y, | ||
| 286 | image_advance | ||
| 287 | ); | ||
| 288 | } | ||
| 289 | #endif | ||
| 290 | |||
| 291 | |||
| 292 | LoadedFont | ||
| 293 | ✗ | get_characters_from_single_image | |
| 294 | ( | ||
| 295 | const Image& image, | ||
| 296 | const std::string& image_alias, | ||
| 297 | float image_scale, | ||
| 298 | float image_bearing_x, | ||
| 299 | float image_bearing_y, | ||
| 300 | float image_advance | ||
| 301 | ) | ||
| 302 | { | ||
| 303 | ✗ | LoadedFont font; | |
| 304 | |||
| 305 | ✗ | const auto s = 1 / image_scale; | |
| 306 | ✗ | LoadedGlyph glyph; | |
| 307 | ✗ | glyph.size = s * static_cast<float>(image.height); | |
| 308 | ✗ | glyph.bearing_y = int_from_float(s * static_cast<float>(image.height) + image_bearing_y); | |
| 309 | ✗ | glyph.bearing_x = int_from_float(image_bearing_x); | |
| 310 | ✗ | glyph.advance = int_from_float(s * static_cast<float>(image.width) + image_advance); | |
| 311 | ✗ | glyph.code_point= font.generate_new_index_from_private_use(image_alias); | |
| 312 | // todo(Gustav): add ability to clip image | ||
| 313 | ✗ | glyph.image = image; | |
| 314 | ✗ | font.codepoint_to_glyph[glyph.code_point] = glyph; | |
| 315 | |||
| 316 | ✗ | return font; | |
| 317 | ✗ | } | |
| 318 | |||
| 319 | } | ||
| 320 | |||
| 321 |