#include "datafile.hh" #include "importtools.hh" #include "bdf_import.hh" #include "freetype_import.hh" #include "export_rlefont.hh" #include "encode_rlefont.hh" #include "optimize_rlefont.hh" #include "export_bwfont.hh" #include #include #include #include #include #include #include #include #include #include "ccfixes.hh" using namespace mcufont; static std::string strip_extension(std::string filename) { size_t pos = filename.find_last_of('.'); if (pos == std::string::npos) { return filename; } else { return filename.substr(0, pos); } } static std::unique_ptr load_dat(std::string src) { std::ifstream infile(src); if (!infile.good()) { std::cerr << "Could not open " << src << std::endl; return nullptr; } std::unique_ptr f = DataFile::Load(infile); if (!f) { std::cerr << "Invalid format for .dat file: " << src << std::endl; return nullptr; } return f; } static bool save_dat(std::string dest, DataFile *f) { std::ofstream outfile(dest); if (!outfile.good()) { std::cerr << "Could not open " << dest << std::endl; return false; } f->Save(outfile); if (!outfile.good()) { std::cerr << "Could not write to " << dest << std::endl; return false; } return true; } enum status_t { STATUS_OK = 0, // All good STATUS_INVALID = 1, // Invalid command or args STATUS_ERROR = 2 // Error when executing command }; static status_t cmd_import_ttf(const std::vector &args) { if (args.size() != 3 && args.size() != 4) return STATUS_INVALID; std::string src = args.at(1); int size = std::stoi(args.at(2)); bool bw = (args.size() == 4 && args.at(3) == "bw"); std::string dest = strip_extension(src) + std::to_string(size) + (bw ? "bw" : "") + ".dat"; std::ifstream infile(src); if (!infile.good()) { std::cerr << "Could not open " << src << std::endl; return STATUS_ERROR; } std::cout << "Importing " << src << " to " << dest << std::endl; std::unique_ptr f = LoadFreetype(infile, size, bw); mcufont::rlefont::init_dictionary(*f); if (!save_dat(dest, f.get())) return STATUS_ERROR; std::cout << "Done: " << f->GetGlyphCount() << " unique glyphs." << std::endl; return STATUS_OK; } static status_t cmd_import_bdf(const std::vector &args) { if (args.size() != 2) return STATUS_INVALID; std::string src = args.at(1); std::string dest = strip_extension(args.at(1)) + ".dat"; std::ifstream infile(src); if (!infile.good()) { std::cerr << "Could not open " << src << std::endl; return STATUS_ERROR; } std::cout << "Importing " << src << " to " << dest << std::endl; std::unique_ptr f = LoadBDF(infile); mcufont::rlefont::init_dictionary(*f); if (!save_dat(dest, f.get())) return STATUS_ERROR; std::cout << "Done: " << f->GetGlyphCount() << " unique glyphs." << std::endl; return STATUS_OK; } static status_t cmd_filter(const std::vector &args) { if (args.size() < 3) return STATUS_INVALID; std::set allowed; // Parse arguments for (size_t i = 2; i < args.size(); i++) { std::string s = args.at(i); size_t pos = s.find('-'); if (pos == std::string::npos) { // Single char allowed.insert(std::stoi(s, nullptr, 0)); } else { // Range int start = std::stoi(s.substr(0, pos), nullptr, 0); int end = std::stoi(s.substr(pos + 1), nullptr, 0); for (int j = start; j <= end; j++) { allowed.insert(j); } } } std::string src = args.at(1); std::unique_ptr f = load_dat(src); if (!f) return STATUS_ERROR; std::cout << "Font originally had " << f->GetGlyphCount() << " glyphs." << std::endl; // Filter the glyphs std::vector newglyphs; for (size_t i = 0; i < f->GetGlyphCount(); i++) { DataFile::glyphentry_t g = f->GetGlyphEntry(i); for (size_t j = 0; j < g.chars.size(); j++) { if (!allowed.count(g.chars.at(j))) { g.chars.erase(g.chars.begin() + j); j--; } } if (g.chars.size()) { newglyphs.push_back(g); } } DataFile::fontinfo_t fontinfo = f->GetFontInfo(); crop_glyphs(newglyphs, fontinfo); detect_flags(newglyphs, fontinfo); f.reset(new DataFile(f->GetDictionary(), newglyphs, fontinfo)); std::cout << "After filtering, " << f->GetGlyphCount() << " glyphs remain." << std::endl; if (!save_dat(src, f.get())) return STATUS_ERROR; return STATUS_OK; } static status_t cmd_show_glyph(const std::vector &args) { if (args.size() != 3) return STATUS_INVALID; std::string src = args.at(1); std::unique_ptr f = load_dat(src); if (!f) return STATUS_ERROR; size_t index = 0; if (args.at(2) == "largest") { std::unique_ptr e = mcufont::rlefont::encode_font(*f, false); size_t maxlen = 0; size_t i = 0; for (mcufont::rlefont::encoded_font_t::refstring_t g : e->glyphs) { if (g.size() > maxlen) { maxlen = g.size(); index = i; } i++; } std::cout << "Index " << index << ", length " << maxlen << std::endl; } else { index = strtol(args.at(2).c_str(), nullptr, 0); } if (index < 0 || index >= f->GetGlyphCount()) { std::cerr << "No such glyph " << index << std::endl; return STATUS_ERROR; } std::cout << "Width: " << f->GetGlyphEntry(index).width << std::endl; std::cout << "Chars: "; for (int c: f->GetGlyphEntry(index).chars) std::cout << c << " "; std::cout << std::endl; std::cout << f->GlyphToText(index); return STATUS_OK; } static status_t cmd_rlefont_export(const std::vector &args) { if (args.size() != 2 && args.size() != 3) return STATUS_INVALID; std::string src = args.at(1); std::string dst = (args.size() == 2) ? strip_extension(src) + ".c" : args.at(2); std::unique_ptr f = load_dat(src); if (!f) return STATUS_ERROR; { std::ofstream source(dst); mcufont::rlefont::write_source(source, dst, *f); std::cout << "Wrote " << dst << std::endl; } return STATUS_OK; } static status_t cmd_rlefont_size(const std::vector &args) { if (args.size() != 2) return STATUS_INVALID; std::string src = args.at(1); std::unique_ptr f = load_dat(src); if (!f) return STATUS_ERROR; size_t size = mcufont::rlefont::get_encoded_size(*f); std::cout << "Glyph count: " << f->GetGlyphCount() << std::endl; std::cout << "Glyph bbox: " << f->GetFontInfo().max_width << "x" << f->GetFontInfo().max_height << " pixels" << std::endl; std::cout << "Uncompressed size: " << f->GetGlyphCount() * f->GetFontInfo().max_width * f->GetFontInfo().max_height / 2 << " bytes" << std::endl; std::cout << "Compressed size: " << size << " bytes" << std::endl; std::cout << "Bytes per glyph: " << size / f->GetGlyphCount() << std::endl; return STATUS_OK; } static status_t cmd_rlefont_optimize(const std::vector &args) { if (args.size() != 2 && args.size() != 3) return STATUS_INVALID; std::string src = args.at(1); std::unique_ptr f = load_dat(src); if (!f) return STATUS_ERROR; size_t oldsize = mcufont::rlefont::get_encoded_size(*f); std::cout << "Original size is " << oldsize << " bytes" << std::endl; std::cout << "Press ctrl-C at any time to stop." << std::endl; std::cout << "Results are saved automatically after each iteration." << std::endl; int limit = 100; if (args.size() == 3) { limit = std::stoi(args.at(2)); } if (limit > 0) std::cout << "Limit is " << limit << " iterations" << std::endl; int i = 0; time_t oldtime = time(NULL); while (!limit || i < limit) { mcufont::rlefont::optimize(*f); size_t newsize = mcufont::rlefont::get_encoded_size(*f); time_t newtime = time(NULL); int bytes_per_min = (oldsize - newsize) * 60 / (newtime - oldtime + 1); i++; std::cout << "iteration " << i << ", size " << newsize << " bytes, speed " << bytes_per_min << " B/min" << std::endl; { if (!save_dat(src, f.get())) return STATUS_ERROR; } } return STATUS_OK; } static status_t cmd_rlefont_show_encoded(const std::vector &args) { if (args.size() != 2) return STATUS_INVALID; std::string src = args.at(1); std::unique_ptr f = load_dat(src); if (!f) return STATUS_ERROR; std::unique_ptr e = mcufont::rlefont::encode_font(*f, false); int i = 0; for (mcufont::rlefont::encoded_font_t::rlestring_t d : e->rle_dictionary) { std::cout << "Dict RLE " << 24 + i++ << ": "; for (uint8_t v : d) std::cout << std::setfill('0') << std::setw(2) << std::hex << (int)v << " "; std::cout << std::endl; } for (mcufont::rlefont::encoded_font_t::refstring_t d : e->ref_dictionary) { std::cout << "Dict Ref " << 24 + i++ << ": "; for (uint8_t v : d) std::cout << std::setfill('0') << std::setw(2) << std::hex << (int)v << " "; std::cout << std::endl; } i = 0; for (mcufont::rlefont::encoded_font_t::refstring_t g : e->glyphs) { std::cout << "Glyph " << i++ << ": "; for (uint8_t v : g) std::cout << std::setfill('0') << std::setw(2) << std::hex << (int)v << " "; std::cout << std::endl; } return STATUS_OK; } static status_t cmd_bwfont_export(const std::vector &args) { if (args.size() != 2 && args.size() != 3) return STATUS_INVALID; std::string src = args.at(1); std::string dst = (args.size() == 2) ? strip_extension(src) + ".c" : args.at(2); std::unique_ptr f = load_dat(src); if (!f) return STATUS_ERROR; if (!(f->GetFontInfo().flags & DataFile::FLAG_BW)) { std::cout << "Warning: font is not black and white" << std::endl; } { std::ofstream source(dst); mcufont::bwfont::write_source(source, dst, *f); std::cout << "Wrote " << dst << std::endl; } return STATUS_OK; } static const char *usage_msg = "Usage: mcufont [options] ...\n" "Commands for importing:\n" " import_ttf [bw] Import a .ttf font into a data file.\n" " import_bdf Import a .bdf font into a data file.\n" "\n" "Commands for inspecting and editing data files:\n" " filter ... Remove everything except specified characters.\n" " show_glyph Show the glyph at index.\n" "\n" "Commands specific to rlefont format:\n" " rlefont_size Check the encoded size of the data file.\n" " rlefont_optimize Perform an optimization pass on the data file.\n" " rlefont_export [outfile] Export to .c source code.\n" " rlefont_show_encoded Show the encoded data for debugging.\n" "\n" "Commands specific to bwfont format:\n" " bwfont_export [outfile Export to .c source code.\n" ""; typedef status_t (*cmd_t)(const std::vector &args); static const std::map command_list { {"import_ttf", cmd_import_ttf}, {"import_bdf", cmd_import_bdf}, {"filter", cmd_filter}, {"show_glyph", cmd_show_glyph}, {"rlefont_size", cmd_rlefont_size}, {"rlefont_optimize", cmd_rlefont_optimize}, {"rlefont_export", cmd_rlefont_export}, {"rlefont_show_encoded", cmd_rlefont_show_encoded}, {"bwfont_export", cmd_bwfont_export}, }; int main(int argc, char **argv) { std::vector args; for (int i = 1; i < argc; i++) args.push_back(argv[i]); status_t status = STATUS_INVALID; if (args.size() >= 1 && command_list.count(args.at(0))) { status = command_list.find(args.at(0))->second(args); } if (status == STATUS_INVALID) { std::cout << usage_msg << std::endl; } return status; }