You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
208 lines
5.5 KiB
208 lines
5.5 KiB
23 hours ago
|
#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());
|
||
|
}
|