version 2.8
main.cc
1 #include "datafile.hh"
2 #include "importtools.hh"
3 #include "bdf_import.hh"
4 #include "freetype_import.hh"
5 #include "export_rlefont.hh"
6 #include "encode_rlefont.hh"
7 #include "optimize_rlefont.hh"
8 #include "export_bwfont.hh"
9 #include <vector>
10 #include <string>
11 #include <set>
12 #include <fstream>
13 #include <iostream>
14 #include <iomanip>
15 #include <cstdlib>
16 #include <ctime>
17 #include <map>
18 #include "ccfixes.hh"
19 
20 using namespace mcufont;
21 
22 static std::string strip_extension(std::string filename)
23 {
24  size_t pos = filename.find_last_of('.');
25 
26  if (pos == std::string::npos)
27  {
28  return filename;
29  }
30  else
31  {
32  return filename.substr(0, pos);
33  }
34 }
35 
36 static std::unique_ptr<DataFile> load_dat(std::string src)
37 {
38  std::ifstream infile(src);
39 
40  if (!infile.good())
41  {
42  std::cerr << "Could not open " << src << std::endl;
43  return nullptr;
44  }
45 
46  std::unique_ptr<DataFile> f = DataFile::Load(infile);
47  if (!f)
48  {
49  std::cerr << "Invalid format for .dat file: " << src << std::endl;
50  return nullptr;
51  }
52 
53  return f;
54 }
55 
56 static bool save_dat(std::string dest, DataFile *f)
57 {
58  std::ofstream outfile(dest);
59 
60  if (!outfile.good())
61  {
62  std::cerr << "Could not open " << dest << std::endl;
63  return false;
64  }
65 
66  f->Save(outfile);
67 
68  if (!outfile.good())
69  {
70  std::cerr << "Could not write to " << dest << std::endl;
71  return false;
72  }
73 
74  return true;
75 }
76 
77 enum status_t
78 {
79  STATUS_OK = 0, // All good
80  STATUS_INVALID = 1, // Invalid command or args
81  STATUS_ERROR = 2 // Error when executing command
82 };
83 
84 static status_t cmd_import_ttf(const std::vector<std::string> &args)
85 {
86  if (args.size() != 3 && args.size() != 4)
87  return STATUS_INVALID;
88 
89  std::string src = args.at(1);
90  int size = std::stoi(args.at(2));
91  bool bw = (args.size() == 4 && args.at(3) == "bw");
92  std::string dest = strip_extension(src) + std::to_string(size) + (bw ? "bw" : "") + ".dat";
93  std::ifstream infile(src, std::ios::binary);
94 
95  if (!infile.good())
96  {
97  std::cerr << "Could not open " << src << std::endl;
98  return STATUS_ERROR;
99  }
100 
101  std::cout << "Importing " << src << " to " << dest << std::endl;
102 
103  std::unique_ptr<DataFile> f = LoadFreetype(infile, size, bw);
104 
105  mcufont::rlefont::init_dictionary(*f);
106 
107  if (!save_dat(dest, f.get()))
108  return STATUS_ERROR;
109 
110  std::cout << "Done: " << f->GetGlyphCount() << " unique glyphs." << std::endl;
111  return STATUS_OK;
112 }
113 
114 static status_t cmd_import_bdf(const std::vector<std::string> &args)
115 {
116  if (args.size() != 2)
117  return STATUS_INVALID;
118 
119  std::string src = args.at(1);
120  std::string dest = strip_extension(args.at(1)) + ".dat";
121  std::ifstream infile(src, std::ios::binary);
122 
123  if (!infile.good())
124  {
125  std::cerr << "Could not open " << src << std::endl;
126  return STATUS_ERROR;
127  }
128 
129  std::cout << "Importing " << src << " to " << dest << std::endl;
130 
131  std::unique_ptr<DataFile> f = LoadBDF(infile);
132 
133  mcufont::rlefont::init_dictionary(*f);
134 
135  if (!save_dat(dest, f.get()))
136  return STATUS_ERROR;
137 
138  std::cout << "Done: " << f->GetGlyphCount() << " unique glyphs." << std::endl;
139  return STATUS_OK;
140 }
141 
142 static status_t cmd_filter(const std::vector<std::string> &args)
143 {
144  if (args.size() < 3)
145  return STATUS_INVALID;
146 
147  std::set<int> allowed;
148 
149  // Parse arguments
150  for (size_t i = 2; i < args.size(); i++)
151  {
152  std::string s = args.at(i);
153  size_t pos = s.find('-');
154  if (pos == std::string::npos)
155  {
156  // Single char
157  allowed.insert(std::stoi(s, nullptr, 0));
158  }
159  else
160  {
161  // Range
162  int start = std::stoi(s.substr(0, pos), nullptr, 0);
163  int end = std::stoi(s.substr(pos + 1), nullptr, 0);
164 
165  for (int j = start; j <= end; j++)
166  {
167  allowed.insert(j);
168  }
169  }
170  }
171 
172  std::string src = args.at(1);
173  std::unique_ptr<DataFile> f = load_dat(src);
174  if (!f)
175  return STATUS_ERROR;
176 
177  std::cout << "Font originally had " << f->GetGlyphCount() << " glyphs." << std::endl;
178 
179  // Filter the glyphs
180  std::vector<DataFile::glyphentry_t> newglyphs;
181  for (size_t i = 0; i < f->GetGlyphCount(); i++)
182  {
183  DataFile::glyphentry_t g = f->GetGlyphEntry(i);
184 
185  for (size_t j = 0; j < g.chars.size(); j++)
186  {
187  if (!allowed.count(g.chars.at(j)))
188  {
189  g.chars.erase(g.chars.begin() + j);
190  j--;
191  }
192  }
193 
194  if (g.chars.size())
195  {
196  newglyphs.push_back(g);
197  }
198  }
199 
200  DataFile::fontinfo_t fontinfo = f->GetFontInfo();
201  crop_glyphs(newglyphs, fontinfo);
202  detect_flags(newglyphs, fontinfo);
203 
204  f.reset(new DataFile(f->GetDictionary(), newglyphs, fontinfo));
205  std::cout << "After filtering, " << f->GetGlyphCount() << " glyphs remain." << std::endl;
206 
207  if (!save_dat(src, f.get()))
208  return STATUS_ERROR;
209 
210  return STATUS_OK;
211 }
212 
213 static status_t cmd_show_glyph(const std::vector<std::string> &args)
214 {
215  if (args.size() != 3)
216  return STATUS_INVALID;
217 
218  std::string src = args.at(1);
219  std::unique_ptr<DataFile> f = load_dat(src);
220 
221  if (!f)
222  return STATUS_ERROR;
223 
224  size_t index = 0;
225  if (args.at(2) == "largest")
226  {
227  std::unique_ptr<mcufont::rlefont::encoded_font_t> e =
228  mcufont::rlefont::encode_font(*f, false);
229  size_t maxlen = 0;
230  size_t i = 0;
231  for (mcufont::rlefont::encoded_font_t::refstring_t g : e->glyphs)
232  {
233  if (g.size() > maxlen)
234  {
235  maxlen = g.size();
236  index = i;
237  }
238  i++;
239  }
240 
241  std::cout << "Index " << index << ", length " << maxlen << std::endl;
242  }
243  else
244  {
245  index = strtol(args.at(2).c_str(), nullptr, 0);
246  }
247 
248  if (index < 0 || index >= f->GetGlyphCount())
249  {
250  std::cerr << "No such glyph " << index << std::endl;
251  return STATUS_ERROR;
252  }
253 
254  std::cout << "Width: " << f->GetGlyphEntry(index).width << std::endl;
255  std::cout << "Chars: ";
256  for (int c: f->GetGlyphEntry(index).chars) std::cout << c << " ";
257  std::cout << std::endl;
258 
259  std::cout << f->GlyphToText(index);
260  return STATUS_OK;
261 }
262 
263 static status_t cmd_rlefont_export(const std::vector<std::string> &args)
264 {
265  if (args.size() != 2 && args.size() != 3)
266  return STATUS_INVALID;
267 
268  std::string src = args.at(1);
269  std::string dst = (args.size() == 2) ? strip_extension(src) + ".c" : args.at(2);
270  std::unique_ptr<DataFile> f = load_dat(src);
271 
272  if (!f)
273  return STATUS_ERROR;
274 
275  {
276  std::ofstream source(dst);
277  mcufont::rlefont::write_source(source, dst, *f);
278  std::cout << "Wrote " << dst << std::endl;
279  }
280 
281  return STATUS_OK;
282 }
283 
284 static status_t cmd_rlefont_size(const std::vector<std::string> &args)
285 {
286  if (args.size() != 2)
287  return STATUS_INVALID;
288 
289  std::string src = args.at(1);
290  std::unique_ptr<DataFile> f = load_dat(src);
291 
292  if (!f)
293  return STATUS_ERROR;
294 
295  size_t size = mcufont::rlefont::get_encoded_size(*f);
296 
297  std::cout << "Glyph count: " << f->GetGlyphCount() << std::endl;
298  std::cout << "Glyph bbox: " << f->GetFontInfo().max_width << "x"
299  << f->GetFontInfo().max_height << " pixels" << std::endl;
300  std::cout << "Uncompressed size: " << f->GetGlyphCount() *
301  f->GetFontInfo().max_width * f->GetFontInfo().max_height / 2
302  << " bytes" << std::endl;
303  std::cout << "Compressed size: " << size << " bytes" << std::endl;
304  std::cout << "Bytes per glyph: " << size / f->GetGlyphCount() << std::endl;
305  return STATUS_OK;
306 }
307 
308 static status_t cmd_rlefont_optimize(const std::vector<std::string> &args)
309 {
310  if (args.size() != 2 && args.size() != 3)
311  return STATUS_INVALID;
312 
313  std::string src = args.at(1);
314  std::unique_ptr<DataFile> f = load_dat(src);
315 
316  if (!f)
317  return STATUS_ERROR;
318 
319  size_t oldsize = mcufont::rlefont::get_encoded_size(*f);
320 
321  std::cout << "Original size is " << oldsize << " bytes" << std::endl;
322  std::cout << "Press ctrl-C at any time to stop." << std::endl;
323  std::cout << "Results are saved automatically after each iteration." << std::endl;
324 
325  int limit = 100;
326  if (args.size() == 3)
327  {
328  limit = std::stoi(args.at(2));
329  }
330 
331  if (limit > 0)
332  std::cout << "Limit is " << limit << " iterations" << std::endl;
333 
334  int i = 0;
335  time_t oldtime = time(NULL);
336  while (!limit || i < limit)
337  {
338  mcufont::rlefont::optimize(*f);
339 
340  size_t newsize = mcufont::rlefont::get_encoded_size(*f);
341  time_t newtime = time(NULL);
342 
343  int bytes_per_min = (oldsize - newsize) * 60 / (newtime - oldtime + 1);
344 
345  i++;
346  std::cout << "iteration " << i << ", size " << newsize
347  << " bytes, speed " << bytes_per_min << " B/min"
348  << std::endl;
349 
350  {
351  if (!save_dat(src, f.get()))
352  return STATUS_ERROR;
353  }
354  }
355 
356  return STATUS_OK;
357 }
358 
359 static status_t cmd_rlefont_show_encoded(const std::vector<std::string> &args)
360 {
361  if (args.size() != 2)
362  return STATUS_INVALID;
363 
364  std::string src = args.at(1);
365  std::unique_ptr<DataFile> f = load_dat(src);
366 
367  if (!f)
368  return STATUS_ERROR;
369 
370  std::unique_ptr<mcufont::rlefont::encoded_font_t> e =
371  mcufont::rlefont::encode_font(*f, false);
372 
373  int i = 0;
374  for (mcufont::rlefont::encoded_font_t::rlestring_t d : e->rle_dictionary)
375  {
376  std::cout << "Dict RLE " << 24 + i++ << ": ";
377  for (uint8_t v : d)
378  std::cout << std::setfill('0') << std::setw(2) << std::hex << (int)v << " ";
379  std::cout << std::endl;
380  }
381 
382  for (mcufont::rlefont::encoded_font_t::refstring_t d : e->ref_dictionary)
383  {
384  std::cout << "Dict Ref " << 24 + i++ << ": ";
385  for (uint8_t v : d)
386  std::cout << std::setfill('0') << std::setw(2) << std::hex << (int)v << " ";
387  std::cout << std::endl;
388  }
389 
390  i = 0;
391  for (mcufont::rlefont::encoded_font_t::refstring_t g : e->glyphs)
392  {
393  std::cout << "Glyph " << i++ << ": ";
394  for (uint8_t v : g)
395  std::cout << std::setfill('0') << std::setw(2) << std::hex << (int)v << " ";
396  std::cout << std::endl;
397  }
398 
399  return STATUS_OK;
400 }
401 
402 static status_t cmd_bwfont_export(const std::vector<std::string> &args)
403 {
404  if (args.size() != 2 && args.size() != 3)
405  return STATUS_INVALID;
406 
407  std::string src = args.at(1);
408  std::string dst = (args.size() == 2) ? strip_extension(src) + ".c" : args.at(2);
409  std::unique_ptr<DataFile> f = load_dat(src);
410 
411  if (!f)
412  return STATUS_ERROR;
413 
414  if (!(f->GetFontInfo().flags & DataFile::FLAG_BW))
415  {
416  std::cout << "Warning: font is not black and white" << std::endl;
417  }
418 
419  {
420  std::ofstream source(dst);
421  mcufont::bwfont::write_source(source, dst, *f);
422  std::cout << "Wrote " << dst << std::endl;
423  }
424 
425  return STATUS_OK;
426 }
427 
428 
429 static const char *usage_msg =
430  "Usage: mcufont <command> [options] ...\n"
431  "Commands for importing:\n"
432  " import_ttf <ttffile> <size> [bw] Import a .ttf font into a data file.\n"
433  " import_bdf <bdffile> Import a .bdf font into a data file.\n"
434  "\n"
435  "Commands for inspecting and editing data files:\n"
436  " filter <datfile> <range> ... Remove everything except specified characters.\n"
437  " show_glyph <datfile> <index> Show the glyph at index.\n"
438  "\n"
439  "Commands specific to rlefont format:\n"
440  " rlefont_size <datfile> Check the encoded size of the data file.\n"
441  " rlefont_optimize <datfile> Perform an optimization pass on the data file.\n"
442  " rlefont_export <datfile> [outfile] Export to .c source code.\n"
443  " rlefont_show_encoded <datfile> Show the encoded data for debugging.\n"
444  "\n"
445  "Commands specific to bwfont format:\n"
446  " bwfont_export <datfile> [outfile Export to .c source code.\n"
447  "";
448 
449 typedef status_t (*cmd_t)(const std::vector<std::string> &args);
450 static const std::map<std::string, cmd_t> command_list {
451  {"import_ttf", cmd_import_ttf},
452  {"import_bdf", cmd_import_bdf},
453  {"filter", cmd_filter},
454  {"show_glyph", cmd_show_glyph},
455  {"rlefont_size", cmd_rlefont_size},
456  {"rlefont_optimize", cmd_rlefont_optimize},
457  {"rlefont_export", cmd_rlefont_export},
458  {"rlefont_show_encoded", cmd_rlefont_show_encoded},
459  {"bwfont_export", cmd_bwfont_export},
460 };
461 
462 int main(int argc, char **argv)
463 {
464  std::vector<std::string> args;
465  for (int i = 1; i < argc; i++)
466  args.push_back(argv[i]);
467 
468  status_t status = STATUS_INVALID;
469  if (args.size() >= 1 && command_list.count(args.at(0)))
470  {
471  status = command_list.find(args.at(0))->second(args);
472  }
473 
474  if (status == STATUS_INVALID)
475  {
476  std::cout << usage_msg << std::endl;
477  }
478 
479  return status;
480 }