#include #include #include // for operator""s, chrono_literals #include // for sleep_for #include "dbc.hpp" #include #include #include "constants.hpp" #include #include #include #include #include #include #include #include FT_FREETYPE_H #include FT_TRUETYPE_TABLES_H #include FT_TRUETYPE_IDS_H using namespace nlohmann; using namespace fmt; namespace fs = std::filesystem; using std::string, std::wstring, std::vector; const size_t CLEAR_CACHE_POINT=100; struct FontIndex { size_t cm_index; // charmap index string glyph; }; struct FontExtractor { size_t $clear_count = 1; wchar_t ui_base_char = L'█'; vector $index; std::wstring_convert> $converter; vector $charmap; vector $wcharmap; sf::Font $font; sf::FloatRect $grid_bounds; int $font_size; fs::path $font_path; json $results; FontExtractor(fs::path font_path) : $font_path(font_path) { bool good = $font.loadFromFile($font_path.string()); dbc::check(good, format("failed to load font {}", $font_path.string())); $font.setSmooth(false); for(int i = 100; i < 200; i++) { auto glyph = $font.getGlyph(ui_base_char, i, false); if(glyph.bounds.width > 0 && glyph.bounds.height > 0) { $grid_bounds = glyph.bounds; $font_size = i; break; } } dbc::check($grid_bounds.width > 0 && $grid_bounds.height > 0, "couldn't find a valid font size"); println("!!!!!!!!!!!!!!!!!!!!! FONT SIZE {}", $font_size); } void configure_font() { FT_ULong charcode; FT_UInt gindex; FT_String buf[32] = {0}; FT_Library library; FT_Face face; auto error = FT_Init_FreeType(&library); dbc::check(!error, "Failed to initialize freetype library."); error = FT_New_Face(library, FONT_FILE_NAME, 0, &face); dbc::check(face->num_faces == 1, format("Font {} has {} but I only support 1.", FONT_FILE_NAME, face->num_faces)); dbc::check(face->charmap, "Font doesn't have a charmap."); println("Font charmaps {}", face->num_charmaps); auto active = FT_Get_Charmap_Index(face->charmap); for(int i = 0; i < face->num_charmaps; i++) { auto format = FT_Get_CMap_Format(face->charmaps[i]); auto lang_id = FT_Get_CMap_Language_ID(face->charmaps[i]); println("{}: {} {}, active: {}, platform: {}, encoding: {}, ms_unicode: {}, language: {}", i, format >= 0 ? "format" : "synthetic", format, i == active, face->charmaps[i]->platform_id, face->charmaps[i]->encoding_id, TT_MS_ID_UNICODE_CS, lang_id); // BUG: this is windows only if(face->charmaps[i]->encoding_id == TT_MS_ID_UNICODE_CS) { FT_Set_Charmap(face, face->charmaps[i]); } } bool has_names = FT_HAS_GLYPH_NAMES(face); // get the first char of the map charcode = FT_Get_First_Char(face, &gindex); // go through every char and pre-convert for(int i = 0; gindex; i++) { if(has_names) FT_Get_Glyph_Name(face, gindex, buf, 32 ); // store it in my pre-convert charmap for later wstring wcodestr{(wchar_t)charcode}; string codestr = from_unicode(wcodestr); $charmap.emplace_back(codestr); $wcharmap.push_back(charcode); // keep going charcode = FT_Get_Next_Char(face, charcode, &gindex ); } FT_Done_Face(face); FT_Done_FreeType(library); println("FreeType extracted {} glyphs.", $charmap.size()); } size_t next_valid_char(size_t cur_char) { for(size_t i = cur_char+1; i < $wcharmap.size(); i++) { wchar_t test_char = $wcharmap[i]; if($font.hasGlyph(test_char)) { auto glyph = $font.getGlyph(test_char, $font_size, false); auto bounds = glyph.bounds; // skip bad chars if(bounds.width <= 0 || bounds.height <= 0) continue; if(bounds.width <= $grid_bounds.width && bounds.height <= $grid_bounds.height) { return i; } } clear_font_cache(); } return 0; } void extract_valid() { size_t cur_char = next_valid_char(1); while(cur_char != 0) { string out = from_unicode(wstring_at(cur_char)); $results.push_back(out); cur_char = next_valid_char(cur_char + 1); } } string from_unicode(wstring input) { try { return $converter.to_bytes(input); } catch(...) { return $converter.to_bytes(L"?"); } } string at(size_t x) { return $index[x].glyph; } wstring wstring_at(size_t cm_index) { return wstring{$wcharmap[cm_index]}; } void clear_font_cache() { if($clear_count % CLEAR_CACHE_POINT == 0) { bool good = $font.loadFromFile($font_path.string()); dbc::check(good, format("failed to load font {}", $font_path.string())); $font.setSmooth(false); $clear_count++; } } }; int main(int argc, char *argv[]) { if(argc != 3) { println("USAGE: fontextractor "); return 1; } fs::path font_path = argv[1]; fs::path out_path = argv[2]; dbc::check(fs::exists(font_path), "ERROR: input file doesn't exist"); FontExtractor fex(font_path); fex.configure_font(); fex.extract_valid(); std::ofstream json_out(out_path, std::ios::binary); json_out << fex.$results.dump(2) << std::endl; println("Wrote {} chars to {}.", fex.$results.size(), out_path.string()); }