260 lines
8.8 KiB
C++
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;
|
|
}
|