Files
vystem/Blastproof/fontgen/fontgen.cpp
2026-03-31 22:15:00 +02:00

260 lines
8.8 KiB
C++

// SPDX-License-Identifier: MPL-2.0
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <map>
#include <algorithm>
#include <filesystem>
#include <fstream>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
using namespace std;
namespace fs=filesystem;
struct color {
uint8_t r,g,b;
};
struct pixel {
bool shade_1;
bool check_1;
bool shade_2;
bool check_2;
bool shade_3;
bool check_3;
bool shade_4;
bool enabled;
};
struct character {
uint32_t codepoint;
vector<uint8_t> data;
};
struct fbm_header {
uint8_t sig[3]={'F','B','M'};
uint8_t encoding;
uint32_t charnum;
uint8_t width;
uint8_t height;
};
uint32_t charname_to_uint32(string charname) {
uint32_t value=0;
stringstream ss;
ss<<hex<<charname;
ss>>value;
int shift=(8-charname.size())*4;
value<<=shift;
return value;
}
int get_shade_from_color(const color &c,const color gradient[16]) {
for (int i=0;i<16;++i) {
if (gradient[i].r==c.r && gradient[i].g==c.g && gradient[i].b==c.b) {
return i;
}
}
return -1;
}
uint8_t pixel_to_byte(pixel p) {
uint8_t out=0x00;
out|=(uint8_t)p.shade_1<<7;
out|=(uint8_t)p.check_1<<6;
out|=(uint8_t)p.shade_2<<5;
out|=(uint8_t)p.check_2<<4;
out|=(uint8_t)p.shade_3<<3;
out|=(uint8_t)p.check_3<<2;
out|=(uint8_t)p.shade_4<<1;
out|=(uint8_t)p.enabled;
return out;
}
map<uint32_t,vector<unsigned char>> chars_data;
vector<string> known_encoding={"ascii","utf8","utf16"};
int main(int argc,char **argv) {
if (argc!=5) {
cout<<"[Fontgen] Error: wrong amount of arguments."<<endl;
return -1;
}
string bg_color=string(argv[1]);
string ft_color=string(argv[2]);
if (bg_color.size()!=7 || bg_color[0]!='#') {
cout<<"[Fontgen] Error: invalid background color."<<endl;
return -1;
}
if (ft_color.size()!=7 || ft_color[0]!='#') {
cout<<"[Fontgen] Error: invalid font color."<<endl;
return -1;
}
for (size_t i=1;i<7;++i) {
char bg=bg_color[i];
char ft=ft_color[i];
if (!isxdigit(static_cast<unsigned char>(bg))) {
cout<<"[Fontgen] Error: invalid background color."<<endl;
return -1;
}
if (!isxdigit(static_cast<unsigned char>(ft))) {
cout<<"[Fontgen] Error: invalid font color."<<endl;
return -1;
}
}
color start,end;
start.r=static_cast<unsigned char>(std::stoi(bg_color.substr(1,2),nullptr,16));
start.g=static_cast<unsigned char>(std::stoi(bg_color.substr(3,2),nullptr,16));
start.b=static_cast<unsigned char>(std::stoi(bg_color.substr(5,2),nullptr,16));
end.r=static_cast<unsigned char>(std::stoi(ft_color.substr(1,2),nullptr,16));
end.g=static_cast<unsigned char>(std::stoi(ft_color.substr(3,2),nullptr,16));
end.b=static_cast<unsigned char>(std::stoi(ft_color.substr(5,2),nullptr,16));
color gradient[16];
for (int i=0;i<16;++i) {
gradient[i].r=start.r+i*(end.r-start.r)/15.0f;
gradient[i].g=start.g+i*(end.g-start.g)/15.0f;
gradient[i].b=start.b+i*(end.b-start.b)/15.0f;
}
string encoding=string(argv[3]);
if (find(known_encoding.begin(),known_encoding.end(),encoding)==known_encoding.end()) {
cout<<"[Fontgen] Error: encoding not supported."<<endl;
return -1;
}
string font_folder=string(argv[4]);
if (!fs::exists(font_folder)) {
cout<<"[Fontgen] Error: provided path doesn't exist."<<endl;
return -1;
}
if (!fs::is_directory(font_folder)) {
cout<<"[Fontgen] Error: provided path isn't a folder."<<endl;
return -1;
}
int width=0,height=0;
int i=0;
for (auto const &entry:fs::directory_iterator(font_folder)) {
if (!entry.is_regular_file()) {
cout<<"[Fontgen] Error: "<<entry.path()<<" isn't a file."<<endl;
return -1;
}
string filenam=entry.path().filename().string();
if (filenam.size()<6) {
cout<<"[Fontgen] Warning: "<<entry.path()<<" filename too short. Skipping."<<endl;
continue;
}
string ext=filenam.substr(filenam.size()-4);
if (ext!=".png" && ext!=".PNG") {
cout<<"[Fontgen] Warning: "<<entry.path()<<" is not a PNG. Skipping."<<endl;
continue;
}
if (filenam.size()<5 || filenam.substr(0,2)!="0x") {
cout<<"[Fontgen] Warning: "<<entry.path()<<" has an ambiguious file name. Skipping."<<endl;
continue;
}
string hexpart=filenam.substr(2,filenam.size()-6);
if (hexpart.empty() || hexpart.size()>8) {
cout<<"[Fontgen] Warning: "<<entry.path()<<" has an invalid hex code. Skipping."<<endl;
continue;
}
uint32_t key=charname_to_uint32(hexpart);
int channel=0,w=0,h=0;
unsigned char* data=stbi_load(entry.path().string().c_str(),&w,&h,&channel,0);
if (!data) {
cout<<"[Fontgen] Error: couldn't read file: "<<filenam<<endl;
return -1;
}
if (i==0) {
width=w;
height=h;
} else {
if (w!=width || h!=height) {
cout<<"[Fontgen] Error: all file aren't the same size. File that caused the error: "<<filenam<<endl;
return -1;
}
}
if (channel!=3) {
cout<<"[Fontgen] Error: only files with three channels are accepted. File that caused the error: "<<filenam<<endl;
return -1;
}
vector<unsigned char> pixels(data,data+w*h*channel);
stbi_image_free(data);
chars_data[key]=std::move(pixels);
i++;
}
vector<character> characters;
for (auto it:chars_data) {
character chara;
chara.codepoint=it.first;
uint8_t byte1=(chara.codepoint>>24) & 0xFF;
uint8_t byte2=(chara.codepoint>>16) & 0xFF;
uint8_t byte3=(chara.codepoint>>8) & 0xFF;
bool pcheck_1=(byte1>>1) & 1;
bool pcheck_2=(byte2>>3) & 1;
bool pcheck_3=(byte3>>5) & 1;
for (int y=0;y<height;++y) {
for (int x=0;x<width;++x) {
int idx=(y*width+x)*3;
color c;
c.r=it.second[idx];
c.g=it.second[idx+1];
c.b=it.second[idx+2];
int i=get_shade_from_color(c,gradient);
if (i==-1 || i>15 || i<0) {
cout<<"[Fontgen] Error: found a color that isn't in shade map. Codepoint that caused the error: "<<hex<<setw(2)<<setfill('0')<<it.first<<endl;
return -1;
}
uint8_t shade=i;
pixel p{};
p.shade_1=(shade>>3)&1;
p.shade_2=(shade>>2)&1;
p.shade_3=(shade>>1)&1;
p.shade_4=(shade>>0)&1;
if (shade!=0) {
p.enabled=true;
} else {
p.enabled=false;
}
p.check_1=pcheck_1;
p.check_2=pcheck_2;
p.check_3=pcheck_3;
uint8_t byte=pixel_to_byte(p);
chara.data.push_back(byte);
}
}
characters.push_back(chara);
}
fbm_header header;
header.charnum=(uint32_t)characters.size();
if (width>255 || width<1) {
cout<<"[Fontgen] Error: width isn't between 1 and 255."<<endl;
return -1;
}
header.width=width;
if (height>255 || height<1) {
cout<<"[Fontgen] Error: height isn't between 1 and 255."<<endl;
return -1;
}
header.height=height;
if (encoding=="ascii") {
header.encoding=1;
} else if (encoding=="utf8") {
header.encoding=8;
} else if (encoding=="utf16") {
header.encoding=16;
} else {
header.encoding=255;
}
vector<unsigned char> fbm_font;
size_t charsize=4+header.width*header.height;
fbm_font.resize(10+header.charnum*charsize);
memcpy(fbm_font.data(),header.sig,3);
memcpy(fbm_font.data()+3,&header.encoding,1);
memcpy(fbm_font.data()+4,&header.charnum,4);
memcpy(fbm_font.data()+8,&header.width,1);
memcpy(fbm_font.data()+9,&header.height,1);
for (int i=0;i<characters.size();++i) {
size_t char_offset=10+i*charsize;
memcpy(fbm_font.data()+char_offset,&characters[i].codepoint,4);
memcpy(fbm_font.data()+char_offset+4,characters[i].data.data(),characters[i].data.size());
}
ofstream fileout("font.fbm",ios::binary);
if (!fileout) {
cout<<"[Fontgen] Error: couldn't open output file."<<endl;
return -1;
}
fileout.write(reinterpret_cast<const char*>(fbm_font.data()),fbm_font.size());
fileout.close();
cout<<"[Fontgen] Successfully generated font.fbm ("<<fbm_font.size()<<" bytes, "<<characters.size()<<" glyphs)"<<endl;
return 0;
}