main.cpp 53 KB

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