main.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. /*
  2. * sfnconv/main.c
  3. *
  4. * Copyright (C) 2020 bzt (bztsrc@gitlab)
  5. *
  6. * Permission is hereby granted, free of charge, to any person
  7. * obtaining a copy of this software and associated documentation
  8. * files (the "Software"), to deal in the Software without
  9. * restriction, including without limitation the rights to use, copy,
  10. * modify, merge, publish, distribute, sublicense, and/or sell copies
  11. * of the Software, and to permit persons to whom the Software is
  12. * furnished to do so, subject to the following conditions:
  13. *
  14. * The above copyright notice and this permission notice shall be
  15. * included in all copies or substantial portions of the Software.
  16. *
  17. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  18. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  19. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  20. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  21. * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  22. * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  24. * DEALINGS IN THE SOFTWARE.
  25. *
  26. * @brief SSFN converter main function
  27. *
  28. */
  29. #include <stdlib.h>
  30. #include <string.h>
  31. #include <stdio.h>
  32. #include "libsfn.h"
  33. #include "zlib.h"
  34. int zip = 1, ascii = 0, lines = 0, dump = 0, quiet = 0, lastpercent = 100;
  35. char *_ssfn_zlib_decode(const char *buffer);
  36. /**
  37. * Load a (compressed) file
  38. */
  39. ssfn_font_t *load_file(char *infile, int *size)
  40. {
  41. ssfn_font_t *data = NULL;
  42. long int origsize = 0, fsize;
  43. FILE *f;
  44. #ifndef SSFN_MAXLINES
  45. uint8_t c, r, *ptr;
  46. #endif
  47. *size = 0;
  48. f = fopen(infile,"rb");
  49. if(!f) {
  50. badfile: fprintf(stderr,"sfnconv: unable to load '%s'\n", infile); exit(3);
  51. }
  52. fseek(f, -4L, SEEK_END);
  53. if(!fread(&origsize, 4, 1, f)) { fclose(f); return NULL; }
  54. fsize = ftell(f);
  55. fseek(f, 0, SEEK_SET);
  56. data = (ssfn_font_t*)malloc(fsize+1);
  57. if(!data) { fprintf(stderr,"sfnconv: memory allocation error\n"); exit(2); }
  58. if(!fread(data, fsize, 1, f)) { free(data); fclose(f); return NULL; }
  59. ((uint8_t*)data)[fsize] = 0;
  60. fclose(f);
  61. if(((uint8_t *)data)[0] == 0x1f && ((uint8_t *)data)[1] == 0x8b) {
  62. #ifdef SSFN_MAXLINES
  63. goto badfile;
  64. #else
  65. ptr = (uint8_t*)data + 2;
  66. data = (ssfn_font_t*)malloc(origsize);
  67. if(!data) { fprintf(stderr,"sfnconv: memory allocation error\n"); exit(2); }
  68. if(*ptr++ != 8) goto badfile;
  69. c = *ptr++; ptr += 6;
  70. if(c & 4) { r = *ptr++; r += (*ptr++ << 8); ptr += r; }
  71. if(c & 8) { while(*ptr++ != 0); }
  72. if(c & 16) { while(*ptr++ != 0); }
  73. data = (ssfn_font_t*)_ssfn_zlib_decode((const char*)ptr);
  74. if(!data) goto badfile;
  75. *size = origsize;
  76. #endif
  77. } else
  78. *size = fsize;
  79. return data;
  80. }
  81. /**
  82. * Save a (compressed) file
  83. */
  84. void save_file(char *outfile, ssfn_font_t *font)
  85. {
  86. FILE *f;
  87. uint32_t crc;
  88. z_stream stream;
  89. unsigned char *buf = (unsigned char*)font;
  90. unsigned long int size = font->size;
  91. if(zip) {
  92. stream.avail_out = compressBound(font->size) + 16;
  93. buf = malloc(stream.avail_out);
  94. if(!buf) { fprintf(stderr,"sfnconv: memory allocation error\n"); return; }
  95. stream.zalloc = (alloc_func)0;
  96. stream.zfree = (free_func)0;
  97. stream.opaque = (voidpf)0;
  98. if(deflateInit(&stream, 9) != Z_OK) { fprintf(stderr,"sfnconv: deflate error\n"); return; }
  99. stream.next_out = (z_const Bytef *)buf + 8;
  100. stream.avail_in = font->size;
  101. stream.next_in = (z_const Bytef *)font;
  102. crc = crc32(0, stream.next_in, stream.avail_in);
  103. deflate(&stream, Z_FINISH);
  104. memset(buf, 0, 10);
  105. buf[0] = 0x1f; buf[1] = 0x8b; buf[2] = 0x8; buf[9] = 3;
  106. memcpy(buf + 8 + stream.total_out, &crc, 4);
  107. memcpy(buf + 12 + stream.total_out, &font->size, 4);
  108. size = stream.total_out + 16;
  109. }
  110. f = fopen(outfile, "wb");
  111. if(!f) { fprintf(stderr, "sfnconv: unable to write '%s'\n", outfile); exit(4); }
  112. fwrite(buf, size, 1, f);
  113. fclose(f);
  114. if(buf != (unsigned char*)font) free(buf);
  115. }
  116. /**
  117. * Usage instructions
  118. */
  119. void usage()
  120. {
  121. printf("Scalable Screen Font 2.0 by bzt Copyright (C) 2020 MIT license\n"
  122. " https://gitlab.com/bztsrc/scalable-font2\n"
  123. " UNICODE database: %s\n\n"
  124. "./sfnconv [-c|-e|-d|-dd|-dd...|-D] [-C] [-U] [-A] [-R] [-B <size>|-V] [-L] [-g] [-T]\n"
  125. " [-b <p>] [-u <p>] [-a <p>] [-M <n>] [-o] [-q] [-S <U+xxx>] [-E] [-t [b][i]<0..4>]\n"
  126. " [-X <px>] [-Y <py>]"
  127. , uniname_date);
  128. printf("\n [-n <name>] [-f <family>] [-s <subfamily>] [-v <ver>] [-m <manufacturer>] "
  129. "\n [-l <license>] [[-r <from> <to>] [-F <samplefile>] <in> ...]] <out>\n\n"
  130. " -c: create font collection\n"
  131. " -e: extract font collection\n"
  132. " -d: dump font (-d = header, -dd = string table, -ddd = fragments etc.)\n"
  133. " -D: dump all tables in the font\n"
  134. " -C: UNICODE range coverage report\n"
  135. " -U: save uncompressed, non-gzipped output\n"
  136. " -A: output SSFN ASCII\n"
  137. " -R: replace characters from new files\n");
  138. printf(" -B: rasterize vector fonts to bitmaps\n"
  139. " -V: vectorize bitmap fonts to scalable fonts\n"
  140. " -L: convert curves to lines in contours\n"
  141. " -g: save grid information for hinting\n"
  142. " -T: recalculate bitmap advances\n"
  143. " -b: horizontal baseline in pixels (1-255)\n"
  144. " -u: underline position in pixels (relative to baseline)\n"
  145. " -a: add a constant to advance (1-255, some fonts need it)\n"
  146. " -M: monospacing, round advances up to multiple of n\n"
  147. " -o: use original width and height instead of calculated one\n");
  148. printf(" -p: convert to proportional font\n"
  149. " -q: quiet, don't report font errors\n"
  150. " -S: skip a UNICODE code point, this flag can be repeated\n"
  151. " -E: don't care about rounding errors\n"
  152. " -X: set pixel width for image import\n"
  153. " -Y: set pixel height for image import\n"
  154. " -t: set type b=bold,i=italic,u,U,0=Serif,1/s=Sans,2/d=Decor,3/m=Mono,4/h=Hand\n");
  155. printf(" -n: set font unique name\n"
  156. " -f: set font family (like FreeSerif, Vera, Helvetica)\n"
  157. " -s: set subfamily (like Regular, Medium, Bold, Oblique, Thin, etc.)\n"
  158. " -v: set font version / revision (like creation date for example)\n"
  159. " -m: set manufacturer (creator, designer, foundry)\n"
  160. " -l: set license (like MIT, GPL or URL to the license)\n"
  161. " -r: code point range, this flag can be repeated before each input\n"
  162. " -F: code point range from sample file, this flag can be repeated\n");
  163. printf(" in: input font(s) SSFN"
  164. #ifdef USE_NOFOREIGN
  165. " and ASC"
  166. #else
  167. ",ASC"
  168. #if HAS_FT
  169. ",TTF,OTF,WOFF,PST1,PST42"
  170. #endif
  171. ",PSF2,PCF,BDF,SFD,HEX,BMF,YAFF,KBITS,FON,FNT,Fxx,TGA,PNG,"
  172. #endif
  173. "*\n"
  174. " out: output SSFN/ASC filename**\n\n"
  175. "* - input files can be gzip compressed"
  176. #ifndef USE_NOFOREIGN
  177. ", like .psfu.gz, .bdf.gz or .hex.gz"
  178. #endif
  179. "\n** - output file will be gzip compressed by default (use -U to avoid)\n\n"
  180. );
  181. exit(1);
  182. }
  183. /**
  184. * Progressbar hook
  185. */
  186. void progressbar(int step, int numstep, int curr, int total, int msg)
  187. {
  188. int i, n;
  189. char *str[] = { "", "Measuring BBox", "Querying outlines", "Querying all kerning combinations", "Quantizing image",
  190. "Reading file", "Reading bitmap", "Reading square pixel map", "Reading tall pixel map", "Reading wide pixel map",
  191. "Generating fragments", "Compressing fragments", "Serializing fragments", "Writing character map", "Writing file",
  192. "Rasterizing", "Vectorizing", "Converting to lines" };
  193. n = (long int)(curr + 1) * 100L / (long int)(total + 1);
  194. if(n == lastpercent) return;
  195. lastpercent = n;
  196. printf("\r\x1b[K");
  197. if(numstep) printf("%d / %d ", step, numstep);
  198. printf("[");
  199. for(i = 0; i < 20; i++)
  200. printf(i < n/5 ? "#" : " ");
  201. printf("] %3d%% %s ", n, str[msg]);
  202. fflush(stdout);
  203. }
  204. /**
  205. * Main procedure
  206. */
  207. int main(int argc, char **argv)
  208. {
  209. int i, j, in = 0;
  210. char *outfile = NULL, *c;
  211. int size = 0, total = 8;
  212. ssfn_font_t *font, *end;
  213. unsigned char *out = NULL;
  214. /* parse flags and arguments */
  215. if(argc<3) usage();
  216. /* collection management */
  217. if(argv[1][0] == '-' && argv[1][1] == 'c') {
  218. /* create collection */
  219. if(argc<5) usage();
  220. i = 2;
  221. for(; i<argc && argv[i][0] == '-'; i++)
  222. if(argv[i][0] == '-' && argv[i][1] == 'U') zip = 0;
  223. for(; i + 1 < argc; i++) {
  224. font = load_file(argv[i], &size);
  225. if(memcmp(font->magic, SSFN_MAGIC, 4)) {
  226. fprintf(stderr, "sfnconv: not an SSFN font '%s'\n", argv[i]);
  227. return 1;
  228. }
  229. out = (unsigned char *)realloc(out, total+size);
  230. memcpy(out + total, font, font->size);
  231. total += size;
  232. size = 0;
  233. free(font);
  234. }
  235. if(out) {
  236. memcpy(out, SSFN_COLLECTION, 4);
  237. memcpy(out + 4, &total, 4);
  238. save_file(argv[i], (ssfn_font_t*)out);
  239. }
  240. return 0;
  241. }
  242. if(argv[1][0] == '-' && argv[1][1] == 'e') {
  243. /* extract collection */
  244. i = 2; j = 0;
  245. for(; i<argc && argv[i][0] == '-'; i++)
  246. if(argv[i][0] == '-' && argv[i][1] == 'U') zip = 0;
  247. font = load_file(argv[i], &size);
  248. if(memcmp(font->magic, SSFN_COLLECTION, 4)) {
  249. fprintf(stderr, "sfnconv: not an SSFN font collection '%s'\n", argv[i]);
  250. return 1;
  251. }
  252. end = (ssfn_font_t*)((uint8_t*)font + font->size);
  253. for(i++, font = (ssfn_font_t*)((uint8_t*)font + 8); font < end; font = (ssfn_font_t*)((uint8_t*)font + font->size)) {
  254. if(argc < 4) {
  255. if(!j) { j = 1; printf("-t\t-B\t-n\n"); }
  256. printf("%s%s%s%s%d\t%d\t%s\n", SSFN_TYPE_STYLE(font->type) & SSFN_STYLE_BOLD ? "b":"",
  257. SSFN_TYPE_STYLE(font->type) & SSFN_STYLE_ITALIC ? "i":"",
  258. SSFN_TYPE_STYLE(font->type) & SSFN_STYLE_USRDEF1 ? "u":"",
  259. SSFN_TYPE_STYLE(font->type) & SSFN_STYLE_USRDEF2 ? "U":"",
  260. SSFN_TYPE_FAMILY(font->type), font->height,
  261. (char*)font + sizeof(ssfn_font_t));
  262. } else
  263. save_file(argv[i++], font);
  264. }
  265. return 0;
  266. }
  267. /* convert fonts */
  268. sfn_init(progressbar);
  269. for(i=1;argv[i];i++){
  270. if(argv[i][0] == '-') {
  271. switch(argv[i][1]) {
  272. case 'n': if(++i>=argc) usage(); sfn_setstr(&ctx.name, argv[i], 0); continue;
  273. case 'f': if(++i>=argc) usage(); sfn_setstr(&ctx.familyname, argv[i], 0); continue;
  274. case 's': if(++i>=argc) usage(); sfn_setstr(&ctx.subname, argv[i], 0); continue;
  275. case 'v': if(++i>=argc) usage(); sfn_setstr(&ctx.revision, argv[i], 0); continue;
  276. case 'm': if(++i>=argc) usage(); sfn_setstr(&ctx.manufacturer, argv[i], 0); continue;
  277. case 'l': if(++i>=argc) usage(); sfn_setstr(&ctx.license, argv[i], 0); continue;
  278. case 'b': if(++i>=argc) usage(); ctx.baseline = atoi(argv[i]); continue;
  279. case 'M': if(++i>=argc) usage(); monosize = atoi(argv[i]); continue;
  280. case 'a': if(++i>=argc) usage(); adv = atoi(argv[i]); continue;
  281. case 'u': if(++i>=argc) usage(); relul = atoi(argv[i]); continue;
  282. case 'B': if(++i>=argc) usage(); rasterize = atoi(argv[i]); continue;
  283. case 'X': if(++i>=argc) usage(); px = atoi(argv[i]); continue;
  284. case 'Y': if(++i>=argc) usage(); py = atoi(argv[i]); continue;
  285. case 'S':
  286. if(++i>=argc) usage();
  287. if(!strcmp(argv[i], "undef")) skipundef = 1; else
  288. if(!strcmp(argv[i], "code")) skipcode = 1; else
  289. if(argv[i][0] == '!') sfn_skipdel(getnum(argv[i] + 1)); else
  290. sfn_skipadd(getnum(argv[i]));
  291. continue;
  292. case 't':
  293. if(++i>=argc) usage();
  294. for(j = 0, c = argv[i]; *c; c++) {
  295. switch(*c) {
  296. case 'b': ctx.style |= SSFN_STYLE_BOLD; break;
  297. case 'i': ctx.style |= SSFN_STYLE_ITALIC; break;
  298. case 'u': ctx.style |= SSFN_STYLE_USRDEF1; break;
  299. case 'U': ctx.style |= SSFN_STYLE_USRDEF2; break;
  300. case '1': case 's': j = 1; break;
  301. case '2': case 'd': j = 2; break;
  302. case '3': case 'm': j = 3; break;
  303. case '4': case 'h': j = 4; break;
  304. }
  305. }
  306. sfn_setfamilytype(j);
  307. continue;
  308. case 'r':
  309. if(++i>=argc) usage();
  310. if((argv[i][0] >= '0' && argv[i][0] <= '9') || (argv[i][0]=='U' && argv[i][1]=='+') || argv[i][0]=='\'') {
  311. if(i+1>=argc) usage();
  312. rs = getnum(argv[i++]); re = getnum(argv[i]);
  313. } else {
  314. for(rs=re=j=0;j<UNICODE_NUMBLOCKS;j++)
  315. if(!unicmp(argv[i], ublocks[j].name)) {
  316. rs = ublocks[j].start; re = ublocks[j].end;
  317. break;
  318. }
  319. if(!re) {
  320. fprintf(stderr, "sfnconv: unable to get range '%s', did you mean:\n", argv[i]);
  321. for(j=0;j<UNICODE_NUMBLOCKS;j++)
  322. if(tolowercase(argv[i][0]) == tolowercase(ublocks[j].name[0]) &&
  323. tolowercase(argv[i][1]) == tolowercase(ublocks[j].name[1])) {
  324. fprintf(stderr, " %s\n", ublocks[j].name);
  325. re++;
  326. }
  327. if(!re)
  328. for(j=0;j<UNICODE_NUMBLOCKS;j++)
  329. if(tolowercase(argv[i][0]) == tolowercase(ublocks[j].name[0])) {
  330. fprintf(stderr, " %s\n", ublocks[j].name);
  331. re++;
  332. }
  333. if(!re)
  334. fprintf(stderr, "no matching blocks found\n");
  335. return 1;
  336. }
  337. }
  338. if(rs > 0x10FFFF || re > 0x10FFFF || rs > re) {
  339. fprintf(stderr, "sfnconv: unable to get range '%s' '%s'\n", argv[i], argv[i]+1);
  340. return 1;
  341. }
  342. continue;
  343. case 'F':
  344. if(++i>=argc) usage();
  345. sfn_rangesample(argv[i]);
  346. continue;
  347. default:
  348. for(j=1;argv[i][j];j++) {
  349. switch(argv[i][j]) {
  350. case 'g': hinting = 1; break;
  351. case 'U': zip = 0; break;
  352. case 'V': rasterize = -1; break;
  353. case 'L': lines = 1; break;
  354. case 'R': replace = 1; break;
  355. case 'A': ascii = 1; break;
  356. case 'o': origwh = 1; break;
  357. case 'q': quiet = 1; break;
  358. case 'E': dorounderr = 1; break;
  359. case 'd': dump++; break;
  360. case 'D': dump = 99; break;
  361. case 'C': dump = -1; break;
  362. case 'T': advrecalc = 1; break;
  363. case 'p': propo = 1; break;
  364. default: fprintf(stderr, "sfnconv: unknown flag '%c'\n", argv[i][j]); return 1;
  365. }
  366. }
  367. break;
  368. }
  369. } else {
  370. if(dump) sfn_load(argv[i], dump);
  371. else {
  372. if(argv[i+1]) {
  373. if(!argv[i]) usage();
  374. if(sfn_load(argv[i], dump))
  375. printf("\r\x1b[K");
  376. else {
  377. if(!quiet) fprintf(stderr, "sfnconv: unable to open '%s'\n", argv[i]);
  378. return 1;
  379. }
  380. rs = 0; re = 0x10FFFF; in++;
  381. } else outfile = argv[i];
  382. }
  383. }
  384. }
  385. /* save output font */
  386. if(!dump) {
  387. if(!in || !outfile) usage();
  388. sfn_sanitize(-1);
  389. if(rasterize) {
  390. if(rasterize == -1) {
  391. sfn_vectorize();
  392. } else {
  393. sfn_rasterize(rasterize);
  394. }
  395. }
  396. if(lines) sfn_lines();
  397. printf("\r\x1b[KSaving '%s' (%s%s%s)\n", outfile, ascii ? "ASCII" : "bin",
  398. zip ? ", compress" : "", hinting ? ", hinting" : "");
  399. i = sfn_save(outfile, ascii, zip);
  400. if(!i)
  401. printf("\r\x1b[KError saving!\n\n");
  402. else {
  403. printf("\r\x1b[KDone.");
  404. if(ctx.total > 0 && i > 1)
  405. printf(" Compressed to %ld.%ld%% (%ld bytes)", (long int)i*100/ctx.total, ((long int)i*10000/ctx.total)%100,
  406. i - ctx.total);
  407. printf("\n\n");
  408. }
  409. } else
  410. if(dump == -1) {
  411. printf("\r\x1b[K\n");
  412. sfn_coverage();
  413. }
  414. /* free resources */
  415. sfn_free();
  416. uniname_free();
  417. return 0;
  418. }