Rought font extractor that probably has a memory error causing it to behave mysteriously, and the designer now uses a json file of the characters that will work.
parent
9ab064126f
commit
a9e25668fb
@ -0,0 +1,207 @@ |
|||||||
|
#include <fstream> |
||||||
|
#include <iostream> |
||||||
|
#include <chrono> // for operator""s, chrono_literals |
||||||
|
#include <thread> // for sleep_for |
||||||
|
#include "dbc.hpp" |
||||||
|
#include <filesystem> |
||||||
|
#include <fcntl.h> |
||||||
|
#include "constants.hpp" |
||||||
|
#include <fmt/core.h> |
||||||
|
#include <locale> |
||||||
|
#include <codecvt> |
||||||
|
#include <vector> |
||||||
|
#include <nlohmann/json.hpp> |
||||||
|
#include <ft2build.h> |
||||||
|
#include <SFML/Graphics/Font.hpp> |
||||||
|
#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<FontIndex> $index; |
||||||
|
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> $converter; |
||||||
|
vector<string> $charmap; |
||||||
|
vector<wchar_t> $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 <input> <output>"); |
||||||
|
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()); |
||||||
|
} |
Loading…
Reference in new issue