main.cpp 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347
  1. /*
  2. * MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR - standalone console program
  3. * --------------------------------------------------------------------------
  4. * A utility by Viktor Chlumsky, (c) 2014 - 2023
  5. *
  6. */
  7. #ifdef MSDFGEN_STANDALONE
  8. #define _USE_MATH_DEFINES
  9. #define _CRT_SECURE_NO_WARNINGS
  10. #include <cstdio>
  11. #include <cmath>
  12. #include <cstring>
  13. #include <string>
  14. #include "msdfgen.h"
  15. #ifdef MSDFGEN_EXTENSIONS
  16. #include "msdfgen-ext.h"
  17. #endif
  18. #include "core/ShapeDistanceFinder.h"
  19. #define SDF_ERROR_ESTIMATE_PRECISION 19
  20. #define DEFAULT_ANGLE_THRESHOLD ::msdfgen::real(3)
  21. #if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_PNG)
  22. #define DEFAULT_IMAGE_EXTENSION "png"
  23. #define SAVE_DEFAULT_IMAGE_FORMAT savePng
  24. #else
  25. #define DEFAULT_IMAGE_EXTENSION "tif"
  26. #define SAVE_DEFAULT_IMAGE_FORMAT saveTiff
  27. #endif
  28. using namespace msdfgen;
  29. enum Format {
  30. AUTO,
  31. PNG,
  32. BMP,
  33. TIFF,
  34. TEXT,
  35. TEXT_FLOAT,
  36. BINARY,
  37. BINARY_FLOAT,
  38. BINARY_FLOAT_BE
  39. };
  40. static bool is8bitFormat(Format format) {
  41. return format == PNG || format == BMP || format == TEXT || format == BINARY;
  42. }
  43. static char toupper(char c) {
  44. return c >= 'a' && c <= 'z' ? c-'a'+'A' : c;
  45. }
  46. static bool parseUnsigned(unsigned &value, const char *arg) {
  47. char c;
  48. return sscanf(arg, "%u%c", &value, &c) == 1;
  49. }
  50. static bool parseUnsignedDecOrHex(unsigned &value, const char *arg) {
  51. if (arg[0] == '0' && (arg[1] == 'x' || arg[1] == 'X')) {
  52. char c;
  53. return sscanf(arg+2, "%x%c", &value, &c) == 1;
  54. }
  55. return parseUnsigned(value, arg);
  56. }
  57. static bool parseUnsignedLL(unsigned long long &value, const char *arg) {
  58. char c;
  59. return sscanf(arg, "%llu%c", &value, &c) == 1;
  60. }
  61. static bool parseReal(real &value, const char *arg) {
  62. char c;
  63. double dblValue;
  64. if (sscanf(arg, "%lf%c", &dblValue, &c) == 1) {
  65. value = dblValue;
  66. return true;
  67. }
  68. return false;
  69. }
  70. static bool parseAngle(real &value, const char *arg) {
  71. char c1, c2;
  72. double dblValue;
  73. int result = sscanf(arg, "%lf%c%c", &dblValue, &c1, &c2);
  74. if (result == 1) {
  75. value = real(dblValue);
  76. return true;
  77. }
  78. if (result == 2 && (c1 == 'd' || c1 == 'D')) {
  79. value = real(M_PI)/real(180)*real(dblValue);
  80. return true;
  81. }
  82. return false;
  83. }
  84. static void parseColoring(Shape &shape, const char *edgeAssignment) {
  85. unsigned c = 0, e = 0;
  86. if (shape.contours.size() < c) return;
  87. Contour *contour = &shape.contours[c];
  88. bool change = false;
  89. bool clear = true;
  90. for (const char *in = edgeAssignment; *in; ++in) {
  91. switch (*in) {
  92. case ',':
  93. if (change)
  94. ++e;
  95. if (clear)
  96. while (e < contour->edges.size()) {
  97. contour->edges[e]->color = WHITE;
  98. ++e;
  99. }
  100. ++c, e = 0;
  101. if (shape.contours.size() <= c) return;
  102. contour = &shape.contours[c];
  103. change = false;
  104. clear = true;
  105. break;
  106. case '?':
  107. clear = false;
  108. break;
  109. case 'C': case 'M': case 'W': case 'Y': case 'c': case 'm': case 'w': case 'y':
  110. if (change) {
  111. ++e;
  112. change = false;
  113. }
  114. if (e < contour->edges.size()) {
  115. contour->edges[e]->color = EdgeColor(
  116. (*in == 'C' || *in == 'c')*CYAN|
  117. (*in == 'M' || *in == 'm')*MAGENTA|
  118. (*in == 'Y' || *in == 'y')*YELLOW|
  119. (*in == 'W' || *in == 'w')*WHITE);
  120. change = true;
  121. }
  122. break;
  123. }
  124. }
  125. }
  126. #ifdef MSDFGEN_EXTENSIONS
  127. static bool parseUnicode(unicode_t &unicode, const char *arg) {
  128. unsigned uuc;
  129. if (parseUnsignedDecOrHex(uuc, arg)) {
  130. unicode = uuc;
  131. return true;
  132. }
  133. if (arg[0] == '\'' && arg[1] && arg[2] == '\'' && !arg[3]) {
  134. unicode = (unicode_t) (unsigned char) arg[1];
  135. return true;
  136. }
  137. return false;
  138. }
  139. #ifndef MSDFGEN_DISABLE_VARIABLE_FONTS
  140. static FontHandle *loadVarFont(FreetypeHandle *library, const char *filename) {
  141. std::string buffer;
  142. while (*filename && *filename != '?')
  143. buffer.push_back(*filename++);
  144. FontHandle *font = loadFont(library, buffer.c_str());
  145. if (font && *filename++ == '?') {
  146. do {
  147. buffer.clear();
  148. while (*filename && *filename != '=')
  149. buffer.push_back(*filename++);
  150. if (*filename == '=') {
  151. double value = 0;
  152. int skip = 0;
  153. if (sscanf(++filename, "%lf%n", &value, &skip) == 1) {
  154. setFontVariationAxis(library, font, buffer.c_str(), real(value));
  155. filename += skip;
  156. }
  157. }
  158. } while (*filename++ == '&');
  159. }
  160. return font;
  161. }
  162. #endif
  163. #endif
  164. template <int N>
  165. static void invertColor(const BitmapRef<float, N> &bitmap) {
  166. const float *end = bitmap.pixels+N*bitmap.width*bitmap.height;
  167. for (float *p = bitmap.pixels; p < end; ++p)
  168. *p = 1.f-*p;
  169. }
  170. static bool writeTextBitmap(FILE *file, const float *values, int cols, int rows) {
  171. for (int row = 0; row < rows; ++row) {
  172. for (int col = 0; col < cols; ++col) {
  173. int v = clamp(int((*values++)*0x100), 0xff);
  174. fprintf(file, col ? " %02X" : "%02X", v);
  175. }
  176. fprintf(file, "\n");
  177. }
  178. return true;
  179. }
  180. static bool writeTextBitmapFloat(FILE *file, const float *values, int cols, int rows) {
  181. for (int row = 0; row < rows; ++row) {
  182. for (int col = 0; col < cols; ++col) {
  183. fprintf(file, col ? " %.9g" : "%.9g", *values++);
  184. }
  185. fprintf(file, "\n");
  186. }
  187. return true;
  188. }
  189. static bool writeBinBitmap(FILE *file, const float *values, int count) {
  190. for (int pos = 0; pos < count; ++pos) {
  191. unsigned char v = clamp(int((*values++)*0x100), 0xff);
  192. fwrite(&v, 1, 1, file);
  193. }
  194. return true;
  195. }
  196. #ifdef __BIG_ENDIAN__
  197. static bool writeBinBitmapFloatBE(FILE *file, const float *values, int count)
  198. #else
  199. static bool writeBinBitmapFloat(FILE *file, const float *values, int count)
  200. #endif
  201. {
  202. return (int) fwrite(values, sizeof(float), count, file) == count;
  203. }
  204. #ifdef __BIG_ENDIAN__
  205. static bool writeBinBitmapFloat(FILE *file, const float *values, int count)
  206. #else
  207. static bool writeBinBitmapFloatBE(FILE *file, const float *values, int count)
  208. #endif
  209. {
  210. for (int pos = 0; pos < count; ++pos) {
  211. const unsigned char *b = reinterpret_cast<const unsigned char *>(values++);
  212. for (int i = sizeof(float)-1; i >= 0; --i)
  213. fwrite(b+i, 1, 1, file);
  214. }
  215. return true;
  216. }
  217. static bool cmpExtension(const char *path, const char *ext) {
  218. for (const char *a = path+strlen(path)-1, *b = ext+strlen(ext)-1; b >= ext; --a, --b)
  219. if (a < path || toupper(*a) != toupper(*b))
  220. return false;
  221. return true;
  222. }
  223. template <int N>
  224. static const char *writeOutput(const BitmapConstRef<float, N> &bitmap, const char *filename, Format &format) {
  225. if (filename) {
  226. if (format == AUTO) {
  227. #if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_PNG)
  228. if (cmpExtension(filename, ".png")) format = PNG;
  229. #else
  230. if (cmpExtension(filename, ".png"))
  231. return "PNG format is not available in core-only version.";
  232. #endif
  233. else if (cmpExtension(filename, ".bmp")) format = BMP;
  234. else if (cmpExtension(filename, ".tif") || cmpExtension(filename, ".tiff")) format = TIFF;
  235. else if (cmpExtension(filename, ".txt")) format = TEXT;
  236. else if (cmpExtension(filename, ".bin")) format = BINARY;
  237. else
  238. return "Could not deduce format from output file name.";
  239. }
  240. switch (format) {
  241. #if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_PNG)
  242. case PNG: return savePng(bitmap, filename) ? NULL : "Failed to write output PNG image.";
  243. #endif
  244. case BMP: return saveBmp(bitmap, filename) ? NULL : "Failed to write output BMP image.";
  245. case TIFF: return saveTiff(bitmap, filename) ? NULL : "Failed to write output TIFF image.";
  246. case TEXT: case TEXT_FLOAT: {
  247. FILE *file = fopen(filename, "w");
  248. if (!file) return "Failed to write output text file.";
  249. if (format == TEXT)
  250. writeTextBitmap(file, bitmap.pixels, N*bitmap.width, bitmap.height);
  251. else if (format == TEXT_FLOAT)
  252. writeTextBitmapFloat(file, bitmap.pixels, N*bitmap.width, bitmap.height);
  253. fclose(file);
  254. return NULL;
  255. }
  256. case BINARY: case BINARY_FLOAT: case BINARY_FLOAT_BE: {
  257. FILE *file = fopen(filename, "wb");
  258. if (!file) return "Failed to write output binary file.";
  259. if (format == BINARY)
  260. writeBinBitmap(file, bitmap.pixels, N*bitmap.width*bitmap.height);
  261. else if (format == BINARY_FLOAT)
  262. writeBinBitmapFloat(file, bitmap.pixels, N*bitmap.width*bitmap.height);
  263. else if (format == BINARY_FLOAT_BE)
  264. writeBinBitmapFloatBE(file, bitmap.pixels, N*bitmap.width*bitmap.height);
  265. fclose(file);
  266. return NULL;
  267. }
  268. default:;
  269. }
  270. } else {
  271. if (format == AUTO || format == TEXT)
  272. writeTextBitmap(stdout, bitmap.pixels, N*bitmap.width, bitmap.height);
  273. else if (format == TEXT_FLOAT)
  274. writeTextBitmapFloat(stdout, bitmap.pixels, N*bitmap.width, bitmap.height);
  275. else
  276. return "Unsupported format for standard output.";
  277. }
  278. return NULL;
  279. }
  280. #define STRINGIZE_(x) #x
  281. #define STRINGIZE(x) STRINGIZE_(x)
  282. #define MSDFGEN_VERSION_STRING STRINGIZE(MSDFGEN_VERSION)
  283. #ifdef MSDFGEN_VERSION_UNDERLINE
  284. #define VERSION_UNDERLINE STRINGIZE(MSDFGEN_VERSION_UNDERLINE)
  285. #else
  286. #define VERSION_UNDERLINE "--------"
  287. #endif
  288. #if defined(MSDFGEN_EXTENSIONS) && (defined(MSDFGEN_DISABLE_SVG) || defined(MSDFGEN_DISABLE_PNG) || defined(MSDFGEN_DISABLE_VARIABLE_FONTS))
  289. #define TITLE_SUFFIX " - custom config"
  290. #define SUFFIX_UNDERLINE "----------------"
  291. #elif !defined(MSDFGEN_EXTENSIONS) && defined(MSDFGEN_USE_OPENMP)
  292. #define TITLE_SUFFIX " - core with OpenMP"
  293. #define SUFFIX_UNDERLINE "-------------------"
  294. #elif !defined(MSDFGEN_EXTENSIONS)
  295. #define TITLE_SUFFIX " - core only"
  296. #define SUFFIX_UNDERLINE "------------"
  297. #elif defined(MSDFGEN_USE_SKIA) && defined(MSDFGEN_USE_OPENMP)
  298. #define TITLE_SUFFIX " with Skia & OpenMP"
  299. #define SUFFIX_UNDERLINE "-------------------"
  300. #elif defined(MSDFGEN_USE_SKIA)
  301. #define TITLE_SUFFIX " with Skia"
  302. #define SUFFIX_UNDERLINE "----------"
  303. #elif defined(MSDFGEN_USE_OPENMP)
  304. #define TITLE_SUFFIX " with OpenMP"
  305. #define SUFFIX_UNDERLINE "------------"
  306. #else
  307. #define TITLE_SUFFIX
  308. #define SUFFIX_UNDERLINE
  309. #endif
  310. static const char *const versionText =
  311. "MSDFgen v" MSDFGEN_VERSION_STRING TITLE_SUFFIX "\n"
  312. "(c) 2016 - " STRINGIZE(MSDFGEN_COPYRIGHT_YEAR) " Viktor Chlumsky";
  313. static const char *const helpText =
  314. "\n"
  315. "Multi-channel signed distance field generator by Viktor Chlumsky v" MSDFGEN_VERSION_STRING TITLE_SUFFIX "\n"
  316. "------------------------------------------------------------------" VERSION_UNDERLINE SUFFIX_UNDERLINE "\n"
  317. " Usage: msdfgen"
  318. #ifdef _WIN32
  319. ".exe"
  320. #endif
  321. " <mode> <input specification> <options>\n"
  322. "\n"
  323. "MODES\n"
  324. " sdf - Generate conventional monochrome (true) signed distance field.\n"
  325. " psdf - Generate monochrome signed pseudo-distance field.\n"
  326. " msdf - Generate multi-channel signed distance field. This is used by default if no mode is specified.\n"
  327. " mtsdf - Generate combined multi-channel and true signed distance field in the alpha channel.\n"
  328. " metrics - Report shape metrics only.\n"
  329. "\n"
  330. "INPUT SPECIFICATION\n"
  331. " -defineshape <definition>\n"
  332. "\tDefines input shape using the ad-hoc text definition.\n"
  333. #ifdef MSDFGEN_EXTENSIONS
  334. " -font <filename.ttf> <character code>\n"
  335. "\tLoads a single glyph from the specified font file.\n"
  336. "\tFormat of character code is '?', 63, 0x3F (Unicode value), or g34 (glyph index).\n"
  337. #endif
  338. " -shapedesc <filename.txt>\n"
  339. "\tLoads text shape description from a file.\n"
  340. " -stdin\n"
  341. "\tReads text shape description from the standard input.\n"
  342. #if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_SVG)
  343. " -svg <filename.svg>\n"
  344. "\tLoads the last vector path found in the specified SVG file.\n"
  345. #endif
  346. #if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_VARIABLE_FONTS)
  347. " -varfont <filename and variables> <character code>\n"
  348. "\tLoads a single glyph from a variable font. Specify variable values as x.ttf?var1=0.5&var2=1\n"
  349. #endif
  350. "\n"
  351. // Keep alphabetical order!
  352. "OPTIONS\n"
  353. " -angle <angle>\n"
  354. "\tSpecifies the minimum angle between adjacent edges to be considered a corner. Append D for degrees.\n"
  355. " -ascale <x scale> <y scale>\n"
  356. "\tSets the scale used to convert shape units to pixels asymmetrically.\n"
  357. " -autoframe\n"
  358. "\tAutomatically scales (unless specified) and translates the shape to fit.\n"
  359. " -coloringstrategy <simple / inktrap / distance>\n"
  360. "\tSelects the strategy of the edge coloring heuristic.\n"
  361. " -distanceshift <shift>\n"
  362. "\tShifts all normalized distances in the output distance field by this value.\n"
  363. " -edgecolors <sequence>\n"
  364. "\tOverrides automatic edge coloring with the specified color sequence.\n"
  365. " -errorcorrection <mode>\n"
  366. "\tChanges the MSDF/MTSDF error correction mode. Use -errorcorrection help for a list of valid modes.\n"
  367. " -errordeviationratio <ratio>\n"
  368. "\tSets the minimum ratio between the actual and maximum expected distance delta to be considered an error.\n"
  369. " -errorimproveratio <ratio>\n"
  370. "\tSets the minimum ratio between the pre-correction distance error and the post-correction distance error.\n"
  371. " -estimateerror\n"
  372. "\tComputes and prints the distance field's estimated fill error to the standard output.\n"
  373. " -exportshape <filename.txt>\n"
  374. "\tSaves the shape description into a text file that can be edited and loaded using -shapedesc.\n"
  375. " -fillrule <nonzero / evenodd / positive / negative>\n"
  376. "\tSets the fill rule for the scanline pass. Default is nonzero.\n"
  377. #if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_PNG)
  378. " -format <png / bmp / tiff / text / textfloat / bin / binfloat / binfloatbe>\n"
  379. #else
  380. " -format <bmp / tiff / text / textfloat / bin / binfloat / binfloatbe>\n"
  381. #endif
  382. "\tSpecifies the output format of the distance field. Otherwise it is chosen based on output file extension.\n"
  383. " -guessorder\n"
  384. "\tAttempts to detect if shape contours have the wrong winding and generates the SDF with the right one.\n"
  385. " -help\n"
  386. "\tDisplays this help.\n"
  387. " -legacy\n"
  388. "\tUses the original (legacy) distance field algorithms.\n"
  389. #ifdef MSDFGEN_USE_SKIA
  390. " -nopreprocess\n"
  391. "\tDisables path preprocessing which resolves self-intersections and overlapping contours.\n"
  392. #else
  393. " -nooverlap\n"
  394. "\tDisables resolution of overlapping contours.\n"
  395. " -noscanline\n"
  396. "\tDisables the scanline pass, which corrects the distance field's signs according to the selected fill rule.\n"
  397. #endif
  398. " -o <filename>\n"
  399. "\tSets the output file name. The default value is \"output." DEFAULT_IMAGE_EXTENSION "\".\n"
  400. #ifdef MSDFGEN_USE_SKIA
  401. " -overlap\n"
  402. "\tSwitches to distance field generator with support for overlapping contours.\n"
  403. #endif
  404. " -printmetrics\n"
  405. "\tPrints relevant metrics of the shape to the standard output.\n"
  406. " -pxrange <range>\n"
  407. "\tSets the width of the range between the lowest and highest signed distance in pixels.\n"
  408. " -range <range>\n"
  409. "\tSets the width of the range between the lowest and highest signed distance in shape units.\n"
  410. " -reverseorder\n"
  411. "\tGenerates the distance field as if the shape's vertices were in reverse order.\n"
  412. " -scale <scale>\n"
  413. "\tSets the scale used to convert shape units to pixels.\n"
  414. #ifdef MSDFGEN_USE_SKIA
  415. " -scanline\n"
  416. "\tPerforms an additional scanline pass to fix the signs of the distances.\n"
  417. #endif
  418. " -seed <n>\n"
  419. "\tSets the random seed for edge coloring heuristic.\n"
  420. " -size <width> <height>\n"
  421. "\tSets the dimensions of the output image.\n"
  422. " -stdout\n"
  423. "\tPrints the output instead of storing it in a file. Only text formats are supported.\n"
  424. " -testrender <filename." DEFAULT_IMAGE_EXTENSION "> <width> <height>\n"
  425. #if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_PNG)
  426. "\tRenders an image preview using the generated distance field and saves it as a PNG file.\n"
  427. #else
  428. "\tRenders an image preview using the generated distance field and saves it as a TIFF file.\n"
  429. #endif
  430. " -testrendermulti <filename." DEFAULT_IMAGE_EXTENSION "> <width> <height>\n"
  431. "\tRenders an image preview without flattening the color channels.\n"
  432. " -translate <x> <y>\n"
  433. "\tSets the translation of the shape in shape units.\n"
  434. " -version\n"
  435. "\tPrints the version of the program.\n"
  436. " -windingpreprocess\n"
  437. "\tAttempts to fix only the contour windings assuming no self-intersections and even-odd fill rule.\n"
  438. " -yflip\n"
  439. "\tInverts the Y axis in the output distance field. The default order is bottom to top.\n"
  440. "\n";
  441. static const char *errorCorrectionHelpText =
  442. "\n"
  443. "ERROR CORRECTION MODES\n"
  444. " auto-fast\n"
  445. "\tDetects inversion artifacts and distance errors that do not affect edges by range testing.\n"
  446. " auto-full\n"
  447. "\tDetects inversion artifacts and distance errors that do not affect edges by exact distance evaluation.\n"
  448. " auto-mixed (default)\n"
  449. "\tDetects inversions by distance evaluation and distance errors that do not affect edges by range testing.\n"
  450. " disabled\n"
  451. "\tDisables error correction.\n"
  452. " distance-fast\n"
  453. "\tDetects distance errors by range testing. Does not care if edges and corners are affected.\n"
  454. " distance-full\n"
  455. "\tDetects distance errors by exact distance evaluation. Does not care if edges and corners are affected, slow.\n"
  456. " edge-fast\n"
  457. "\tDetects inversion artifacts only by range testing.\n"
  458. " edge-full\n"
  459. "\tDetects inversion artifacts only by exact distance evaluation.\n"
  460. " help\n"
  461. "\tDisplays this help.\n"
  462. "\n";
  463. int main(int argc, const char *const *argv) {
  464. #define ABORT(msg) do { fputs(msg "\n", stderr); return 1; } while (false)
  465. // Parse command line arguments
  466. enum {
  467. NONE,
  468. SVG,
  469. FONT,
  470. VAR_FONT,
  471. DESCRIPTION_ARG,
  472. DESCRIPTION_STDIN,
  473. DESCRIPTION_FILE
  474. } inputType = NONE;
  475. enum {
  476. SINGLE,
  477. PSEUDO,
  478. MULTI,
  479. MULTI_AND_TRUE,
  480. METRICS
  481. } mode = MULTI;
  482. enum {
  483. NO_PREPROCESS,
  484. WINDING_PREPROCESS,
  485. FULL_PREPROCESS
  486. } geometryPreproc = (
  487. #ifdef MSDFGEN_USE_SKIA
  488. FULL_PREPROCESS
  489. #else
  490. NO_PREPROCESS
  491. #endif
  492. );
  493. bool legacyMode = false;
  494. MSDFGeneratorConfig generatorConfig;
  495. generatorConfig.overlapSupport = geometryPreproc == NO_PREPROCESS;
  496. bool scanlinePass = geometryPreproc == NO_PREPROCESS;
  497. FillRule fillRule = FILL_NONZERO;
  498. Format format = AUTO;
  499. const char *input = NULL;
  500. const char *output = "output." DEFAULT_IMAGE_EXTENSION;
  501. const char *shapeExport = NULL;
  502. const char *testRender = NULL;
  503. const char *testRenderMulti = NULL;
  504. bool outputSpecified = false;
  505. #ifdef MSDFGEN_EXTENSIONS
  506. bool glyphIndexSpecified = false;
  507. GlyphIndex glyphIndex;
  508. unicode_t unicode = 0;
  509. #endif
  510. int width = 64, height = 64;
  511. int testWidth = 0, testHeight = 0;
  512. int testWidthM = 0, testHeightM = 0;
  513. bool autoFrame = false;
  514. enum {
  515. RANGE_UNIT,
  516. RANGE_PX
  517. } rangeMode = RANGE_PX;
  518. real range = 1;
  519. real pxRange = 2;
  520. Vector2 translate;
  521. Vector2 scale = 1;
  522. bool scaleSpecified = false;
  523. real angleThreshold = DEFAULT_ANGLE_THRESHOLD;
  524. float outputDistanceShift = 0.f;
  525. const char *edgeAssignment = NULL;
  526. bool yFlip = false;
  527. bool printMetrics = false;
  528. bool estimateError = false;
  529. bool skipColoring = false;
  530. enum {
  531. KEEP,
  532. REVERSE,
  533. GUESS
  534. } orientation = KEEP;
  535. unsigned long long coloringSeed = 0;
  536. void (*edgeColoring)(Shape &, real, unsigned long long) = edgeColoringSimple;
  537. bool explicitErrorCorrectionMode = false;
  538. int argPos = 1;
  539. bool suggestHelp = false;
  540. while (argPos < argc) {
  541. const char *arg = argv[argPos];
  542. #define ARG_CASE(s, p) if (!strcmp(arg, s) && argPos+(p) < argc)
  543. #define ARG_MODE(s, m) if (!strcmp(arg, s)) { mode = m; ++argPos; continue; }
  544. #define SET_FORMAT(fmt, ext) do { format = fmt; if (!outputSpecified) output = "output." ext; } while (false)
  545. // Accept arguments prefixed with -- instead of -
  546. if (arg[0] == '-' && arg[1] == '-')
  547. ++arg;
  548. ARG_MODE("sdf", SINGLE)
  549. ARG_MODE("psdf", PSEUDO)
  550. ARG_MODE("msdf", MULTI)
  551. ARG_MODE("mtsdf", MULTI_AND_TRUE)
  552. ARG_MODE("metrics", METRICS)
  553. #if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_SVG)
  554. ARG_CASE("-svg", 1) {
  555. inputType = SVG;
  556. input = argv[argPos+1];
  557. argPos += 2;
  558. continue;
  559. }
  560. #endif
  561. #ifdef MSDFGEN_EXTENSIONS
  562. //ARG_CASE -font, -varfont
  563. if (argPos+2 < argc && (
  564. (!strcmp(arg, "-font") && (inputType = FONT, true))
  565. #ifndef MSDFGEN_DISABLE_VARIABLE_FONTS
  566. || (!strcmp(arg, "-varfont") && (inputType = VAR_FONT, true))
  567. #endif
  568. )) {
  569. input = argv[argPos+1];
  570. const char *charArg = argv[argPos+2];
  571. unsigned gi;
  572. switch (charArg[0]) {
  573. case 'G': case 'g':
  574. if (parseUnsignedDecOrHex(gi, charArg+1)) {
  575. glyphIndex = GlyphIndex(gi);
  576. glyphIndexSpecified = true;
  577. }
  578. break;
  579. case 'U': case 'u':
  580. ++charArg;
  581. // fallthrough
  582. default:
  583. parseUnicode(unicode, charArg);
  584. }
  585. argPos += 3;
  586. continue;
  587. }
  588. #else
  589. ARG_CASE("-svg", 1) {
  590. ABORT("SVG input is not available in core-only version.");
  591. }
  592. ARG_CASE("-font", 2) {
  593. ABORT("Font input is not available in core-only version.");
  594. }
  595. ARG_CASE("-varfont", 2) {
  596. ABORT("Variable font input is not available in core-only version.");
  597. }
  598. #endif
  599. ARG_CASE("-defineshape", 1) {
  600. inputType = DESCRIPTION_ARG;
  601. input = argv[argPos+1];
  602. argPos += 2;
  603. continue;
  604. }
  605. ARG_CASE("-stdin", 0) {
  606. inputType = DESCRIPTION_STDIN;
  607. input = "stdin";
  608. argPos += 1;
  609. continue;
  610. }
  611. ARG_CASE("-shapedesc", 1) {
  612. inputType = DESCRIPTION_FILE;
  613. input = argv[argPos+1];
  614. argPos += 2;
  615. continue;
  616. }
  617. ARG_CASE("-o", 1) {
  618. output = argv[argPos+1];
  619. outputSpecified = true;
  620. argPos += 2;
  621. continue;
  622. }
  623. ARG_CASE("-stdout", 0) {
  624. output = NULL;
  625. argPos += 1;
  626. continue;
  627. }
  628. ARG_CASE("-legacy", 0) {
  629. legacyMode = true;
  630. argPos += 1;
  631. continue;
  632. }
  633. ARG_CASE("-nopreprocess", 0) {
  634. geometryPreproc = NO_PREPROCESS;
  635. argPos += 1;
  636. continue;
  637. }
  638. ARG_CASE("-windingpreprocess", 0) {
  639. geometryPreproc = WINDING_PREPROCESS;
  640. argPos += 1;
  641. continue;
  642. }
  643. ARG_CASE("-preprocess", 0) {
  644. geometryPreproc = FULL_PREPROCESS;
  645. argPos += 1;
  646. continue;
  647. }
  648. ARG_CASE("-nooverlap", 0) {
  649. generatorConfig.overlapSupport = false;
  650. argPos += 1;
  651. continue;
  652. }
  653. ARG_CASE("-overlap", 0) {
  654. generatorConfig.overlapSupport = true;
  655. argPos += 1;
  656. continue;
  657. }
  658. ARG_CASE("-noscanline", 0) {
  659. scanlinePass = false;
  660. argPos += 1;
  661. continue;
  662. }
  663. ARG_CASE("-scanline", 0) {
  664. scanlinePass = true;
  665. argPos += 1;
  666. continue;
  667. }
  668. ARG_CASE("-fillrule", 1) {
  669. scanlinePass = true;
  670. if (!strcmp(argv[argPos+1], "nonzero")) fillRule = FILL_NONZERO;
  671. else if (!strcmp(argv[argPos+1], "evenodd") || !strcmp(argv[argPos+1], "odd")) fillRule = FILL_ODD;
  672. else if (!strcmp(argv[argPos+1], "positive")) fillRule = FILL_POSITIVE;
  673. else if (!strcmp(argv[argPos+1], "negative")) fillRule = FILL_NEGATIVE;
  674. else
  675. fputs("Unknown fill rule specified.\n", stderr);
  676. argPos += 2;
  677. continue;
  678. }
  679. ARG_CASE("-format", 1) {
  680. if (!strcmp(argv[argPos+1], "auto")) format = AUTO;
  681. #if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_PNG)
  682. else if (!strcmp(argv[argPos+1], "png")) SET_FORMAT(PNG, "png");
  683. #else
  684. else if (!strcmp(argv[argPos+1], "png"))
  685. fputs("PNG format is not available in core-only version.\n", stderr);
  686. #endif
  687. else if (!strcmp(argv[argPos+1], "bmp")) SET_FORMAT(BMP, "bmp");
  688. else if (!strcmp(argv[argPos+1], "tiff") || !strcmp(argv[argPos+1], "tif")) SET_FORMAT(TIFF, "tif");
  689. else if (!strcmp(argv[argPos+1], "text") || !strcmp(argv[argPos+1], "txt")) SET_FORMAT(TEXT, "txt");
  690. else if (!strcmp(argv[argPos+1], "textfloat") || !strcmp(argv[argPos+1], "txtfloat")) SET_FORMAT(TEXT_FLOAT, "txt");
  691. else if (!strcmp(argv[argPos+1], "bin") || !strcmp(argv[argPos+1], "binary")) SET_FORMAT(BINARY, "bin");
  692. else if (!strcmp(argv[argPos+1], "binfloat") || !strcmp(argv[argPos+1], "binfloatle")) SET_FORMAT(BINARY_FLOAT, "bin");
  693. else if (!strcmp(argv[argPos+1], "binfloatbe")) SET_FORMAT(BINARY_FLOAT_BE, "bin");
  694. else
  695. fputs("Unknown format specified.\n", stderr);
  696. argPos += 2;
  697. continue;
  698. }
  699. ARG_CASE("-size", 2) {
  700. unsigned w, h;
  701. if (!(parseUnsigned(w, argv[argPos+1]) && parseUnsigned(h, argv[argPos+2]) && w && h))
  702. ABORT("Invalid size arguments. Use -size <width> <height> with two positive integers.");
  703. width = w, height = h;
  704. argPos += 3;
  705. continue;
  706. }
  707. ARG_CASE("-autoframe", 0) {
  708. autoFrame = true;
  709. argPos += 1;
  710. continue;
  711. }
  712. ARG_CASE("-range", 1) {
  713. real r;
  714. if (!(parseReal(r, argv[argPos+1]) && r > real(0)))
  715. ABORT("Invalid range argument. Use -range <range> with a positive real number.");
  716. rangeMode = RANGE_UNIT;
  717. range = r;
  718. argPos += 2;
  719. continue;
  720. }
  721. ARG_CASE("-pxrange", 1) {
  722. real r;
  723. if (!(parseReal(r, argv[argPos+1]) && r > real(0)))
  724. ABORT("Invalid range argument. Use -pxrange <range> with a positive real number.");
  725. rangeMode = RANGE_PX;
  726. pxRange = r;
  727. argPos += 2;
  728. continue;
  729. }
  730. ARG_CASE("-scale", 1) {
  731. real s;
  732. if (!(parseReal(s, argv[argPos+1]) && s > real(0)))
  733. ABORT("Invalid scale argument. Use -scale <scale> with a positive real number.");
  734. scale = s;
  735. scaleSpecified = true;
  736. argPos += 2;
  737. continue;
  738. }
  739. ARG_CASE("-ascale", 2) {
  740. real sx, sy;
  741. if (!(parseReal(sx, argv[argPos+1]) && parseReal(sy, argv[argPos+2]) && sx > real(0) && sy > real(0)))
  742. ABORT("Invalid scale arguments. Use -ascale <x> <y> with two positive real numbers.");
  743. scale.set(sx, sy);
  744. scaleSpecified = true;
  745. argPos += 3;
  746. continue;
  747. }
  748. ARG_CASE("-translate", 2) {
  749. real tx, ty;
  750. if (!(parseReal(tx, argv[argPos+1]) && parseReal(ty, argv[argPos+2])))
  751. ABORT("Invalid translate arguments. Use -translate <x> <y> with two real numbers.");
  752. translate.set(tx, ty);
  753. argPos += 3;
  754. continue;
  755. }
  756. ARG_CASE("-angle", 1) {
  757. real at;
  758. if (!parseAngle(at, argv[argPos+1]))
  759. 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.");
  760. angleThreshold = at;
  761. argPos += 2;
  762. continue;
  763. }
  764. ARG_CASE("-errorcorrection", 1) {
  765. if (!strcmp(argv[argPos+1], "disabled") || !strcmp(argv[argPos+1], "0") || !strcmp(argv[argPos+1], "none")) {
  766. generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::DISABLED;
  767. generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
  768. } else if (!strcmp(argv[argPos+1], "default") || !strcmp(argv[argPos+1], "auto") || !strcmp(argv[argPos+1], "auto-mixed") || !strcmp(argv[argPos+1], "mixed")) {
  769. generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_PRIORITY;
  770. generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::CHECK_DISTANCE_AT_EDGE;
  771. } else if (!strcmp(argv[argPos+1], "auto-fast") || !strcmp(argv[argPos+1], "fast")) {
  772. generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_PRIORITY;
  773. generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
  774. } else if (!strcmp(argv[argPos+1], "auto-full") || !strcmp(argv[argPos+1], "full")) {
  775. generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_PRIORITY;
  776. generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE;
  777. } else if (!strcmp(argv[argPos+1], "distance") || !strcmp(argv[argPos+1], "distance-fast") || !strcmp(argv[argPos+1], "indiscriminate") || !strcmp(argv[argPos+1], "indiscriminate-fast")) {
  778. generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::INDISCRIMINATE;
  779. generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
  780. } else if (!strcmp(argv[argPos+1], "distance-full") || !strcmp(argv[argPos+1], "indiscriminate-full")) {
  781. generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::INDISCRIMINATE;
  782. generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE;
  783. } else if (!strcmp(argv[argPos+1], "edge-fast")) {
  784. generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_ONLY;
  785. generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
  786. } else if (!strcmp(argv[argPos+1], "edge") || !strcmp(argv[argPos+1], "edge-full")) {
  787. generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_ONLY;
  788. generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE;
  789. } else if (!strcmp(argv[argPos+1], "help")) {
  790. puts(errorCorrectionHelpText);
  791. return 0;
  792. } else
  793. fputs("Unknown error correction mode. Use -errorcorrection help for more information.\n", stderr);
  794. explicitErrorCorrectionMode = true;
  795. argPos += 2;
  796. continue;
  797. }
  798. ARG_CASE("-errordeviationratio", 1) {
  799. real edr;
  800. if (!(parseReal(edr, argv[argPos+1]) && edr > real(0)))
  801. ABORT("Invalid error deviation ratio. Use -errordeviationratio <ratio> with a positive real number.");
  802. generatorConfig.errorCorrection.minDeviationRatio = edr;
  803. argPos += 2;
  804. continue;
  805. }
  806. ARG_CASE("-errorimproveratio", 1) {
  807. real eir;
  808. if (!(parseReal(eir, argv[argPos+1]) && eir > real(0)))
  809. ABORT("Invalid error improvement ratio. Use -errorimproveratio <ratio> with a positive real number.");
  810. generatorConfig.errorCorrection.minImproveRatio = eir;
  811. argPos += 2;
  812. continue;
  813. }
  814. ARG_CASE("-coloringstrategy", 1) {
  815. if (!strcmp(argv[argPos+1], "simple")) edgeColoring = edgeColoringSimple;
  816. else if (!strcmp(argv[argPos+1], "inktrap")) edgeColoring = edgeColoringInkTrap;
  817. else if (!strcmp(argv[argPos+1], "distance")) edgeColoring = edgeColoringByDistance;
  818. else
  819. fputs("Unknown coloring strategy specified.\n", stderr);
  820. argPos += 2;
  821. continue;
  822. }
  823. ARG_CASE("-edgecolors", 1) {
  824. static const char *allowed = " ?,cmwyCMWY";
  825. for (int i = 0; argv[argPos+1][i]; ++i) {
  826. for (int j = 0; allowed[j]; ++j)
  827. if (argv[argPos+1][i] == allowed[j])
  828. goto EDGE_COLOR_VERIFIED;
  829. ABORT("Invalid edge coloring sequence. Use -edgecolors <color sequence> with only the colors C, M, Y, and W. Separate contours by commas and use ? to keep the default assigment for a contour.");
  830. EDGE_COLOR_VERIFIED:;
  831. }
  832. edgeAssignment = argv[argPos+1];
  833. argPos += 2;
  834. continue;
  835. }
  836. ARG_CASE("-distanceshift", 1) {
  837. real ds;
  838. if (!parseReal(ds, argv[argPos+1]))
  839. ABORT("Invalid distance shift. Use -distanceshift <shift> with a real value.");
  840. outputDistanceShift = (float) ds;
  841. argPos += 2;
  842. continue;
  843. }
  844. ARG_CASE("-exportshape", 1) {
  845. shapeExport = argv[argPos+1];
  846. argPos += 2;
  847. continue;
  848. }
  849. ARG_CASE("-testrender", 3) {
  850. unsigned w, h;
  851. if (!parseUnsigned(w, argv[argPos+2]) || !parseUnsigned(h, argv[argPos+3]) || !w || !h)
  852. ABORT("Invalid arguments for test render. Use -testrender <output." DEFAULT_IMAGE_EXTENSION "> <width> <height>.");
  853. testRender = argv[argPos+1];
  854. testWidth = w, testHeight = h;
  855. argPos += 4;
  856. continue;
  857. }
  858. ARG_CASE("-testrendermulti", 3) {
  859. unsigned w, h;
  860. if (!parseUnsigned(w, argv[argPos+2]) || !parseUnsigned(h, argv[argPos+3]) || !w || !h)
  861. ABORT("Invalid arguments for test render. Use -testrendermulti <output." DEFAULT_IMAGE_EXTENSION "> <width> <height>.");
  862. testRenderMulti = argv[argPos+1];
  863. testWidthM = w, testHeightM = h;
  864. argPos += 4;
  865. continue;
  866. }
  867. ARG_CASE("-yflip", 0) {
  868. yFlip = true;
  869. argPos += 1;
  870. continue;
  871. }
  872. ARG_CASE("-printmetrics", 0) {
  873. printMetrics = true;
  874. argPos += 1;
  875. continue;
  876. }
  877. ARG_CASE("-estimateerror", 0) {
  878. estimateError = true;
  879. argPos += 1;
  880. continue;
  881. }
  882. ARG_CASE("-keeporder", 0) {
  883. orientation = KEEP;
  884. argPos += 1;
  885. continue;
  886. }
  887. ARG_CASE("-reverseorder", 0) {
  888. orientation = REVERSE;
  889. argPos += 1;
  890. continue;
  891. }
  892. ARG_CASE("-guessorder", 0) {
  893. orientation = GUESS;
  894. argPos += 1;
  895. continue;
  896. }
  897. ARG_CASE("-seed", 1) {
  898. if (!parseUnsignedLL(coloringSeed, argv[argPos+1]))
  899. ABORT("Invalid seed. Use -seed <N> with N being a non-negative integer.");
  900. argPos += 2;
  901. continue;
  902. }
  903. ARG_CASE("-version", 0) {
  904. puts(versionText);
  905. return 0;
  906. }
  907. ARG_CASE("-help", 0) {
  908. puts(helpText);
  909. return 0;
  910. }
  911. fprintf(stderr, "Unknown setting or insufficient parameters: %s\n", argv[argPos]);
  912. suggestHelp = true;
  913. ++argPos;
  914. }
  915. if (suggestHelp)
  916. fprintf(stderr, "Use -help for more information.\n");
  917. // Load input
  918. Shape::Bounds svgViewBox = { };
  919. real glyphAdvance = 0;
  920. if (!inputType || !input) {
  921. #ifdef MSDFGEN_EXTENSIONS
  922. #ifdef MSDFGEN_DISABLE_SVG
  923. ABORT("No input specified! Use -font <file.ttf/otf> <character code> or see -help.");
  924. #else
  925. ABORT("No input specified! Use either -svg <file.svg> or -font <file.ttf/otf> <character code>, or see -help.");
  926. #endif
  927. #else
  928. ABORT("No input specified! See -help.");
  929. #endif
  930. }
  931. if (mode == MULTI_AND_TRUE && (format == BMP || (format == AUTO && output && cmpExtension(output, ".bmp"))))
  932. ABORT("Incompatible image format. A BMP file cannot contain alpha channel, which is required in mtsdf mode.");
  933. Shape shape;
  934. switch (inputType) {
  935. #if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_SVG)
  936. case SVG: {
  937. int svgImportFlags = loadSvgShape(shape, svgViewBox, input);
  938. if (!(svgImportFlags&SVG_IMPORT_SUCCESS_FLAG))
  939. ABORT("Failed to load shape from SVG file.");
  940. if (svgImportFlags&SVG_IMPORT_PARTIAL_FAILURE_FLAG)
  941. fputs("Warning: Failed to load part of SVG file.\n", stderr);
  942. if (svgImportFlags&SVG_IMPORT_INCOMPLETE_FLAG)
  943. fputs("Warning: SVG file contains multiple paths or shapes but this version is only able to load one.\n", stderr);
  944. else if (svgImportFlags&SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG)
  945. fputs("Warning: SVG file likely contains elements that are unsupported.\n", stderr);
  946. if (svgImportFlags&SVG_IMPORT_TRANSFORMATION_IGNORED_FLAG)
  947. fputs("Warning: SVG path transformation ignored.\n", stderr);
  948. break;
  949. }
  950. #endif
  951. #ifdef MSDFGEN_EXTENSIONS
  952. case FONT: case VAR_FONT: {
  953. if (!glyphIndexSpecified && !unicode)
  954. ABORT("No character specified! Use -font <file.ttf/otf> <character code>. Character code can be a Unicode index (65, 0x41), a character in apostrophes ('A'), or a glyph index prefixed by g (g36, g0x24).");
  955. FreetypeHandle *ft = initializeFreetype();
  956. if (!ft)
  957. return -1;
  958. FontHandle *font = (
  959. #ifndef MSDFGEN_DISABLE_VARIABLE_FONTS
  960. inputType == VAR_FONT ? loadVarFont(ft, input) :
  961. #endif
  962. loadFont(ft, input)
  963. );
  964. if (!font) {
  965. deinitializeFreetype(ft);
  966. ABORT("Failed to load font file.");
  967. }
  968. if (unicode)
  969. getGlyphIndex(glyphIndex, font, unicode);
  970. if (!loadGlyph(shape, font, glyphIndex, &glyphAdvance)) {
  971. destroyFont(font);
  972. deinitializeFreetype(ft);
  973. ABORT("Failed to load glyph from font file.");
  974. }
  975. destroyFont(font);
  976. deinitializeFreetype(ft);
  977. break;
  978. }
  979. #endif
  980. case DESCRIPTION_ARG: {
  981. if (!readShapeDescription(input, shape, &skipColoring))
  982. ABORT("Parse error in shape description.");
  983. break;
  984. }
  985. case DESCRIPTION_STDIN: {
  986. if (!readShapeDescription(stdin, shape, &skipColoring))
  987. ABORT("Parse error in shape description.");
  988. break;
  989. }
  990. case DESCRIPTION_FILE: {
  991. FILE *file = fopen(input, "r");
  992. if (!file)
  993. ABORT("Failed to load shape description file.");
  994. if (!readShapeDescription(file, shape, &skipColoring))
  995. ABORT("Parse error in shape description.");
  996. fclose(file);
  997. break;
  998. }
  999. default:;
  1000. }
  1001. // Validate and normalize shape
  1002. if (!shape.validate())
  1003. ABORT("The geometry of the loaded shape is invalid.");
  1004. switch (geometryPreproc) {
  1005. case NO_PREPROCESS:
  1006. break;
  1007. case WINDING_PREPROCESS:
  1008. shape.orientContours();
  1009. break;
  1010. case FULL_PREPROCESS:
  1011. #ifdef MSDFGEN_USE_SKIA
  1012. if (!resolveShapeGeometry(shape))
  1013. fputs("Shape geometry preprocessing failed, skipping.\n", stderr);
  1014. else if (skipColoring) {
  1015. skipColoring = false;
  1016. fputs("Note: Input shape coloring won't be preserved due to geometry preprocessing.\n", stderr);
  1017. }
  1018. #else
  1019. ABORT("Shape geometry preprocessing (-preprocess) is not available in this version because the Skia library is not present.");
  1020. #endif
  1021. break;
  1022. }
  1023. shape.normalize();
  1024. if (yFlip)
  1025. shape.inverseYAxis = !shape.inverseYAxis;
  1026. real avgScale = real(.5)*(scale.x+scale.y);
  1027. Shape::Bounds bounds = { };
  1028. if (autoFrame || mode == METRICS || printMetrics || orientation == GUESS)
  1029. bounds = shape.getBounds();
  1030. // Auto-frame
  1031. if (autoFrame) {
  1032. real l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t;
  1033. Vector2 frame(width, height);
  1034. real m = real(.5)+real(outputDistanceShift);
  1035. if (!scaleSpecified) {
  1036. if (rangeMode == RANGE_UNIT)
  1037. l -= m*range, b -= m*range, r += m*range, t += m*range;
  1038. else
  1039. frame -= real(2)*m*pxRange;
  1040. }
  1041. if (l >= r || b >= t)
  1042. l = 0, b = 0, r = 1, t = 1;
  1043. if (frame.x <= real(0) || frame.y <= real(0))
  1044. ABORT("Cannot fit the specified pixel range.");
  1045. Vector2 dims(r-l, t-b);
  1046. if (scaleSpecified)
  1047. translate = real(.5)*(frame/scale-dims)-Vector2(l, b);
  1048. else {
  1049. if (dims.x*frame.y < dims.y*frame.x) {
  1050. translate.set(real(.5)*(frame.x/frame.y*dims.y-dims.x)-l, -b);
  1051. scale = avgScale = frame.y/dims.y;
  1052. } else {
  1053. translate.set(-l, real(.5)*(frame.y/frame.x*dims.x-dims.y)-b);
  1054. scale = avgScale = frame.x/dims.x;
  1055. }
  1056. }
  1057. if (rangeMode == RANGE_PX && !scaleSpecified)
  1058. translate += m*pxRange/scale;
  1059. }
  1060. if (rangeMode == RANGE_PX)
  1061. range = pxRange/min(scale.x, scale.y);
  1062. // Print metrics
  1063. if (mode == METRICS || printMetrics) {
  1064. FILE *out = stdout;
  1065. if (mode == METRICS && outputSpecified)
  1066. out = fopen(output, "w");
  1067. if (!out)
  1068. ABORT("Failed to write output file.");
  1069. if (shape.inverseYAxis)
  1070. fprintf(out, "inverseY = true\n");
  1071. if (svgViewBox.l < svgViewBox.r && svgViewBox.b < svgViewBox.t)
  1072. fprintf(out, "view box = %.17g, %.17g, %.17g, %.17g\n", double(svgViewBox.l), double(svgViewBox.b), double(svgViewBox.r), double(svgViewBox.t));
  1073. if (bounds.l < bounds.r && bounds.b < bounds.t)
  1074. fprintf(out, "bounds = %.17g, %.17g, %.17g, %.17g\n", double(bounds.l), double(bounds.b), double(bounds.r), double(bounds.t));
  1075. if (glyphAdvance != 0)
  1076. fprintf(out, "advance = %.17g\n", double(glyphAdvance));
  1077. if (autoFrame) {
  1078. if (!scaleSpecified)
  1079. fprintf(out, "scale = %.17g\n", double(avgScale));
  1080. fprintf(out, "translate = %.17g, %.17g\n", double(translate.x), double(translate.y));
  1081. }
  1082. if (rangeMode == RANGE_PX)
  1083. fprintf(out, "range = %.17g\n", double(range));
  1084. if (mode == METRICS && outputSpecified)
  1085. fclose(out);
  1086. }
  1087. // Compute output
  1088. Projection projection(scale, translate);
  1089. Bitmap<float, 1> sdf;
  1090. Bitmap<float, 3> msdf;
  1091. Bitmap<float, 4> mtsdf;
  1092. MSDFGeneratorConfig postErrorCorrectionConfig(generatorConfig);
  1093. if (scanlinePass) {
  1094. if (explicitErrorCorrectionMode && generatorConfig.errorCorrection.distanceCheckMode != ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE) {
  1095. const char *fallbackModeName = "unknown";
  1096. switch (generatorConfig.errorCorrection.mode) {
  1097. case ErrorCorrectionConfig::DISABLED: fallbackModeName = "disabled"; break;
  1098. case ErrorCorrectionConfig::INDISCRIMINATE: fallbackModeName = "distance-fast"; break;
  1099. case ErrorCorrectionConfig::EDGE_PRIORITY: fallbackModeName = "auto-fast"; break;
  1100. case ErrorCorrectionConfig::EDGE_ONLY: fallbackModeName = "edge-fast"; break;
  1101. }
  1102. fprintf(stderr, "Selected error correction mode not compatible with scanline pass, falling back to %s.\n", fallbackModeName);
  1103. }
  1104. generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::DISABLED;
  1105. postErrorCorrectionConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
  1106. }
  1107. switch (mode) {
  1108. case SINGLE: {
  1109. sdf = Bitmap<float, 1>(width, height);
  1110. if (legacyMode)
  1111. generateSDF_legacy(sdf, shape, range, scale, translate);
  1112. else
  1113. generateSDF(sdf, shape, projection, range, generatorConfig);
  1114. break;
  1115. }
  1116. case PSEUDO: {
  1117. sdf = Bitmap<float, 1>(width, height);
  1118. if (legacyMode)
  1119. generatePseudoSDF_legacy(sdf, shape, range, scale, translate);
  1120. else
  1121. generatePseudoSDF(sdf, shape, projection, range, generatorConfig);
  1122. break;
  1123. }
  1124. case MULTI: {
  1125. if (!skipColoring)
  1126. edgeColoring(shape, angleThreshold, coloringSeed);
  1127. if (edgeAssignment)
  1128. parseColoring(shape, edgeAssignment);
  1129. msdf = Bitmap<float, 3>(width, height);
  1130. if (legacyMode)
  1131. generateMSDF_legacy(msdf, shape, range, scale, translate, generatorConfig.errorCorrection);
  1132. else
  1133. generateMSDF(msdf, shape, projection, range, generatorConfig);
  1134. break;
  1135. }
  1136. case MULTI_AND_TRUE: {
  1137. if (!skipColoring)
  1138. edgeColoring(shape, angleThreshold, coloringSeed);
  1139. if (edgeAssignment)
  1140. parseColoring(shape, edgeAssignment);
  1141. mtsdf = Bitmap<float, 4>(width, height);
  1142. if (legacyMode)
  1143. generateMTSDF_legacy(mtsdf, shape, range, scale, translate, generatorConfig.errorCorrection);
  1144. else
  1145. generateMTSDF(mtsdf, shape, projection, range, generatorConfig);
  1146. break;
  1147. }
  1148. default:;
  1149. }
  1150. if (orientation == GUESS) {
  1151. // Get sign of signed distance outside bounds
  1152. Point2 p(bounds.l-(bounds.r-bounds.l)-real(1), bounds.b-(bounds.t-bounds.b)-real(1));
  1153. real distance = SimpleTrueShapeDistanceFinder::oneShotDistance(shape, p);
  1154. orientation = distance <= real(0) ? KEEP : REVERSE;
  1155. }
  1156. if (orientation == REVERSE) {
  1157. switch (mode) {
  1158. case SINGLE:
  1159. case PSEUDO:
  1160. invertColor<1>(sdf);
  1161. break;
  1162. case MULTI:
  1163. invertColor<3>(msdf);
  1164. break;
  1165. case MULTI_AND_TRUE:
  1166. invertColor<4>(mtsdf);
  1167. break;
  1168. default:;
  1169. }
  1170. }
  1171. if (scanlinePass) {
  1172. switch (mode) {
  1173. case SINGLE:
  1174. case PSEUDO:
  1175. distanceSignCorrection(sdf, shape, projection, fillRule);
  1176. break;
  1177. case MULTI:
  1178. distanceSignCorrection(msdf, shape, projection, fillRule);
  1179. msdfErrorCorrection(msdf, shape, projection, range, postErrorCorrectionConfig);
  1180. break;
  1181. case MULTI_AND_TRUE:
  1182. distanceSignCorrection(mtsdf, shape, projection, fillRule);
  1183. msdfErrorCorrection(msdf, shape, projection, range, postErrorCorrectionConfig);
  1184. break;
  1185. default:;
  1186. }
  1187. }
  1188. if (outputDistanceShift) {
  1189. float *pixel = NULL, *pixelsEnd = NULL;
  1190. switch (mode) {
  1191. case SINGLE:
  1192. case PSEUDO:
  1193. pixel = (float *) sdf;
  1194. pixelsEnd = pixel+1*sdf.width()*sdf.height();
  1195. break;
  1196. case MULTI:
  1197. pixel = (float *) msdf;
  1198. pixelsEnd = pixel+3*msdf.width()*msdf.height();
  1199. break;
  1200. case MULTI_AND_TRUE:
  1201. pixel = (float *) mtsdf;
  1202. pixelsEnd = pixel+4*mtsdf.width()*mtsdf.height();
  1203. break;
  1204. default:;
  1205. }
  1206. while (pixel < pixelsEnd)
  1207. *pixel++ += outputDistanceShift;
  1208. }
  1209. // Save output
  1210. if (shapeExport) {
  1211. FILE *file = fopen(shapeExport, "w");
  1212. if (file) {
  1213. writeShapeDescription(file, shape);
  1214. fclose(file);
  1215. } else
  1216. fputs("Failed to write shape export file.\n", stderr);
  1217. }
  1218. const char *error = NULL;
  1219. switch (mode) {
  1220. case SINGLE:
  1221. case PSEUDO:
  1222. if ((error = writeOutput<1>(sdf, output, format))) {
  1223. fprintf(stderr, "%s\n", error);
  1224. return 1;
  1225. }
  1226. if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError))
  1227. simulate8bit(sdf);
  1228. if (estimateError) {
  1229. double sdfError = estimateSDFError(sdf, shape, projection, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
  1230. printf("SDF error ~ %e\n", sdfError);
  1231. }
  1232. if (testRenderMulti) {
  1233. Bitmap<float, 3> render(testWidthM, testHeightM);
  1234. renderSDF(render, sdf, avgScale*range, .5f+outputDistanceShift);
  1235. if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRenderMulti))
  1236. fputs("Failed to write test render file.\n", stderr);
  1237. }
  1238. if (testRender) {
  1239. Bitmap<float, 1> render(testWidth, testHeight);
  1240. renderSDF(render, sdf, avgScale*range, .5f+outputDistanceShift);
  1241. if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRender))
  1242. fputs("Failed to write test render file.\n", stderr);
  1243. }
  1244. break;
  1245. case MULTI:
  1246. if ((error = writeOutput<3>(msdf, output, format))) {
  1247. fprintf(stderr, "%s\n", error);
  1248. return 1;
  1249. }
  1250. if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError))
  1251. simulate8bit(msdf);
  1252. if (estimateError) {
  1253. double sdfError = estimateSDFError(msdf, shape, projection, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
  1254. printf("SDF error ~ %e\n", sdfError);
  1255. }
  1256. if (testRenderMulti) {
  1257. Bitmap<float, 3> render(testWidthM, testHeightM);
  1258. renderSDF(render, msdf, avgScale*range, .5f+outputDistanceShift);
  1259. if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRenderMulti))
  1260. fputs("Failed to write test render file.\n", stderr);
  1261. }
  1262. if (testRender) {
  1263. Bitmap<float, 1> render(testWidth, testHeight);
  1264. renderSDF(render, msdf, avgScale*range, .5f+outputDistanceShift);
  1265. if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRender))
  1266. fputs("Failed to write test render file.\n", stderr);
  1267. }
  1268. break;
  1269. case MULTI_AND_TRUE:
  1270. if ((error = writeOutput<4>(mtsdf, output, format))) {
  1271. fprintf(stderr, "%s\n", error);
  1272. return 1;
  1273. }
  1274. if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError))
  1275. simulate8bit(mtsdf);
  1276. if (estimateError) {
  1277. double sdfError = estimateSDFError(mtsdf, shape, projection, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
  1278. printf("SDF error ~ %e\n", sdfError);
  1279. }
  1280. if (testRenderMulti) {
  1281. Bitmap<float, 4> render(testWidthM, testHeightM);
  1282. renderSDF(render, mtsdf, avgScale*range, .5f+outputDistanceShift);
  1283. if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRenderMulti))
  1284. fputs("Failed to write test render file.\n", stderr);
  1285. }
  1286. if (testRender) {
  1287. Bitmap<float, 1> render(testWidth, testHeight);
  1288. renderSDF(render, mtsdf, avgScale*range, .5f+outputDistanceShift);
  1289. if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRender))
  1290. fputs("Failed to write test render file.\n", stderr);
  1291. }
  1292. break;
  1293. default:;
  1294. }
  1295. return 0;
  1296. }
  1297. #endif