main.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763
  1. /*
  2. * MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.1 (2016-05-08) - standalone console program
  3. * --------------------------------------------------------------------------------------------
  4. * A utility by Viktor Chlumsky, (c) 2014 - 2016
  5. *
  6. */
  7. #ifdef MSDFGEN_STANDALONE
  8. #define _USE_MATH_DEFINES
  9. #include <cstdio>
  10. #include <cmath>
  11. #include <cstring>
  12. #include "msdfgen.h"
  13. #include "msdfgen-ext.h"
  14. #ifdef _WIN32
  15. #pragma warning(disable:4996)
  16. #endif
  17. #define LARGE_VALUE 1e240
  18. using namespace msdfgen;
  19. enum Format {
  20. AUTO,
  21. PNG,
  22. BMP,
  23. TEXT,
  24. TEXT_FLOAT,
  25. BINARY,
  26. BINARY_FLOAT,
  27. BINART_FLOAT_BE
  28. };
  29. static char toupper(char c) {
  30. return c >= 'a' && c <= 'z' ? c-'a'+'A' : c;
  31. }
  32. static bool parseUnsigned(unsigned &value, const char *arg) {
  33. static char c;
  34. return sscanf(arg, "%u%c", &value, &c) == 1;
  35. }
  36. static bool parseUnsignedHex(unsigned &value, const char *arg) {
  37. static char c;
  38. return sscanf(arg, "%x%c", &value, &c) == 1;
  39. }
  40. static bool parseDouble(double &value, const char *arg) {
  41. static char c;
  42. return sscanf(arg, "%lf%c", &value, &c) == 1;
  43. }
  44. static bool parseUnicode(int &unicode, const char *arg) {
  45. unsigned uuc;
  46. if (parseUnsigned(uuc, arg)) {
  47. unicode = uuc;
  48. return true;
  49. }
  50. if (arg[0] == '0' && (arg[1] == 'x' || arg[1] == 'X') && parseUnsignedHex(uuc, arg+2)) {
  51. unicode = uuc;
  52. return true;
  53. }
  54. if (arg[0] == '\'' && arg[1] && arg[2] == '\'' && !arg[3]) {
  55. unicode = arg[1];
  56. return true;
  57. }
  58. return false;
  59. }
  60. static bool parseAngle(double &value, const char *arg) {
  61. char c1, c2;
  62. int result = sscanf(arg, "%lf%c%c", &value, &c1, &c2);
  63. if (result == 1)
  64. return true;
  65. if (result == 2 && (c1 == 'd' || c1 == 'D')) {
  66. value = M_PI*value/180;
  67. return true;
  68. }
  69. return false;
  70. }
  71. static void parseColoring(Shape &shape, const char *edgeAssignment) {
  72. unsigned c = 0, e = 0;
  73. if (shape.contours.size() < c) return;
  74. Contour *contour = &shape.contours[c];
  75. bool change = false;
  76. bool clear = true;
  77. for (const char *in = edgeAssignment; *in; ++in) {
  78. switch (*in) {
  79. case ',':
  80. if (change)
  81. ++e;
  82. if (clear)
  83. while (e < contour->edges.size()) {
  84. contour->edges[e]->color = WHITE;
  85. ++e;
  86. }
  87. ++c, e = 0;
  88. if (shape.contours.size() <= c) return;
  89. contour = &shape.contours[c];
  90. change = false;
  91. clear = true;
  92. break;
  93. case '?':
  94. clear = false;
  95. break;
  96. case 'C': case 'M': case 'W': case 'Y': case 'c': case 'm': case 'w': case 'y':
  97. if (change) {
  98. ++e;
  99. change = false;
  100. }
  101. if (e < contour->edges.size()) {
  102. contour->edges[e]->color = EdgeColor(
  103. (*in == 'C' || *in == 'c')*CYAN|
  104. (*in == 'M' || *in == 'm')*MAGENTA|
  105. (*in == 'Y' || *in == 'y')*YELLOW|
  106. (*in == 'W' || *in == 'w')*WHITE);
  107. change = true;
  108. }
  109. break;
  110. }
  111. }
  112. }
  113. static bool writeTextBitmap(FILE *file, const float *values, int cols, int rows) {
  114. for (int row = 0; row < rows; ++row) {
  115. for (int col = 0; col < cols; ++col) {
  116. int v = clamp(int((*values++)*0x100), 0xff);
  117. fprintf(file, col ? " %02X" : "%02X", v);
  118. }
  119. fprintf(file, "\n");
  120. }
  121. return true;
  122. }
  123. static bool writeTextBitmapFloat(FILE *file, const float *values, int cols, int rows) {
  124. for (int row = 0; row < rows; ++row) {
  125. for (int col = 0; col < cols; ++col) {
  126. fprintf(file, col ? " %g" : "%g", *values++);
  127. }
  128. fprintf(file, "\n");
  129. }
  130. return true;
  131. }
  132. static bool writeBinBitmap(FILE *file, const float *values, int count) {
  133. for (int pos = 0; pos < count; ++pos) {
  134. unsigned char v = clamp(int((*values++)*0x100), 0xff);
  135. fwrite(&v, 1, 1, file);
  136. }
  137. return true;
  138. }
  139. #ifdef __BIG_ENDIAN__
  140. static bool writeBinBitmapFloatBE(FILE *file, const float *values, int count)
  141. #else
  142. static bool writeBinBitmapFloat(FILE *file, const float *values, int count)
  143. #endif
  144. {
  145. return fwrite(values, sizeof(float), count, file) == count;
  146. }
  147. #ifdef __BIG_ENDIAN__
  148. static bool writeBinBitmapFloat(FILE *file, const float *values, int count)
  149. #else
  150. static bool writeBinBitmapFloatBE(FILE *file, const float *values, int count)
  151. #endif
  152. {
  153. for (int pos = 0; pos < count; ++pos) {
  154. const unsigned char *b = reinterpret_cast<const unsigned char *>(values++);
  155. for (int i = sizeof(float)-1; i >= 0; --i)
  156. fwrite(b+i, 1, 1, file);
  157. }
  158. return true;
  159. }
  160. static bool cmpExtension(const char *path, const char *ext) {
  161. for (const char *a = path+strlen(path)-1, *b = ext+strlen(ext)-1; b >= ext; --a, --b)
  162. if (a < path || toupper(*a) != toupper(*b))
  163. return false;
  164. return true;
  165. }
  166. template <typename T>
  167. static const char * writeOutput(const Bitmap<T> &bitmap, const char *filename, Format format) {
  168. if (filename) {
  169. if (format == AUTO) {
  170. if (cmpExtension(filename, ".png")) format = PNG;
  171. else if (cmpExtension(filename, ".bmp")) format = BMP;
  172. else if (cmpExtension(filename, ".txt")) format = TEXT;
  173. else if (cmpExtension(filename, ".bin")) format = BINARY;
  174. else
  175. return "Could not deduce format from output file name.";
  176. }
  177. switch (format) {
  178. case PNG: return savePng(bitmap, filename) ? NULL : "Failed to write output PNG image.";
  179. case BMP: return saveBmp(bitmap, filename) ? NULL : "Failed to write output BMP image.";
  180. case TEXT: case TEXT_FLOAT: {
  181. FILE *file = fopen(filename, "w");
  182. if (!file) return "Failed to write output text file.";
  183. if (format == TEXT)
  184. writeTextBitmap(file, reinterpret_cast<const float *>(&bitmap(0, 0)), sizeof(T)/sizeof(float)*bitmap.width(), bitmap.height());
  185. else if (format == TEXT_FLOAT)
  186. writeTextBitmapFloat(file, reinterpret_cast<const float *>(&bitmap(0, 0)), sizeof(T)/sizeof(float)*bitmap.width(), bitmap.height());
  187. fclose(file);
  188. return NULL;
  189. }
  190. case BINARY: case BINARY_FLOAT: case BINART_FLOAT_BE: {
  191. FILE *file = fopen(filename, "wb");
  192. if (!file) return "Failed to write output binary file.";
  193. if (format == BINARY)
  194. writeBinBitmap(file, reinterpret_cast<const float *>(&bitmap(0, 0)), sizeof(T)/sizeof(float)*bitmap.width()*bitmap.height());
  195. else if (format == BINARY_FLOAT)
  196. writeBinBitmapFloat(file, reinterpret_cast<const float *>(&bitmap(0, 0)), sizeof(T)/sizeof(float)*bitmap.width()*bitmap.height());
  197. else if (format == BINART_FLOAT_BE)
  198. writeBinBitmapFloatBE(file, reinterpret_cast<const float *>(&bitmap(0, 0)), sizeof(T)/sizeof(float)*bitmap.width()*bitmap.height());
  199. fclose(file);
  200. return NULL;
  201. }
  202. default:
  203. break;
  204. }
  205. } else {
  206. if (format == AUTO || format == TEXT)
  207. writeTextBitmap(stdout, reinterpret_cast<const float *>(&bitmap(0, 0)), sizeof(T)/sizeof(float)*bitmap.width(), bitmap.height());
  208. else if (format == TEXT_FLOAT)
  209. writeTextBitmapFloat(stdout, reinterpret_cast<const float *>(&bitmap(0, 0)), sizeof(T)/sizeof(float)*bitmap.width(), bitmap.height());
  210. else
  211. return "Unsupported format for standard output.";
  212. }
  213. return NULL;
  214. }
  215. static const char *helpText =
  216. "\n"
  217. "Multi-channel signed distance field generator by Viktor Chlumsky v" MSDFGEN_VERSION "\n"
  218. "---------------------------------------------------------------------\n"
  219. " Usage: msdfgen"
  220. #ifdef _WIN32
  221. ".exe"
  222. #endif
  223. " <mode> <input specification> <options>\n"
  224. "\n"
  225. "MODES\n"
  226. " sdf - Generate conventional monochrome signed distance field.\n"
  227. " psdf - Generate monochrome signed pseudo-distance field.\n"
  228. " msdf - Generate multi-channel signed distance field. This is used by default if no mode is specified.\n"
  229. " metrics - Report shape metrics only.\n"
  230. "\n"
  231. "INPUT SPECIFICATION\n"
  232. " -defineshape <definition>\n"
  233. "\tDefines input shape using the ad-hoc text definition.\n"
  234. " -font <filename.ttf> <character code>\n"
  235. "\tLoads a single glyph from the specified font file. Format of character code is '?', 63 or 0x3F.\n"
  236. " -shapedesc <filename.txt>\n"
  237. "\tLoads text shape description from a file.\n"
  238. " -stdin\n"
  239. "\tReads text shape description from the standard input.\n"
  240. " -svg <filename.svg>\n"
  241. "\tLoads the first vector path encountered in the specified SVG file.\n"
  242. "\n"
  243. "OPTIONS\n"
  244. " -angle <angle>\n"
  245. "\tSpecifies the minimum angle between adjacent edges to be considered a corner. Append D for degrees.\n"
  246. " -ascale <x scale> <y scale>\n"
  247. "\tSets the scale used to convert shape units to pixels asymmetrically.\n"
  248. " -autoframe\n"
  249. "\tAutomatically scales (unless specified) and translates the shape to fit.\n"
  250. " -edgecolors <sequence>\n"
  251. "\tOverrides automatic edge coloring with the specified color sequence.\n"
  252. " -errorcorrection <threshold>\n"
  253. "\tChanges the threshold used to detect and correct potential artifacts. 0 disables error correction.\n"
  254. " -exportshape <filename.txt>\n"
  255. "\tSaves the shape description into a text file that can be edited and loaded using -shapedesc.\n"
  256. " -format <png / bmp / text / textfloat / bin / binfloat / binfloatbe>\n"
  257. "\tSpecifies the output format of the distance field. Otherwise it is chosen based on output file extension.\n"
  258. " -help\n"
  259. "\tDisplays this help.\n"
  260. " -o <filename>\n"
  261. "\tSets the output file name. The default value is \"output.png\".\n"
  262. " -printmetrics\n"
  263. "\tPrints relevant metrics of the shape to the standard output.\n"
  264. " -pxrange <range>\n"
  265. "\tSets the width of the range between the lowest and highest signed distance in pixels.\n"
  266. " -range <range>\n"
  267. "\tSets the width of the range between the lowest and highest signed distance in shape units.\n"
  268. " -scale <scale>\n"
  269. "\tSets the scale used to convert shape units to pixels.\n"
  270. " -size <width> <height>\n"
  271. "\tSets the dimensions of the output image.\n"
  272. " -stdout\n"
  273. "\tPrints the output instead of storing it in a file. Only text formats are supported.\n"
  274. " -testrender <filename.png> <width> <height>\n"
  275. "\tRenders an image preview using the generated distance field and saves it as a PNG file.\n"
  276. " -testrendermulti <filename.png> <width> <height>\n"
  277. "\tRenders an image preview without flattening the color channels.\n"
  278. " -translate <x> <y>\n"
  279. "\tSets the translation of the shape in shape units.\n"
  280. " -yflip\n"
  281. "\tInverts the Y axis in the output distance field. The default order is bottom to top.\n"
  282. "\n";
  283. int main(int argc, const char * const *argv) {
  284. #define ABORT(msg) { puts(msg); return 0; }
  285. // Parse command line arguments
  286. enum {
  287. NONE,
  288. SVG,
  289. FONT,
  290. DESCRIPTION_ARG,
  291. DESCRIPTION_STDIN,
  292. DESCRIPTION_FILE
  293. } inputType = NONE;
  294. enum {
  295. SINGLE,
  296. PSEUDO,
  297. MULTI,
  298. METRICS
  299. } mode = MULTI;
  300. Format format = AUTO;
  301. const char *input = NULL;
  302. const char *output = "output.png";
  303. const char *shapeExport = NULL;
  304. const char *testRender = NULL;
  305. const char *testRenderMulti = NULL;
  306. bool outputSpecified = false;
  307. int unicode = 0;
  308. int width = 64, height = 64;
  309. int testWidth = 0, testHeight = 0;
  310. int testWidthM = 0, testHeightM = 0;
  311. bool autoFrame = false;
  312. enum {
  313. RANGE_UNIT,
  314. RANGE_PX
  315. } rangeMode = RANGE_PX;
  316. double range = 1;
  317. double pxRange = 2;
  318. Vector2 translate;
  319. Vector2 scale = 1;
  320. bool scaleSpecified = false;
  321. double angleThreshold = 3;
  322. double edgeThreshold = 1.00000001;
  323. bool defEdgeAssignment = true;
  324. const char *edgeAssignment = NULL;
  325. bool yFlip = false;
  326. bool printMetrics = false;
  327. bool skipColoring = false;
  328. int argPos = 1;
  329. bool suggestHelp = false;
  330. while (argPos < argc) {
  331. const char *arg = argv[argPos];
  332. #define ARG_CASE(s, p) if (!strcmp(arg, s) && argPos+(p) < argc)
  333. #define ARG_MODE(s, m) if (!strcmp(arg, s)) { mode = m; ++argPos; continue; }
  334. #define SETFORMAT(fmt, ext) do { format = fmt; if (!outputSpecified) output = "output." ext; } while (false)
  335. ARG_MODE("sdf", SINGLE)
  336. ARG_MODE("psdf", PSEUDO)
  337. ARG_MODE("msdf", MULTI)
  338. ARG_MODE("metrics", METRICS)
  339. ARG_CASE("-svg", 1) {
  340. inputType = SVG;
  341. input = argv[argPos+1];
  342. argPos += 2;
  343. continue;
  344. }
  345. ARG_CASE("-font", 2) {
  346. inputType = FONT;
  347. input = argv[argPos+1];
  348. parseUnicode(unicode, argv[argPos+2]);
  349. argPos += 3;
  350. continue;
  351. }
  352. ARG_CASE("-defineshape", 1) {
  353. inputType = DESCRIPTION_ARG;
  354. input = argv[argPos+1];
  355. argPos += 2;
  356. continue;
  357. }
  358. ARG_CASE("-stdin", 0) {
  359. inputType = DESCRIPTION_STDIN;
  360. input = "stdin";
  361. argPos += 1;
  362. continue;
  363. }
  364. ARG_CASE("-shapedesc", 1) {
  365. inputType = DESCRIPTION_FILE;
  366. input = argv[argPos+1];
  367. argPos += 2;
  368. continue;
  369. }
  370. ARG_CASE("-o", 1) {
  371. output = argv[argPos+1];
  372. outputSpecified = true;
  373. argPos += 2;
  374. continue;
  375. }
  376. ARG_CASE("-stdout", 0) {
  377. output = NULL;
  378. argPos += 1;
  379. continue;
  380. }
  381. ARG_CASE("-format", 1) {
  382. if (!strcmp(argv[argPos+1], "auto")) format = AUTO;
  383. else if (!strcmp(argv[argPos+1], "png")) SETFORMAT(PNG, "png");
  384. else if (!strcmp(argv[argPos+1], "bmp")) SETFORMAT(BMP, "bmp");
  385. else if (!strcmp(argv[argPos+1], "text") || !strcmp(argv[argPos+1], "txt")) SETFORMAT(TEXT, "txt");
  386. else if (!strcmp(argv[argPos+1], "textfloat") || !strcmp(argv[argPos+1], "txtfloat")) SETFORMAT(TEXT_FLOAT, "txt");
  387. else if (!strcmp(argv[argPos+1], "bin") || !strcmp(argv[argPos+1], "binary")) SETFORMAT(BINARY, "bin");
  388. else if (!strcmp(argv[argPos+1], "binfloat") || !strcmp(argv[argPos+1], "binfloatle")) SETFORMAT(BINARY_FLOAT, "bin");
  389. else if (!strcmp(argv[argPos+1], "binfloatbe")) SETFORMAT(BINART_FLOAT_BE, "bin");
  390. else
  391. puts("Unknown format specified.");
  392. argPos += 2;
  393. continue;
  394. }
  395. ARG_CASE("-size", 2) {
  396. unsigned w, h;
  397. if (!parseUnsigned(w, argv[argPos+1]) || !parseUnsigned(h, argv[argPos+2]) || !w || !h)
  398. ABORT("Invalid size arguments. Use -size <width> <height> with two positive integers.");
  399. width = w, height = h;
  400. argPos += 3;
  401. continue;
  402. }
  403. ARG_CASE("-autoframe", 0) {
  404. autoFrame = true;
  405. argPos += 1;
  406. continue;
  407. }
  408. ARG_CASE("-range", 1) {
  409. double r;
  410. if (!parseDouble(r, argv[argPos+1]) || r < 0)
  411. ABORT("Invalid range argument. Use -range <range> with a positive real number.");
  412. rangeMode = RANGE_UNIT;
  413. range = r;
  414. argPos += 2;
  415. continue;
  416. }
  417. ARG_CASE("-pxrange", 1) {
  418. double r;
  419. if (!parseDouble(r, argv[argPos+1]) || r < 0)
  420. ABORT("Invalid range argument. Use -pxrange <range> with a positive real number.");
  421. rangeMode = RANGE_PX;
  422. pxRange = r;
  423. argPos += 2;
  424. continue;
  425. }
  426. ARG_CASE("-scale", 1) {
  427. double s;
  428. if (!parseDouble(s, argv[argPos+1]) || s <= 0)
  429. ABORT("Invalid scale argument. Use -scale <scale> with a positive real number.");
  430. scale = s;
  431. scaleSpecified = true;
  432. argPos += 2;
  433. continue;
  434. }
  435. ARG_CASE("-ascale", 2) {
  436. double sx, sy;
  437. if (!parseDouble(sx, argv[argPos+1]) || !parseDouble(sy, argv[argPos+2]) || sx <= 0 || sy <= 0)
  438. ABORT("Invalid scale arguments. Use -ascale <x> <y> with two positive real numbers.");
  439. scale.set(sx, sy);
  440. scaleSpecified = true;
  441. argPos += 3;
  442. continue;
  443. }
  444. ARG_CASE("-translate", 2) {
  445. double tx, ty;
  446. if (!parseDouble(tx, argv[argPos+1]) || !parseDouble(ty, argv[argPos+2]))
  447. ABORT("Invalid translate arguments. Use -translate <x> <y> with two real numbers.");
  448. translate.set(tx, ty);
  449. argPos += 3;
  450. continue;
  451. }
  452. ARG_CASE("-angle", 1) {
  453. double at;
  454. if (!parseAngle(at, argv[argPos+1]))
  455. ABORT("Invalid angle threshold. Use -angle <min angle> with a positive real number less than PI or a value in degrees followed by 'd' below 180d.");
  456. angleThreshold = at;
  457. argPos += 2;
  458. continue;
  459. }
  460. ARG_CASE("-errorcorrection", 1) {
  461. double et;
  462. if (!parseDouble(et, argv[argPos+1]) || et < 0)
  463. ABORT("Invalid error correction threshold. Use -errorcorrection <threshold> with a real number larger or equal to 1.");
  464. edgeThreshold = et;
  465. argPos += 2;
  466. continue;
  467. }
  468. ARG_CASE("-edgecolors", 1) {
  469. static const char *allowed = " ?,cmyCMY";
  470. for (int i = 0; argv[argPos+1][i]; ++i) {
  471. for (int j = 0; allowed[j]; ++j)
  472. if (argv[argPos+1][i] == allowed[j])
  473. goto ROLL_ARG;
  474. ABORT("Invalid edge coloring sequence. Use -assign <color sequence> with only the colors C, M, and Y. Separate contours by commas and use ? to keep the default assigment for a contour.");
  475. ROLL_ARG:;
  476. }
  477. edgeAssignment = argv[argPos+1];
  478. argPos += 2;
  479. continue;
  480. }
  481. ARG_CASE("-exportshape", 1) {
  482. shapeExport = argv[argPos+1];
  483. argPos += 2;
  484. continue;
  485. }
  486. ARG_CASE("-testrender", 3) {
  487. unsigned w, h;
  488. if (!parseUnsigned(w, argv[argPos+2]) || !parseUnsigned(h, argv[argPos+3]) || !w || !h)
  489. ABORT("Invalid arguments for test render. Use -testrender <output.png> <width> <height>.");
  490. testRender = argv[argPos+1];
  491. testWidth = w, testHeight = h;
  492. argPos += 4;
  493. continue;
  494. }
  495. ARG_CASE("-testrendermulti", 3) {
  496. unsigned w, h;
  497. if (!parseUnsigned(w, argv[argPos+2]) || !parseUnsigned(h, argv[argPos+3]) || !w || !h)
  498. ABORT("Invalid arguments for test render. Use -testrendermulti <output.png> <width> <height>.");
  499. testRenderMulti = argv[argPos+1];
  500. testWidthM = w, testHeightM = h;
  501. argPos += 4;
  502. continue;
  503. }
  504. ARG_CASE("-yflip", 0) {
  505. yFlip = true;
  506. argPos += 1;
  507. continue;
  508. }
  509. ARG_CASE("-printmetrics", 0) {
  510. printMetrics = true;
  511. argPos += 1;
  512. continue;
  513. }
  514. ARG_CASE("-help", 0)
  515. ABORT(helpText);
  516. printf("Unknown setting or insufficient parameters: %s\n", arg);
  517. suggestHelp = true;
  518. ++argPos;
  519. }
  520. if (suggestHelp)
  521. printf("Use -help for more information.\n");
  522. // Load input
  523. Vector2 svgDims;
  524. double glyphAdvance = 0;
  525. if (!inputType || !input)
  526. ABORT("No input specified! Use either -svg <file.svg> or -font <file.ttf/otf> <character code>, or see -help.");
  527. Shape shape;
  528. switch (inputType) {
  529. case SVG: {
  530. if (!loadSvgShape(shape, input, &svgDims))
  531. ABORT("Failed to load shape from SVG file.");
  532. break;
  533. }
  534. case FONT: {
  535. if (!unicode)
  536. ABORT("No character specified! Use -font <file.ttf/otf> <character code>. Character code can be a number (65, 0x41), or a character in apostrophes ('A').");
  537. FreetypeHandle *ft = initializeFreetype();
  538. if (!ft) return -1;
  539. FontHandle *font = loadFont(ft, input);
  540. if (!font) {
  541. deinitializeFreetype(ft);
  542. ABORT("Failed to load font file.");
  543. }
  544. if (!loadGlyph(shape, font, unicode, &glyphAdvance)) {
  545. destroyFont(font);
  546. deinitializeFreetype(ft);
  547. ABORT("Failed to load glyph from font file.");
  548. }
  549. destroyFont(font);
  550. deinitializeFreetype(ft);
  551. break;
  552. }
  553. case DESCRIPTION_ARG: {
  554. if (!readShapeDescription(input, shape, &skipColoring))
  555. ABORT("Parse error in shape description.");
  556. break;
  557. }
  558. case DESCRIPTION_STDIN: {
  559. if (!readShapeDescription(stdin, shape, &skipColoring))
  560. ABORT("Parse error in shape description.");
  561. break;
  562. }
  563. case DESCRIPTION_FILE: {
  564. FILE *file = fopen(input, "r");
  565. if (!file)
  566. ABORT("Failed to load shape description file.");
  567. if (!readShapeDescription(file, shape, &skipColoring))
  568. ABORT("Parse error in shape description.");
  569. fclose(file);
  570. break;
  571. }
  572. default:
  573. break;
  574. }
  575. // Validate and normalize shape
  576. if (!shape.validate())
  577. ABORT("The geometry of the loaded shape is invalid.");
  578. shape.normalize();
  579. if (yFlip)
  580. shape.inverseYAxis = !shape.inverseYAxis;
  581. double avgScale = .5*(scale.x+scale.y);
  582. struct {
  583. double l, b, r, t;
  584. } bounds = {
  585. LARGE_VALUE, LARGE_VALUE, -LARGE_VALUE, -LARGE_VALUE
  586. };
  587. if (autoFrame || mode == METRICS || printMetrics)
  588. shape.bounds(bounds.l, bounds.b, bounds.r, bounds.t);
  589. // Auto-frame
  590. if (autoFrame) {
  591. double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t;
  592. Vector2 frame(width, height);
  593. if (rangeMode == RANGE_UNIT)
  594. l -= range, b -= range, r += range, t += range;
  595. else if (!scaleSpecified)
  596. frame -= 2*pxRange;
  597. if (l >= r || b >= t)
  598. l = 0, b = 0, r = 1, t = 1;
  599. if (frame.x <= 0 || frame.y <= 0)
  600. ABORT("Cannot fit the specified pixel range.");
  601. Vector2 dims(r-l, t-b);
  602. if (scaleSpecified)
  603. translate = .5*(frame/scale-dims)-Vector2(l, b);
  604. else {
  605. if (dims.x*frame.y < dims.y*frame.x) {
  606. translate.set(.5*(frame.x/frame.y*dims.y-dims.x)-l, -b);
  607. scale = avgScale = frame.y/dims.y;
  608. } else {
  609. translate.set(-l, .5*(frame.y/frame.x*dims.x-dims.y)-b);
  610. scale = avgScale = frame.x/dims.x;
  611. }
  612. }
  613. if (rangeMode == RANGE_PX && !scaleSpecified)
  614. translate += pxRange/scale;
  615. }
  616. if (rangeMode == RANGE_PX)
  617. range = pxRange/min(scale.x, scale.y);
  618. // Print metrics
  619. if (mode == METRICS || printMetrics) {
  620. FILE *out = stdout;
  621. if (mode == METRICS && outputSpecified)
  622. out = fopen(output, "w");
  623. if (!out)
  624. ABORT("Failed to write output file.");
  625. if (shape.inverseYAxis)
  626. fprintf(out, "inverseY = true\n");
  627. if (bounds.r >= bounds.l && bounds.t >= bounds.b)
  628. fprintf(out, "bounds = %.12g, %.12g, %.12g, %.12g\n", bounds.l, bounds.b, bounds.r, bounds.t);
  629. if (svgDims.x != 0 && svgDims.y != 0)
  630. fprintf(out, "dimensions = %.12g, %.12g\n", svgDims.x, svgDims.y);
  631. if (glyphAdvance != 0)
  632. fprintf(out, "advance = %.12g\n", glyphAdvance);
  633. if (autoFrame) {
  634. if (!scaleSpecified)
  635. fprintf(out, "scale = %.12g\n", avgScale);
  636. fprintf(out, "translate = %.12g, %.12g\n", translate.x, translate.y);
  637. }
  638. if (rangeMode == RANGE_PX)
  639. fprintf(out, "range = %.12g\n", range);
  640. if (mode == METRICS && outputSpecified)
  641. fclose(out);
  642. }
  643. // Compute output
  644. Bitmap<float> sdf;
  645. Bitmap<FloatRGB> msdf;
  646. switch (mode) {
  647. case SINGLE: {
  648. sdf = Bitmap<float>(width, height);
  649. generateSDF(sdf, shape, range, scale, translate);
  650. break;
  651. }
  652. case PSEUDO: {
  653. sdf = Bitmap<float>(width, height);
  654. generatePseudoSDF(sdf, shape, range, scale, translate);
  655. break;
  656. }
  657. case MULTI: {
  658. if (!skipColoring)
  659. edgeColoringSimple(shape, angleThreshold);
  660. if (edgeAssignment)
  661. parseColoring(shape, edgeAssignment);
  662. msdf = Bitmap<FloatRGB>(width, height);
  663. generateMSDF(msdf, shape, range, scale, translate, edgeThreshold);
  664. break;
  665. }
  666. default:
  667. break;
  668. }
  669. // Save output
  670. if (shapeExport) {
  671. FILE *file = fopen(shapeExport, "w");
  672. if (file) {
  673. writeShapeDescription(file, shape);
  674. fclose(file);
  675. } else
  676. puts("Failed to write shape export file.");
  677. }
  678. const char *error = NULL;
  679. switch (mode) {
  680. case SINGLE:
  681. case PSEUDO:
  682. error = writeOutput(sdf, output, format);
  683. if (error)
  684. ABORT(error);
  685. if (testRenderMulti || testRender)
  686. simulate8bit(sdf);
  687. if (testRenderMulti) {
  688. Bitmap<FloatRGB> render(testWidthM, testHeightM);
  689. renderSDF(render, sdf, avgScale*range);
  690. if (!savePng(render, testRenderMulti))
  691. puts("Failed to write test render file.");
  692. }
  693. if (testRender) {
  694. Bitmap<float> render(testWidth, testHeight);
  695. renderSDF(render, sdf, avgScale*range);
  696. if (!savePng(render, testRender))
  697. puts("Failed to write test render file.");
  698. }
  699. break;
  700. case MULTI:
  701. error = writeOutput(msdf, output, format);
  702. if (error)
  703. ABORT(error);
  704. if (testRenderMulti || testRender)
  705. simulate8bit(msdf);
  706. if (testRenderMulti) {
  707. Bitmap<FloatRGB> render(testWidthM, testHeightM);
  708. renderSDF(render, msdf, avgScale*range);
  709. if (!savePng(render, testRenderMulti))
  710. puts("Failed to write test render file.");
  711. }
  712. if (testRender) {
  713. Bitmap<float> render(testWidth, testHeight);
  714. renderSDF(render, msdf, avgScale*range);
  715. if (!savePng(render, testRender))
  716. ABORT("Failed to write test render file.");
  717. }
  718. break;
  719. default:
  720. break;
  721. }
  722. return 0;
  723. }
  724. #endif