main.cpp 53 KB

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