main.cpp 57 KB

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