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.

main
Zed A. Shaw 18 hours ago
parent 9ab064126f
commit a9e25668fb
  1. 2
      assets/config.json
  2. 1
      constants.hpp
  3. 8
      meson.build
  4. 2
      render.cpp
  5. 7
      status.txt
  6. 110
      tools/designer.cpp
  7. 207
      tools/fontextract.cpp

@ -3,7 +3,7 @@
"WALL_TILE": "\ua5b8", "WALL_TILE": "\ua5b8",
"FLOOR_TILE": "\u2849", "FLOOR_TILE": "\u2849",
"PLAYER_TILE": "\ua66b", "PLAYER_TILE": "\ua66b",
"ENEMY_TILE": "Ω", "ENEMY_TILE": "\u1d5c",
"BG_TILE": "█", "BG_TILE": "█",
"WATER_TILE": "\u26c6" "WATER_TILE": "\u26c6"
}, },

@ -17,3 +17,4 @@ const int MAX_FONT_SIZE = 140;
const int MIN_FONT_SIZE = 20; const int MIN_FONT_SIZE = 20;
const int SCREEN_WIDTH = 40; const int SCREEN_WIDTH = 40;
const int SCREEN_HEIGHT = 30; const int SCREEN_HEIGHT = 30;
#define FONT_FILE_NAME "./assets/text.otf"

@ -92,6 +92,14 @@ designer = executable('designer', [
], ],
dependencies: dependencies) dependencies: dependencies)
fontextract = executable('fontextract', [
'dbc.cpp',
'rand.cpp',
'config.cpp',
'tools/fontextract.cpp'
],
dependencies: dependencies)
img2ansi = executable('img2ansi', [ img2ansi = executable('img2ansi', [
'dbc.cpp', 'dbc.cpp',
'panel.cpp', 'panel.cpp',

@ -25,7 +25,7 @@ SFMLRender::SFMLRender() :
$ansi($default_fg, $default_bg) $ansi($default_fg, $default_bg)
{ {
// force true color, but maybe I want to support different color sets // force true color, but maybe I want to support different color sets
$font.loadFromFile("./assets/text.otf"); $font.loadFromFile(FONT_FILE_NAME);
$font.setSmooth(false); $font.setSmooth(false);
$ui_text.setFont($font); $ui_text.setFont($font);
$ui_text.setPosition(0,0); $ui_text.setPosition(0,0);

@ -1,10 +1,13 @@
TODAY'S GOAL: TODAY'S GOAL:
====
Font Extractor
===
1. Why do Sliders only have to be kept around forever and can't go in containers like everything else? 1. Why do Sliders only have to be kept around forever and can't go in containers like everything else?
2. Why are sliders not selected when I click on them? Is it a hover? 2. Why are sliders not selected when I click on them? Is it a hover?
3. Why do fonts render blank? Also when I scroll they slowly disappear until there's a column. 3. Why do fonts render blank? Also when I scroll they slowly disappear until there's a column.
4. Use freetype to iterate chars. * \u2738 is missing on the row when in grid but works when clicked.
5. Why are all wchar converted chars in from_unicode 3 bytes not 2?
* A designer tool to help find characters for foreground, background, and figure out their colors. * A designer tool to help find characters for foreground, background, and figure out their colors.

@ -1,3 +1,5 @@
#include <iostream>
#include <fstream>
#include <chrono> // for operator""s, chrono_literals #include <chrono> // for operator""s, chrono_literals
#include <thread> // for sleep_for #include <thread> // for sleep_for
#include "dinkyecs.hpp" #include "dinkyecs.hpp"
@ -13,9 +15,9 @@
#include <locale> #include <locale>
#include <codecvt> #include <codecvt>
#include <vector> #include <vector>
#include <ft2build.h> #include <nlohmann/json.hpp>
#include FT_FREETYPE_H
using namespace nlohmann;
using namespace fmt; using namespace fmt;
using namespace ftxui; using namespace ftxui;
using namespace std::chrono_literals; using namespace std::chrono_literals;
@ -26,58 +28,80 @@ using std::string, std::wstring, std::vector;
const Point GRID_SIZE={15,8}; const Point GRID_SIZE={15,8};
const int DEFAULT_FONT_SIZE=200; const int DEFAULT_FONT_SIZE=200;
struct FontGridCell {
size_t cm_index;
string as_string;
wstring as_wstring;
};
struct FontGrid { struct FontGrid {
size_t width; size_t width;
size_t height; size_t height;
vector<vector<string>> $chars; vector<vector<FontGridCell>> $grid;
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> $converter; std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> $converter;
vector<wstring> $wcharmap;
vector<string> $charmap;
FontGrid(size_t width, size_t height) : FontGrid(size_t width, size_t height) :
width(width), height(height), width(width), height(height),
$chars(height, vector<string>(width, "")) $grid(height, vector<FontGridCell>(width, {0, "", L""}))
{ {
configure_font();
} }
void render(wchar_t start_char, bool fill) { void configure_font() {
wchar_t cur_char = start_char; std::ifstream in_file("./fontlist.json");
json input = json::parse(in_file);
for(auto inchar : input) {
$charmap.push_back(inchar);
$wcharmap.push_back($converter.from_bytes(inchar));
}
}
size_t max_chars() {
return $wcharmap.size();
}
void render(size_t start_char, bool fill) {
size_t next_char = start_char;
for(size_t y = 0; y < height; ++y) { for(size_t y = 0; y < height; ++y) {
for(size_t x = 0; x < width; ++x) { for(size_t x = 0; x < width; ++x) {
if(!fill) { if(!fill) {
cur_char += (x+1) * (y+1); next_char++;
} }
wstring out_w{cur_char}; $grid[y][x] = {
$chars[y][x] = from_unicode(out_w); .cm_index = next_char,
.as_string = $charmap[next_char],
.as_wstring = $wcharmap[next_char]
};
} }
} }
} }
string from_unicode(wstring input) { size_t charmap_index(size_t x, size_t y) {
try { FontGridCell &cell = $grid[y][x];
return $converter.to_bytes(input); return cell.cm_index;
} catch(...) {
return $converter.to_bytes(L"?");
}
} }
wchar_t to_unicode_char(size_t x, size_t y) { string& as_string(size_t x, size_t y) {
try { return $grid[y][x].as_string;
string input = $chars[y][x]; // BUG: bounds check this instead }
return $converter.from_bytes(input)[0];
} catch(...) { wstring& as_wstring(size_t x, size_t y) {
return L'?'; return $grid[y][x].as_wstring;
}
} }
string at(size_t x, size_t y) { wchar_t as_wchar(size_t cm_index) {
return $chars[y][x]; return $wcharmap[cm_index][0];
} }
unsigned int page_size() { unsigned int page_size() {
return width * height; return width * height;
} }
}; };
struct WhatTheColor { struct WhatTheColor {
@ -97,8 +121,8 @@ class GUI {
Canvas $canvas; Canvas $canvas;
SFMLRender $renderer; SFMLRender $renderer;
FontGrid $font_grid; FontGrid $font_grid;
wchar_t $start_char = L'\u28cc'; size_t $start_char = 0;
wchar_t $fill_char = WCHAR_MIN; size_t $fill_char = 0;
WhatTheColor $fg_color; WhatTheColor $fg_color;
WhatTheColor $bg_color; WhatTheColor $bg_color;
Component $fg_settings; Component $fg_settings;
@ -113,7 +137,11 @@ class GUI {
$bg_color{.h=100, .s=100, .v=100} $bg_color{.h=100, .s=100, .v=100}
{ {
resize_fonts(DEFAULT_FONT_SIZE); resize_fonts(DEFAULT_FONT_SIZE);
$font_grid.render($start_char, false); render_grid($start_char, false);
}
void render_grid(size_t start_char, bool fill) {
$font_grid.render(start_char, fill);
} }
void resize_fonts(int new_size) { void resize_fonts(int new_size) {
@ -124,11 +152,12 @@ class GUI {
} }
void draw_font_grid() { void draw_font_grid() {
int flip_it = 0;
for(size_t y = 0; y < $font_grid.height; y++) { for(size_t y = 0; y < $font_grid.height; y++) {
for(size_t x = 0; x < $font_grid.width; x++) { for(size_t x = 0; x < $font_grid.width; x++, flip_it++) {
$canvas.DrawText(x * 2, y * 4, $font_grid.at(x, y), [&](auto &pixel) { $canvas.DrawText(x * 2, y * 4, $font_grid.as_string(x, y), [&](auto &pixel) {
pixel.foreground_color = Color::HSV($fg_color.h, $fg_color.s, $fg_color.v); pixel.foreground_color = Color::HSV($fg_color.h, $fg_color.s, $fg_color.v);
pixel.background_color = Color::HSV($bg_color.h, $bg_color.s, $bg_color.v); pixel.background_color = Color::HSV($bg_color.h, $bg_color.s, $bg_color.v / (flip_it % 2 * 2 + 1));
}); });
} }
} }
@ -150,10 +179,12 @@ class GUI {
$bg_color.v_slider = Slider("BG V:", &$bg_color.v, 0, 255, 1); $bg_color.v_slider = Slider("BG V:", &$bg_color.v, 0, 255, 1);
$status_ui.set_renderer(Renderer([&]{ $status_ui.set_renderer(Renderer([&]{
wchar_t fill_char = $font_grid.as_wchar($fill_char);
return hbox({ return hbox({
hflow( hflow(
vbox( vbox(
text(format("\\u{:x} {} MIN: {}, MAX: {}", int($fill_char), int($fill_char), WCHAR_MIN, WCHAR_MAX)) | border, text(format("\\u{:x} {} IDX: {}, MIN: {}, MAX: {}", int(fill_char), int(fill_char), $fill_char, WCHAR_MIN, WCHAR_MAX)) | border,
separator(), separator(),
text(format("FG H: {}, S: {}, V: {}", text(format("FG H: {}, S: {}, V: {}",
$fg_color.h, $fg_color.s, $fg_color.v)) | border, $fg_color.h, $fg_color.s, $fg_color.v)) | border,
@ -190,12 +221,12 @@ class GUI {
} }
void select_cell(Point pos) { void select_cell(Point pos) {
$fill_char = $font_grid.to_unicode_char(pos.x, pos.y); $fill_char = $font_grid.charmap_index(pos.x, pos.y);
$font_grid.render($fill_char, true); render_grid($fill_char, true);
} }
void deselect_cell() { void deselect_cell() {
$font_grid.render($start_char, false); render_grid($start_char, false);
} }
bool handle_ui_events() { bool handle_ui_events() {
@ -210,13 +241,13 @@ class GUI {
return true; return true;
} else if(event.type == sf::Event::KeyPressed) { } else if(event.type == sf::Event::KeyPressed) {
if(KB::isKeyPressed(KB::Up)) { if(KB::isKeyPressed(KB::Up)) {
$start_char = std::max(WCHAR_MIN+1, $start_char - $font_grid.page_size()); $start_char = std::max(size_t(1), $start_char - $font_grid.page_size());
$font_grid.render($start_char, false); render_grid($start_char, false);
event_happened = true; event_happened = true;
$renderer.clear_cache(); $renderer.clear_cache();
} else if(KB::isKeyPressed(KB::Down)) { } else if(KB::isKeyPressed(KB::Down)) {
$start_char = std::min(WCHAR_MAX, $start_char + $font_grid.page_size()); $start_char = std::min($font_grid.max_chars(), $start_char + $font_grid.page_size());
$font_grid.render($start_char, false); render_grid($start_char, false);
$renderer.clear_cache(); $renderer.clear_cache();
} else if(KB::isKeyPressed(KB::Equal)) { } else if(KB::isKeyPressed(KB::Equal)) {
resize_fonts(font_size + 10); resize_fonts(font_size + 10);
@ -264,7 +295,6 @@ int main(int argc, char *argv[]) {
gui.render_scene(); gui.render_scene();
if(gui.handle_ui_events()) { if(gui.handle_ui_events()) {
println("THERE WERE EVENTS");
} }
std::this_thread::sleep_for(10ms); std::this_thread::sleep_for(10ms);

@ -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…
Cancel
Save