libs/core/src/eu/core/loadedfont.cc
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "eu/core/loadedfont.h" | ||
| 2 | |||
| 3 | #include <set> | ||
| 4 | #include <optional> | ||
| 5 | #include <map> | ||
| 6 | |||
| 7 | #include "eustb_truetype.h" | ||
| 8 | |||
| 9 | #include "eu/log/log.h" | ||
| 10 | |||
| 11 | #include "eu/assert/assert.h" | ||
| 12 | |||
| 13 | #include "eu/base/cint.h" | ||
| 14 | #include "eu/base/memorychunk.h" | ||
| 15 | |||
| 16 | #include "eu/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 | float ascent_in_pixels = 0.0f; | ||
| 28 | float descent_in_pixels = 0.0f; | ||
| 29 | float line_gap_in_pixels = 0.0f; | ||
| 30 | |||
| 31 | std::vector<stbtt_kerningentry> kernings; | ||
| 32 | std::map<int, int> index_to_unicode; | ||
| 33 | |||
| 34 | ✗ | FontData(const 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_in_points = 0; | |
| 46 | ✗ | int descent_in_points = 0; | |
| 47 | ✗ | int line_gap_in_points = 0; | |
| 48 | ✗ | stbtt_GetFontVMetrics(&font, &ascent_in_points, &descent_in_points, &line_gap_in_points); | |
| 49 | ✗ | const float points_to_pixels = stbtt_ScaleForPixelHeight(&font, size); | |
| 50 | ✗ | ascent_in_pixels = float_from_int(ascent_in_points) * points_to_pixels; | |
| 51 | ✗ | descent_in_pixels = float_from_int(descent_in_points) * points_to_pixels; | |
| 52 | ✗ | line_gap_in_pixels = float_from_int(line_gap_in_points) * points_to_pixels; | |
| 53 | ✗ | } | |
| 54 | |||
| 55 | ✗ | void define_codepoint(int cp) | |
| 56 | { | ||
| 57 | ✗ | const int index = stbtt_FindGlyphIndex(&font, cp); | |
| 58 | ✗ | index_to_unicode[index] = cp; | |
| 59 | ✗ | } | |
| 60 | |||
| 61 | ✗ | [[nodiscard]] std::optional<int> from_glyph_index_to_unicode(int glyph) const | |
| 62 | { | ||
| 63 | ✗ | auto found = index_to_unicode.find(glyph); | |
| 64 | ✗ | if(found == index_to_unicode.end()) | |
| 65 | { | ||
| 66 | ✗ | return {}; | |
| 67 | } | ||
| 68 | else | ||
| 69 | { | ||
| 70 | ✗ | return found->second; | |
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | [[nodiscard]] LoadedGlyph | ||
| 75 | ✗ | load_glyph(int code_point) const | |
| 76 | { | ||
| 77 | ✗ | int width=0; int height=0; | |
| 78 | ✗ | int xoffset=0; int yoffset=0; | |
| 79 | ✗ | const auto scale = stbtt_ScaleForPixelHeight(&font, size); | |
| 80 | unsigned char *bitmap = stbtt_GetCodepointBitmap | ||
| 81 | ✗ | ( | |
| 82 | &font, | ||
| 83 | scale, | ||
| 84 | scale, | ||
| 85 | code_point, | ||
| 86 | &width, | ||
| 87 | &height, | ||
| 88 | &xoffset, | ||
| 89 | &yoffset | ||
| 90 | ); | ||
| 91 | |||
| 92 | ✗ | LoadedGlyph ch; | |
| 93 | ✗ | ch.code_point= code_point; | |
| 94 | ✗ | ch.size = size; | |
| 95 | ✗ | ch.bearing_x = xoffset; | |
| 96 | ✗ | ch.bearing_y = -yoffset; | |
| 97 | ✗ | ch.valid = true; | |
| 98 | { | ||
| 99 | ✗ | int advance_width = 0; | |
| 100 | ✗ | stbtt_GetCodepointHMetrics(&font, code_point, &advance_width, nullptr); | |
| 101 | ✗ | ch.advance = int_from_float(float_from_int(advance_width) * scale); | |
| 102 | } | ||
| 103 | ✗ | if(width == 0 && height == 0) | |
| 104 | { | ||
| 105 | ✗ | ch.image.make_invalid(); | |
| 106 | } | ||
| 107 | else | ||
| 108 | { | ||
| 109 | ✗ | ch.image.setup_with_alpha_support(width, height); | |
| 110 | |||
| 111 | ✗ | for(int y = 0; y < ch.image.height; y += 1) | |
| 112 | { | ||
| 113 | ✗ | for(int x = 0; x < ch.image.width; x += 1) | |
| 114 | { | ||
| 115 | ✗ | const auto alpha = bitmap[ch.image.width * y + x]; | |
| 116 | ch.image.set_pixel | ||
| 117 | ✗ | ( | |
| 118 | ✗ | x, ch.image.height-y-1, | |
| 119 | 255, 255, 255, | ||
| 120 | alpha | ||
| 121 | ); | ||
| 122 | } | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | ✗ | if(bitmap != nullptr) | |
| 127 | { | ||
| 128 | ✗ | stbtt_FreeBitmap(bitmap, nullptr); | |
| 129 | } | ||
| 130 | |||
| 131 | ✗ | return ch; | |
| 132 | ✗ | } | |
| 133 | }; | ||
| 134 | |||
| 135 | |||
| 136 | int | ||
| 137 | ✗ | LoadedFont::generate_new_index_from_private_use(const std::string& alias) | |
| 138 | { | ||
| 139 | // detect existing private use alias! | ||
| 140 | ✗ | const auto pu = next_private_use; | |
| 141 | ✗ | next_private_use += 1; | |
| 142 | ✗ | private_use_aliases[alias] = pu; | |
| 143 | ✗ | return pu; | |
| 144 | } | ||
| 145 | |||
| 146 | |||
| 147 | ✗ | bool LoadedFont::load_characters_from_font | |
| 148 | ( | ||
| 149 | const MemoryChunk& file_memory, | ||
| 150 | int font_size, | ||
| 151 | const std::string& chars | ||
| 152 | ) | ||
| 153 | { | ||
| 154 | auto f = FontData | ||
| 155 | { | ||
| 156 | ✗ | reinterpret_cast<const unsigned char*>(file_memory.bytes), | |
| 157 | float_from_int(font_size) | ||
| 158 | ✗ | }; | |
| 159 | |||
| 160 | ✗ | if(f.was_loaded == false) { return false; } | |
| 161 | |||
| 162 | ✗ | std::vector<int> code_points; | |
| 163 | calc_utf8_to_codepoints | ||
| 164 | ✗ | ( | |
| 165 | chars, | ||
| 166 | ✗ | [&](int cp){code_points.emplace_back(cp);} | |
| 167 | ); | ||
| 168 | |||
| 169 | ✗ | for(int code_point: code_points) | |
| 170 | { | ||
| 171 | ✗ | const LoadedGlyph cc = f.load_glyph(code_point); | |
| 172 | ✗ | if(!cc.valid) | |
| 173 | { | ||
| 174 | ✗ | LOG_INFO("Invalid codepoint {0}", code_point); | |
| 175 | ✗ | continue; | |
| 176 | } | ||
| 177 | ✗ | this->codepoint_to_glyph[code_point] = cc; | |
| 178 | ✗ | } | |
| 179 | |||
| 180 | ✗ | const float pixels_to_units = 1 / static_cast<float>(font_size); | |
| 181 | |||
| 182 | ✗ | this->ascent = f.ascent_in_pixels * pixels_to_units; | |
| 183 | ✗ | this->descent= f.descent_in_pixels * pixels_to_units; | |
| 184 | ✗ | this->line_gap = f.line_gap_in_pixels * pixels_to_units; | |
| 185 | |||
| 186 | ✗ | for(const auto& cp: code_points) | |
| 187 | { | ||
| 188 | ✗ | f.define_codepoint(cp); | |
| 189 | } | ||
| 190 | |||
| 191 | ✗ | for(const auto& info: f.kernings) | |
| 192 | { | ||
| 193 | ✗ | const auto previous = f.from_glyph_index_to_unicode(info.glyph1); | |
| 194 | ✗ | const auto current = f.from_glyph_index_to_unicode(info.glyph2); | |
| 195 | ✗ | const int dx = info.advance; | |
| 196 | ✗ | if(previous && current) | |
| 197 | { | ||
| 198 | this->kerning.insert | ||
| 199 | ✗ | ( | |
| 200 | ✗ | KerningMap::value_type | |
| 201 | ( | ||
| 202 | ✗ | KerningMap::key_type(*previous, *current), | |
| 203 | ✗ | static_cast<float>(dx) * pixels_to_units | |
| 204 | ✗ | ) | |
| 205 | ); | ||
| 206 | } | ||
| 207 | } | ||
| 208 | |||
| 209 | ✗ | return true; | |
| 210 | ✗ | } | |
| 211 | |||
| 212 | #if 0 | ||
| 213 | LoadedFont | ||
| 214 | get_characters_from_single_image | ||
| 215 | ( | ||
| 216 | io::FileSystem* fs, | ||
| 217 | const io::FilePath& image_file, | ||
| 218 | const std::string& image_alias, | ||
| 219 | float image_scale, | ||
| 220 | float image_bearing_x, | ||
| 221 | float image_bearing_y, | ||
| 222 | float image_advance | ||
| 223 | ) | ||
| 224 | { | ||
| 225 | const auto loaded = load_image | ||
| 226 | ( | ||
| 227 | fs, | ||
| 228 | image_file, | ||
| 229 | AlphaLoad::keep | ||
| 230 | ); | ||
| 231 | |||
| 232 | if(loaded.error.empty() == false) | ||
| 233 | { | ||
| 234 | LOG_ERR("Failed to load font image {}", image_file); | ||
| 235 | return LoadedFont{}; | ||
| 236 | } | ||
| 237 | |||
| 238 | return get_characters_from_single_image | ||
| 239 | ( | ||
| 240 | loaded.image, | ||
| 241 | image_alias, | ||
| 242 | image_scale, | ||
| 243 | image_bearing_x, | ||
| 244 | image_bearing_y, | ||
| 245 | image_advance | ||
| 246 | ); | ||
| 247 | } | ||
| 248 | #endif | ||
| 249 | |||
| 250 | |||
| 251 | LoadedFont | ||
| 252 | ✗ | get_characters_from_single_image | |
| 253 | ( | ||
| 254 | const Image& image, | ||
| 255 | const std::string& image_alias, | ||
| 256 | float image_scale, | ||
| 257 | float image_bearing_x, | ||
| 258 | float image_bearing_y, | ||
| 259 | float image_advance | ||
| 260 | ) | ||
| 261 | { | ||
| 262 | ✗ | LoadedFont font; | |
| 263 | |||
| 264 | ✗ | const auto s = 1 / image_scale; | |
| 265 | ✗ | LoadedGlyph glyph; | |
| 266 | ✗ | glyph.size = s * static_cast<float>(image.height); | |
| 267 | ✗ | glyph.bearing_y = int_from_float(s * static_cast<float>(image.height) + image_bearing_y); | |
| 268 | ✗ | glyph.bearing_x = int_from_float(image_bearing_x); | |
| 269 | ✗ | glyph.advance = int_from_float(s * static_cast<float>(image.width) + image_advance); | |
| 270 | ✗ | glyph.code_point= font.generate_new_index_from_private_use(image_alias); | |
| 271 | // todo(Gustav): add ability to clip image | ||
| 272 | ✗ | glyph.image = image; | |
| 273 | ✗ | font.codepoint_to_glyph[glyph.code_point] = glyph; | |
| 274 | |||
| 275 | ✗ | return font; | |
| 276 | ✗ | } | |
| 277 | |||
| 278 | } | ||
| 279 | |||
| 280 |