main.cpp 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094
  1. /*
  2. * MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.8 (2020-10-17) - standalone console program
  3. * --------------------------------------------------------------------------------------------
  4. * A utility by Viktor Chlumsky, (c) 2014 - 2020
  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 "msdfgen.h"
  14. #include "msdfgen-ext.h"
  15. #include "core/ShapeDistanceFinder.h"
  16. #define SDF_ERROR_ESTIMATE_PRECISION 19
  17. #define DEFAULT_ANGLE_THRESHOLD 3.
  18. using namespace msdfgen;
  19. enum Format {
  20. AUTO,
  21. PNG,
  22. BMP,
  23. TIFF,
  24. TEXT,
  25. TEXT_FLOAT,
  26. BINARY,
  27. BINARY_FLOAT,
  28. BINARY_FLOAT_BE
  29. };
  30. static bool is8bitFormat(Format format) {
  31. return format == PNG || format == BMP || format == TEXT || format == BINARY;
  32. }
  33. static char toupper(char c) {
  34. return c >= 'a' && c <= 'z' ? c-'a'+'A' : c;
  35. }
  36. static bool parseUnsigned(unsigned &value, const char *arg) {
  37. char c;
  38. return sscanf(arg, "%u%c", &value, &c) == 1;
  39. }
  40. static bool parseUnsignedDecOrHex(unsigned &value, const char *arg) {
  41. if (arg[0] == '0' && (arg[1] == 'x' || arg[1] == 'X')) {
  42. char c;
  43. return sscanf(arg+2, "%x%c", &value, &c) == 1;
  44. }
  45. return parseUnsigned(value, arg);
  46. }
  47. static bool parseUnsignedLL(unsigned long long &value, const char *arg) {
  48. char c;
  49. return sscanf(arg, "%llu%c", &value, &c) == 1;
  50. }
  51. static bool parseDouble(double &value, const char *arg) {
  52. char c;
  53. return sscanf(arg, "%lf%c", &value, &c) == 1;
  54. }
  55. static bool parseUnicode(unicode_t &unicode, const char *arg) {
  56. unsigned uuc;
  57. if (parseUnsignedDecOrHex(uuc, arg)) {
  58. unicode = uuc;
  59. return true;
  60. }
  61. if (arg[0] == '\'' && arg[1] && arg[2] == '\'' && !arg[3]) {
  62. unicode = (unicode_t) (unsigned char) arg[1];
  63. return true;
  64. }
  65. return false;
  66. }
  67. static bool parseAngle(double &value, const char *arg) {
  68. char c1, c2;
  69. int result = sscanf(arg, "%lf%c%c", &value, &c1, &c2);
  70. if (result == 1)
  71. return true;
  72. if (result == 2 && (c1 == 'd' || c1 == 'D')) {
  73. value *= M_PI/180;
  74. return true;
  75. }
  76. return false;
  77. }
  78. static void parseColoring(Shape &shape, const char *edgeAssignment) {
  79. unsigned c = 0, e = 0;
  80. if (shape.contours.size() < c) return;
  81. Contour *contour = &shape.contours[c];
  82. bool change = false;
  83. bool clear = true;
  84. for (const char *in = edgeAssignment; *in; ++in) {
  85. switch (*in) {
  86. case ',':
  87. if (change)
  88. ++e;
  89. if (clear)
  90. while (e < contour->edges.size()) {
  91. contour->edges[e]->color = WHITE;
  92. ++e;
  93. }
  94. ++c, e = 0;
  95. if (shape.contours.size() <= c) return;
  96. contour = &shape.contours[c];
  97. change = false;
  98. clear = true;
  99. break;
  100. case '?':
  101. clear = false;
  102. break;
  103. case 'C': case 'M': case 'W': case 'Y': case 'c': case 'm': case 'w': case 'y':
  104. if (change) {
  105. ++e;
  106. change = false;
  107. }
  108. if (e < contour->edges.size()) {
  109. contour->edges[e]->color = EdgeColor(
  110. (*in == 'C' || *in == 'c')*CYAN|
  111. (*in == 'M' || *in == 'm')*MAGENTA|
  112. (*in == 'Y' || *in == 'y')*YELLOW|
  113. (*in == 'W' || *in == 'w')*WHITE);
  114. change = true;
  115. }
  116. break;
  117. }
  118. }
  119. }
  120. template <int N>
  121. static void invertColor(const BitmapRef<float, N> &bitmap) {
  122. const float *end = bitmap.pixels+N*bitmap.width*bitmap.height;
  123. for (float *p = bitmap.pixels; p < end; ++p)
  124. *p = 1.f-*p;
  125. }
  126. static bool writeTextBitmap(FILE *file, const float *values, int cols, int rows) {
  127. for (int row = 0; row < rows; ++row) {
  128. for (int col = 0; col < cols; ++col) {
  129. int v = clamp(int((*values++)*0x100), 0xff);
  130. fprintf(file, col ? " %02X" : "%02X", v);
  131. }
  132. fprintf(file, "\n");
  133. }
  134. return true;
  135. }
  136. static bool writeTextBitmapFloat(FILE *file, const float *values, int cols, int rows) {
  137. for (int row = 0; row < rows; ++row) {
  138. for (int col = 0; col < cols; ++col) {
  139. fprintf(file, col ? " %.9g" : "%.9g", *values++);
  140. }
  141. fprintf(file, "\n");
  142. }
  143. return true;
  144. }
  145. static bool writeBinBitmap(FILE *file, const float *values, int count) {
  146. for (int pos = 0; pos < count; ++pos) {
  147. unsigned char v = clamp(int((*values++)*0x100), 0xff);
  148. fwrite(&v, 1, 1, file);
  149. }
  150. return true;
  151. }
  152. #ifdef __BIG_ENDIAN__
  153. static bool writeBinBitmapFloatBE(FILE *file, const float *values, int count)
  154. #else
  155. static bool writeBinBitmapFloat(FILE *file, const float *values, int count)
  156. #endif
  157. {
  158. return (int) fwrite(values, sizeof(float), count, file) == count;
  159. }
  160. #ifdef __BIG_ENDIAN__
  161. static bool writeBinBitmapFloat(FILE *file, const float *values, int count)
  162. #else
  163. static bool writeBinBitmapFloatBE(FILE *file, const float *values, int count)
  164. #endif
  165. {
  166. for (int pos = 0; pos < count; ++pos) {
  167. const unsigned char *b = reinterpret_cast<const unsigned char *>(values++);
  168. for (int i = sizeof(float)-1; i >= 0; --i)
  169. fwrite(b+i, 1, 1, file);
  170. }
  171. return true;
  172. }
  173. static bool cmpExtension(const char *path, const char *ext) {
  174. for (const char *a = path+strlen(path)-1, *b = ext+strlen(ext)-1; b >= ext; --a, --b)
  175. if (a < path || toupper(*a) != toupper(*b))
  176. return false;
  177. return true;
  178. }
  179. template <int N>
  180. static const char * writeOutput(const BitmapConstRef<float, N> &bitmap, const char *filename, Format &format) {
  181. if (filename) {
  182. if (format == AUTO) {
  183. if (cmpExtension(filename, ".png")) format = PNG;
  184. else if (cmpExtension(filename, ".bmp")) format = BMP;
  185. else if (cmpExtension(filename, ".tif") || cmpExtension(filename, ".tiff")) format = TIFF;
  186. else if (cmpExtension(filename, ".txt")) format = TEXT;
  187. else if (cmpExtension(filename, ".bin")) format = BINARY;
  188. else
  189. return "Could not deduce format from output file name.";
  190. }
  191. switch (format) {
  192. case PNG: return savePng(bitmap, filename) ? NULL : "Failed to write output PNG image.";
  193. case BMP: return saveBmp(bitmap, filename) ? NULL : "Failed to write output BMP image.";
  194. case TIFF: return saveTiff(bitmap, filename) ? NULL : "Failed to write output TIFF image.";
  195. case TEXT: case TEXT_FLOAT: {
  196. FILE *file = fopen(filename, "w");
  197. if (!file) return "Failed to write output text file.";
  198. if (format == TEXT)
  199. writeTextBitmap(file, bitmap.pixels, N*bitmap.width, bitmap.height);
  200. else if (format == TEXT_FLOAT)
  201. writeTextBitmapFloat(file, bitmap.pixels, N*bitmap.width, bitmap.height);
  202. fclose(file);
  203. return NULL;
  204. }
  205. case BINARY: case BINARY_FLOAT: case BINARY_FLOAT_BE: {
  206. FILE *file = fopen(filename, "wb");
  207. if (!file) return "Failed to write output binary file.";
  208. if (format == BINARY)
  209. writeBinBitmap(file, bitmap.pixels, N*bitmap.width*bitmap.height);
  210. else if (format == BINARY_FLOAT)
  211. writeBinBitmapFloat(file, bitmap.pixels, N*bitmap.width*bitmap.height);
  212. else if (format == BINARY_FLOAT_BE)
  213. writeBinBitmapFloatBE(file, bitmap.pixels, N*bitmap.width*bitmap.height);
  214. fclose(file);
  215. return NULL;
  216. }
  217. default:;
  218. }
  219. } else {
  220. if (format == AUTO || format == TEXT)
  221. writeTextBitmap(stdout, bitmap.pixels, N*bitmap.width, bitmap.height);
  222. else if (format == TEXT_FLOAT)
  223. writeTextBitmapFloat(stdout, bitmap.pixels, N*bitmap.width, bitmap.height);
  224. else
  225. return "Unsupported format for standard output.";
  226. }
  227. return NULL;
  228. }
  229. #if defined(MSDFGEN_USE_SKIA) && defined(MSDFGEN_USE_OPENMP)
  230. #define TITLE_SUFFIX " with Skia & OpenMP"
  231. #define EXTRA_UNDERLINE "-------------------"
  232. #elif defined(MSDFGEN_USE_SKIA)
  233. #define TITLE_SUFFIX " with Skia"
  234. #define EXTRA_UNDERLINE "----------"
  235. #elif defined(MSDFGEN_USE_OPENMP)
  236. #define TITLE_SUFFIX " with OpenMP"
  237. #define EXTRA_UNDERLINE "------------"
  238. #else
  239. #define TITLE_SUFFIX
  240. #define EXTRA_UNDERLINE
  241. #endif
  242. static const char *helpText =
  243. "\n"
  244. "Multi-channel signed distance field generator by Viktor Chlumsky v" MSDFGEN_VERSION TITLE_SUFFIX "\n"
  245. "---------------------------------------------------------------------" EXTRA_UNDERLINE "\n"
  246. " Usage: msdfgen"
  247. #ifdef _WIN32
  248. ".exe"
  249. #endif
  250. " <mode> <input specification> <options>\n"
  251. "\n"
  252. "MODES\n"
  253. " sdf - Generate conventional monochrome (true) signed distance field.\n"
  254. " psdf - Generate monochrome signed pseudo-distance field.\n"
  255. " msdf - Generate multi-channel signed distance field. This is used by default if no mode is specified.\n"
  256. " mtsdf - Generate combined multi-channel and true signed distance field in the alpha channel.\n"
  257. " metrics - Report shape metrics only.\n"
  258. "\n"
  259. "INPUT SPECIFICATION\n"
  260. " -defineshape <definition>\n"
  261. "\tDefines input shape using the ad-hoc text definition.\n"
  262. " -font <filename.ttf> <character code>\n"
  263. "\tLoads a single glyph from the specified font file.\n"
  264. "\tFormat of character code is '?', 63, 0x3F (Unicode value), or g34 (glyph index).\n"
  265. " -shapedesc <filename.txt>\n"
  266. "\tLoads text shape description from a file.\n"
  267. " -stdin\n"
  268. "\tReads text shape description from the standard input.\n"
  269. " -svg <filename.svg>\n"
  270. "\tLoads the last vector path found in the specified SVG file.\n"
  271. "\n"
  272. // Keep alphabetical order!
  273. "OPTIONS\n"
  274. " -angle <angle>\n"
  275. "\tSpecifies the minimum angle between adjacent edges to be considered a corner. Append D for degrees.\n"
  276. " -ascale <x scale> <y scale>\n"
  277. "\tSets the scale used to convert shape units to pixels asymmetrically.\n"
  278. " -autoframe\n"
  279. "\tAutomatically scales (unless specified) and translates the shape to fit.\n"
  280. " -coloringstrategy <simple / inktrap>\n"
  281. "\tSelects the strategy of the edge coloring heuristic.\n"
  282. " -distanceshift <shift>\n"
  283. "\tShifts all normalized distances in the output distance field by this value.\n"
  284. " -edgecolors <sequence>\n"
  285. "\tOverrides automatic edge coloring with the specified color sequence.\n"
  286. " -errorcorrection <threshold>\n"
  287. "\tChanges the threshold used to detect and correct potential artifacts. 0 disables error correction.\n"
  288. " -estimateerror\n"
  289. "\tComputes and prints the distance field's estimated fill error to the standard output.\n"
  290. " -exportshape <filename.txt>\n"
  291. "\tSaves the shape description into a text file that can be edited and loaded using -shapedesc.\n"
  292. " -fillrule <nonzero / evenodd / positive / negative>\n"
  293. "\tSets the fill rule for the scanline pass. Default is nonzero.\n"
  294. " -format <png / bmp / tiff / text / textfloat / bin / binfloat / binfloatbe>\n"
  295. "\tSpecifies the output format of the distance field. Otherwise it is chosen based on output file extension.\n"
  296. " -guessorder\n"
  297. "\tAttempts to detect if shape contours have the wrong winding and generates the SDF with the right one.\n"
  298. " -help\n"
  299. "\tDisplays this help.\n"
  300. " -legacy\n"
  301. "\tUses the original (legacy) distance field algorithms.\n"
  302. #ifdef MSDFGEN_USE_SKIA
  303. " -nopreprocess\n"
  304. "\tDisables path preprocessing which resolves self-intersections and overlapping contours.\n"
  305. #else
  306. " -nooverlap\n"
  307. "\tDisables resolution of overlapping contours.\n"
  308. " -noscanline\n"
  309. "\tDisables the scanline pass, which corrects the distance field's signs according to the selected fill rule.\n"
  310. #endif
  311. " -o <filename>\n"
  312. "\tSets the output file name. The default value is \"output.png\".\n"
  313. #ifdef MSDFGEN_USE_SKIA
  314. " -overlap\n"
  315. "\tSwitches to distance field generator with support for overlapping contours.\n"
  316. #endif
  317. " -patchartifacts <balanced / edge / distance / none>\n"
  318. "\tSelects the strategy used to patch the MSDF to prevent interpolation artifacts.\n"
  319. " -printmetrics\n"
  320. "\tPrints relevant metrics of the shape to the standard output.\n"
  321. " -pxrange <range>\n"
  322. "\tSets the width of the range between the lowest and highest signed distance in pixels.\n"
  323. " -range <range>\n"
  324. "\tSets the width of the range between the lowest and highest signed distance in shape units.\n"
  325. " -reverseorder\n"
  326. "\tGenerates the distance field as if the shape's vertices were in reverse order.\n"
  327. " -scale <scale>\n"
  328. "\tSets the scale used to convert shape units to pixels.\n"
  329. #ifdef MSDFGEN_USE_SKIA
  330. " -scanline\n"
  331. "\tPerforms an additional scanline pass to fix the signs of the distances.\n"
  332. #endif
  333. " -seed <n>\n"
  334. "\tSets the random seed for edge coloring heuristic.\n"
  335. " -size <width> <height>\n"
  336. "\tSets the dimensions of the output image.\n"
  337. " -stdout\n"
  338. "\tPrints the output instead of storing it in a file. Only text formats are supported.\n"
  339. " -testrender <filename.png> <width> <height>\n"
  340. "\tRenders an image preview using the generated distance field and saves it as a PNG file.\n"
  341. " -testrendermulti <filename.png> <width> <height>\n"
  342. "\tRenders an image preview without flattening the color channels.\n"
  343. " -translate <x> <y>\n"
  344. "\tSets the translation of the shape in shape units.\n"
  345. " -yflip\n"
  346. "\tInverts the Y axis in the output distance field. The default order is bottom to top.\n"
  347. "\n";
  348. int main(int argc, const char * const *argv) {
  349. #define ABORT(msg) { puts(msg); return 1; }
  350. // Parse command line arguments
  351. enum {
  352. NONE,
  353. SVG,
  354. FONT,
  355. DESCRIPTION_ARG,
  356. DESCRIPTION_STDIN,
  357. DESCRIPTION_FILE
  358. } inputType = NONE;
  359. enum {
  360. SINGLE,
  361. PSEUDO,
  362. MULTI,
  363. MULTI_AND_TRUE,
  364. METRICS
  365. } mode = MULTI;
  366. bool legacyMode = false;
  367. bool geometryPreproc = (
  368. #ifdef MSDFGEN_USE_SKIA
  369. true
  370. #else
  371. false
  372. #endif
  373. );
  374. GeneratorConfig generatorConfig;
  375. generatorConfig.overlapSupport = !geometryPreproc;
  376. bool scanlinePass = !geometryPreproc;
  377. FillRule fillRule = FILL_NONZERO;
  378. Format format = AUTO;
  379. const char *input = NULL;
  380. const char *output = "output.png";
  381. const char *shapeExport = NULL;
  382. const char *testRender = NULL;
  383. const char *testRenderMulti = NULL;
  384. bool outputSpecified = false;
  385. GlyphIndex glyphIndex;
  386. unicode_t unicode = 0;
  387. int svgPathIndex = 0;
  388. int width = 64, height = 64;
  389. int testWidth = 0, testHeight = 0;
  390. int testWidthM = 0, testHeightM = 0;
  391. bool autoFrame = false;
  392. enum {
  393. RANGE_UNIT,
  394. RANGE_PX
  395. } rangeMode = RANGE_PX;
  396. double range = 1;
  397. double pxRange = 2;
  398. Vector2 translate;
  399. Vector2 scale = 1;
  400. bool scaleSpecified = false;
  401. double angleThreshold = DEFAULT_ANGLE_THRESHOLD;
  402. double errorCorrectionThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD;
  403. ArtifactPatcherConfig artifactPatcherConfig;
  404. float outputDistanceShift = 0.f;
  405. const char *edgeAssignment = NULL;
  406. bool yFlip = false;
  407. bool printMetrics = false;
  408. bool estimateError = false;
  409. bool skipColoring = false;
  410. enum {
  411. KEEP,
  412. REVERSE,
  413. GUESS
  414. } orientation = KEEP;
  415. unsigned long long coloringSeed = 0;
  416. void (*edgeColoring)(Shape &, double, unsigned long long) = edgeColoringSimple;
  417. int argPos = 1;
  418. bool suggestHelp = false;
  419. while (argPos < argc) {
  420. const char *arg = argv[argPos];
  421. #define ARG_CASE(s, p) if (!strcmp(arg, s) && argPos+(p) < argc)
  422. #define ARG_MODE(s, m) if (!strcmp(arg, s)) { mode = m; ++argPos; continue; }
  423. #define SET_FORMAT(fmt, ext) do { format = fmt; if (!outputSpecified) output = "output." ext; } while (false)
  424. ARG_MODE("sdf", SINGLE)
  425. ARG_MODE("psdf", PSEUDO)
  426. ARG_MODE("msdf", MULTI)
  427. ARG_MODE("mtsdf", MULTI_AND_TRUE)
  428. ARG_MODE("metrics", METRICS)
  429. ARG_CASE("-svg", 1) {
  430. inputType = SVG;
  431. input = argv[argPos+1];
  432. argPos += 2;
  433. continue;
  434. }
  435. ARG_CASE("-font", 2) {
  436. inputType = FONT;
  437. input = argv[argPos+1];
  438. const char *charArg = argv[argPos+2];
  439. unsigned gi;
  440. switch (charArg[0]) {
  441. case 'G': case 'g':
  442. if (parseUnsignedDecOrHex(gi, charArg+1))
  443. glyphIndex = GlyphIndex(gi);
  444. break;
  445. case 'U': case 'u':
  446. ++charArg;
  447. default:
  448. parseUnicode(unicode, charArg);
  449. }
  450. argPos += 3;
  451. continue;
  452. }
  453. ARG_CASE("-defineshape", 1) {
  454. inputType = DESCRIPTION_ARG;
  455. input = argv[argPos+1];
  456. argPos += 2;
  457. continue;
  458. }
  459. ARG_CASE("-stdin", 0) {
  460. inputType = DESCRIPTION_STDIN;
  461. input = "stdin";
  462. argPos += 1;
  463. continue;
  464. }
  465. ARG_CASE("-shapedesc", 1) {
  466. inputType = DESCRIPTION_FILE;
  467. input = argv[argPos+1];
  468. argPos += 2;
  469. continue;
  470. }
  471. ARG_CASE("-o", 1) {
  472. output = argv[argPos+1];
  473. outputSpecified = true;
  474. argPos += 2;
  475. continue;
  476. }
  477. ARG_CASE("-stdout", 0) {
  478. output = NULL;
  479. argPos += 1;
  480. continue;
  481. }
  482. ARG_CASE("-legacy", 0) {
  483. legacyMode = true;
  484. argPos += 1;
  485. continue;
  486. }
  487. ARG_CASE("-nopreprocess", 0) {
  488. geometryPreproc = false;
  489. argPos += 1;
  490. continue;
  491. }
  492. ARG_CASE("-preprocess", 0) {
  493. geometryPreproc = true;
  494. argPos += 1;
  495. continue;
  496. }
  497. ARG_CASE("-nooverlap", 0) {
  498. generatorConfig.overlapSupport = false;
  499. argPos += 1;
  500. continue;
  501. }
  502. ARG_CASE("-overlap", 0) {
  503. generatorConfig.overlapSupport = true;
  504. argPos += 1;
  505. continue;
  506. }
  507. ARG_CASE("-noscanline", 0) {
  508. scanlinePass = false;
  509. argPos += 1;
  510. continue;
  511. }
  512. ARG_CASE("-scanline", 0) {
  513. scanlinePass = true;
  514. argPos += 1;
  515. continue;
  516. }
  517. ARG_CASE("-fillrule", 1) {
  518. scanlinePass = true;
  519. if (!strcmp(argv[argPos+1], "nonzero")) fillRule = FILL_NONZERO;
  520. else if (!strcmp(argv[argPos+1], "evenodd") || !strcmp(argv[argPos+1], "odd")) fillRule = FILL_ODD;
  521. else if (!strcmp(argv[argPos+1], "positive")) fillRule = FILL_POSITIVE;
  522. else if (!strcmp(argv[argPos+1], "negative")) fillRule = FILL_NEGATIVE;
  523. else
  524. puts("Unknown fill rule specified.");
  525. argPos += 2;
  526. continue;
  527. }
  528. ARG_CASE("-format", 1) {
  529. if (!strcmp(argv[argPos+1], "auto")) format = AUTO;
  530. else if (!strcmp(argv[argPos+1], "png")) SET_FORMAT(PNG, "png");
  531. else if (!strcmp(argv[argPos+1], "bmp")) SET_FORMAT(BMP, "bmp");
  532. else if (!strcmp(argv[argPos+1], "tiff")) SET_FORMAT(TIFF, "tif");
  533. else if (!strcmp(argv[argPos+1], "text") || !strcmp(argv[argPos+1], "txt")) SET_FORMAT(TEXT, "txt");
  534. else if (!strcmp(argv[argPos+1], "textfloat") || !strcmp(argv[argPos+1], "txtfloat")) SET_FORMAT(TEXT_FLOAT, "txt");
  535. else if (!strcmp(argv[argPos+1], "bin") || !strcmp(argv[argPos+1], "binary")) SET_FORMAT(BINARY, "bin");
  536. else if (!strcmp(argv[argPos+1], "binfloat") || !strcmp(argv[argPos+1], "binfloatle")) SET_FORMAT(BINARY_FLOAT, "bin");
  537. else if (!strcmp(argv[argPos+1], "binfloatbe")) SET_FORMAT(BINARY_FLOAT_BE, "bin");
  538. else
  539. puts("Unknown format specified.");
  540. argPos += 2;
  541. continue;
  542. }
  543. ARG_CASE("-size", 2) {
  544. unsigned w, h;
  545. if (!parseUnsigned(w, argv[argPos+1]) || !parseUnsigned(h, argv[argPos+2]) || !w || !h)
  546. ABORT("Invalid size arguments. Use -size <width> <height> with two positive integers.");
  547. width = w, height = h;
  548. argPos += 3;
  549. continue;
  550. }
  551. ARG_CASE("-autoframe", 0) {
  552. autoFrame = true;
  553. argPos += 1;
  554. continue;
  555. }
  556. ARG_CASE("-range", 1) {
  557. double r;
  558. if (!parseDouble(r, argv[argPos+1]) || r < 0)
  559. ABORT("Invalid range argument. Use -range <range> with a positive real number.");
  560. rangeMode = RANGE_UNIT;
  561. range = r;
  562. argPos += 2;
  563. continue;
  564. }
  565. ARG_CASE("-pxrange", 1) {
  566. double r;
  567. if (!parseDouble(r, argv[argPos+1]) || r < 0)
  568. ABORT("Invalid range argument. Use -pxrange <range> with a positive real number.");
  569. rangeMode = RANGE_PX;
  570. pxRange = r;
  571. argPos += 2;
  572. continue;
  573. }
  574. ARG_CASE("-scale", 1) {
  575. double s;
  576. if (!parseDouble(s, argv[argPos+1]) || s <= 0)
  577. ABORT("Invalid scale argument. Use -scale <scale> with a positive real number.");
  578. scale = s;
  579. scaleSpecified = true;
  580. argPos += 2;
  581. continue;
  582. }
  583. ARG_CASE("-ascale", 2) {
  584. double sx, sy;
  585. if (!parseDouble(sx, argv[argPos+1]) || !parseDouble(sy, argv[argPos+2]) || sx <= 0 || sy <= 0)
  586. ABORT("Invalid scale arguments. Use -ascale <x> <y> with two positive real numbers.");
  587. scale.set(sx, sy);
  588. scaleSpecified = true;
  589. argPos += 3;
  590. continue;
  591. }
  592. ARG_CASE("-translate", 2) {
  593. double tx, ty;
  594. if (!parseDouble(tx, argv[argPos+1]) || !parseDouble(ty, argv[argPos+2]))
  595. ABORT("Invalid translate arguments. Use -translate <x> <y> with two real numbers.");
  596. translate.set(tx, ty);
  597. argPos += 3;
  598. continue;
  599. }
  600. ARG_CASE("-angle", 1) {
  601. double at;
  602. if (!parseAngle(at, argv[argPos+1]))
  603. 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.");
  604. angleThreshold = at;
  605. argPos += 2;
  606. continue;
  607. }
  608. ARG_CASE("-patchartifacts", 1) {
  609. if (!strcmp(argv[argPos+1], "none") || !strcmp(argv[argPos+1], "disabled")) artifactPatcherConfig.mode = ArtifactPatcherConfig::DISABLED;
  610. else if (!strcmp(argv[argPos+1], "distance") || !strcmp(argv[argPos+1], "indiscriminate") || !strcmp(argv[argPos+1], "all")) artifactPatcherConfig.mode = ArtifactPatcherConfig::INDISCRIMINATE;
  611. else if (!strcmp(argv[argPos+1], "balanced") || !strcmp(argv[argPos+1], "edgepriority") || !strcmp(argv[argPos+1], "auto")) artifactPatcherConfig.mode = ArtifactPatcherConfig::EDGE_PRIORITY;
  612. else if (!strcmp(argv[argPos+1], "edge") || !strcmp(argv[argPos+1], "edgeonly") || !strcmp(argv[argPos+1], "fast")) artifactPatcherConfig.mode = ArtifactPatcherConfig::EDGE_ONLY;
  613. else
  614. puts("Unknown artifact patcher strategy specified.");
  615. argPos += 2;
  616. continue;
  617. }
  618. ARG_CASE("-errorcorrection", 1) {
  619. double ect;
  620. if (!parseDouble(ect, argv[argPos+1]) && (ect >= 1 || ect == 0))
  621. ABORT("Invalid error correction threshold. Use -errorcorrection <threshold> with a real number greater than or equal to 1 or 0 to disable.");
  622. errorCorrectionThreshold = ect;
  623. argPos += 2;
  624. continue;
  625. }
  626. ARG_CASE("-coloringstrategy", 1) {
  627. if (!strcmp(argv[argPos+1], "simple")) edgeColoring = edgeColoringSimple;
  628. else if (!strcmp(argv[argPos+1], "inktrap")) edgeColoring = edgeColoringInkTrap;
  629. else
  630. puts("Unknown coloring strategy specified.");
  631. argPos += 2;
  632. continue;
  633. }
  634. ARG_CASE("-edgecolors", 1) {
  635. static const char *allowed = " ?,cmwyCMWY";
  636. for (int i = 0; argv[argPos+1][i]; ++i) {
  637. for (int j = 0; allowed[j]; ++j)
  638. if (argv[argPos+1][i] == allowed[j])
  639. goto EDGE_COLOR_VERIFIED;
  640. 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.");
  641. EDGE_COLOR_VERIFIED:;
  642. }
  643. edgeAssignment = argv[argPos+1];
  644. argPos += 2;
  645. continue;
  646. }
  647. ARG_CASE("-distanceshift", 1) {
  648. double ds;
  649. if (!parseDouble(ds, argv[argPos+1]))
  650. ABORT("Invalid distance shift. Use -distanceshift <shift> with a real value.");
  651. outputDistanceShift = (float) ds;
  652. argPos += 2;
  653. continue;
  654. }
  655. ARG_CASE("-exportshape", 1) {
  656. shapeExport = argv[argPos+1];
  657. argPos += 2;
  658. continue;
  659. }
  660. ARG_CASE("-testrender", 3) {
  661. unsigned w, h;
  662. if (!parseUnsigned(w, argv[argPos+2]) || !parseUnsigned(h, argv[argPos+3]) || !w || !h)
  663. ABORT("Invalid arguments for test render. Use -testrender <output.png> <width> <height>.");
  664. testRender = argv[argPos+1];
  665. testWidth = w, testHeight = h;
  666. argPos += 4;
  667. continue;
  668. }
  669. ARG_CASE("-testrendermulti", 3) {
  670. unsigned w, h;
  671. if (!parseUnsigned(w, argv[argPos+2]) || !parseUnsigned(h, argv[argPos+3]) || !w || !h)
  672. ABORT("Invalid arguments for test render. Use -testrendermulti <output.png> <width> <height>.");
  673. testRenderMulti = argv[argPos+1];
  674. testWidthM = w, testHeightM = h;
  675. argPos += 4;
  676. continue;
  677. }
  678. ARG_CASE("-yflip", 0) {
  679. yFlip = true;
  680. argPos += 1;
  681. continue;
  682. }
  683. ARG_CASE("-printmetrics", 0) {
  684. printMetrics = true;
  685. argPos += 1;
  686. continue;
  687. }
  688. ARG_CASE("-estimateerror", 0) {
  689. estimateError = true;
  690. argPos += 1;
  691. continue;
  692. }
  693. ARG_CASE("-keeporder", 0) {
  694. orientation = KEEP;
  695. argPos += 1;
  696. continue;
  697. }
  698. ARG_CASE("-reverseorder", 0) {
  699. orientation = REVERSE;
  700. argPos += 1;
  701. continue;
  702. }
  703. ARG_CASE("-guessorder", 0) {
  704. orientation = GUESS;
  705. argPos += 1;
  706. continue;
  707. }
  708. ARG_CASE("-seed", 1) {
  709. if (!parseUnsignedLL(coloringSeed, argv[argPos+1]))
  710. ABORT("Invalid seed. Use -seed <N> with N being a non-negative integer.");
  711. argPos += 2;
  712. continue;
  713. }
  714. ARG_CASE("-help", 0) {
  715. puts(helpText);
  716. return 0;
  717. }
  718. printf("Unknown setting or insufficient parameters: %s\n", arg);
  719. suggestHelp = true;
  720. ++argPos;
  721. }
  722. if (suggestHelp)
  723. printf("Use -help for more information.\n");
  724. // Load input
  725. Vector2 svgDims;
  726. double glyphAdvance = 0;
  727. if (!inputType || !input)
  728. ABORT("No input specified! Use either -svg <file.svg> or -font <file.ttf/otf> <character code>, or see -help.");
  729. if (mode == MULTI_AND_TRUE && (format == BMP || (format == AUTO && output && cmpExtension(output, ".bmp"))))
  730. ABORT("Incompatible image format. A BMP file cannot contain alpha channel, which is required in mtsdf mode.");
  731. Shape shape;
  732. switch (inputType) {
  733. case SVG: {
  734. if (!loadSvgShape(shape, input, svgPathIndex, &svgDims))
  735. ABORT("Failed to load shape from SVG file.");
  736. break;
  737. }
  738. case FONT: {
  739. if (!glyphIndex && !unicode)
  740. 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).");
  741. FreetypeHandle *ft = initializeFreetype();
  742. if (!ft) return -1;
  743. FontHandle *font = loadFont(ft, input);
  744. if (!font) {
  745. deinitializeFreetype(ft);
  746. ABORT("Failed to load font file.");
  747. }
  748. if (unicode)
  749. getGlyphIndex(glyphIndex, font, unicode);
  750. if (!loadGlyph(shape, font, glyphIndex, &glyphAdvance)) {
  751. destroyFont(font);
  752. deinitializeFreetype(ft);
  753. ABORT("Failed to load glyph from font file.");
  754. }
  755. destroyFont(font);
  756. deinitializeFreetype(ft);
  757. break;
  758. }
  759. case DESCRIPTION_ARG: {
  760. if (!readShapeDescription(input, shape, &skipColoring))
  761. ABORT("Parse error in shape description.");
  762. break;
  763. }
  764. case DESCRIPTION_STDIN: {
  765. if (!readShapeDescription(stdin, shape, &skipColoring))
  766. ABORT("Parse error in shape description.");
  767. break;
  768. }
  769. case DESCRIPTION_FILE: {
  770. FILE *file = fopen(input, "r");
  771. if (!file)
  772. ABORT("Failed to load shape description file.");
  773. if (!readShapeDescription(file, shape, &skipColoring))
  774. ABORT("Parse error in shape description.");
  775. fclose(file);
  776. break;
  777. }
  778. default:;
  779. }
  780. // Validate and normalize shape
  781. if (!shape.validate())
  782. ABORT("The geometry of the loaded shape is invalid.");
  783. if (geometryPreproc) {
  784. #ifdef MSDFGEN_USE_SKIA
  785. if (!resolveShapeGeometry(shape))
  786. puts("Shape geometry preprocessing failed, skipping.");
  787. #else
  788. ABORT("Shape geometry preprocessing (-preprocess) is not available in this version because the Skia library is not present.");
  789. #endif
  790. }
  791. shape.normalize();
  792. if (yFlip)
  793. shape.inverseYAxis = !shape.inverseYAxis;
  794. double avgScale = .5*(scale.x+scale.y);
  795. Shape::Bounds bounds = { };
  796. if (autoFrame || mode == METRICS || printMetrics || orientation == GUESS)
  797. bounds = shape.getBounds();
  798. // Auto-frame
  799. if (autoFrame) {
  800. double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t;
  801. Vector2 frame(width, height);
  802. double m = .5+(double) outputDistanceShift;
  803. if (!scaleSpecified) {
  804. if (rangeMode == RANGE_UNIT)
  805. l -= m*range, b -= m*range, r += m*range, t += m*range;
  806. else
  807. frame -= 2*m*pxRange;
  808. }
  809. if (l >= r || b >= t)
  810. l = 0, b = 0, r = 1, t = 1;
  811. if (frame.x <= 0 || frame.y <= 0)
  812. ABORT("Cannot fit the specified pixel range.");
  813. Vector2 dims(r-l, t-b);
  814. if (scaleSpecified)
  815. translate = .5*(frame/scale-dims)-Vector2(l, b);
  816. else {
  817. if (dims.x*frame.y < dims.y*frame.x) {
  818. translate.set(.5*(frame.x/frame.y*dims.y-dims.x)-l, -b);
  819. scale = avgScale = frame.y/dims.y;
  820. } else {
  821. translate.set(-l, .5*(frame.y/frame.x*dims.x-dims.y)-b);
  822. scale = avgScale = frame.x/dims.x;
  823. }
  824. }
  825. if (rangeMode == RANGE_PX && !scaleSpecified)
  826. translate += m*pxRange/scale;
  827. }
  828. if (rangeMode == RANGE_PX)
  829. range = pxRange/min(scale.x, scale.y);
  830. // Print metrics
  831. if (mode == METRICS || printMetrics) {
  832. FILE *out = stdout;
  833. if (mode == METRICS && outputSpecified)
  834. out = fopen(output, "w");
  835. if (!out)
  836. ABORT("Failed to write output file.");
  837. if (shape.inverseYAxis)
  838. fprintf(out, "inverseY = true\n");
  839. if (bounds.r >= bounds.l && bounds.t >= bounds.b)
  840. fprintf(out, "bounds = %.12g, %.12g, %.12g, %.12g\n", bounds.l, bounds.b, bounds.r, bounds.t);
  841. if (svgDims.x != 0 && svgDims.y != 0)
  842. fprintf(out, "dimensions = %.12g, %.12g\n", svgDims.x, svgDims.y);
  843. if (glyphAdvance != 0)
  844. fprintf(out, "advance = %.12g\n", glyphAdvance);
  845. if (autoFrame) {
  846. if (!scaleSpecified)
  847. fprintf(out, "scale = %.12g\n", avgScale);
  848. fprintf(out, "translate = %.12g, %.12g\n", translate.x, translate.y);
  849. }
  850. if (rangeMode == RANGE_PX)
  851. fprintf(out, "range = %.12g\n", range);
  852. if (mode == METRICS && outputSpecified)
  853. fclose(out);
  854. }
  855. // Compute output
  856. Projection projection(scale, translate);
  857. Bitmap<float, 1> sdf;
  858. Bitmap<float, 3> msdf;
  859. Bitmap<float, 4> mtsdf;
  860. artifactPatcherConfig.minImproveRatio = errorCorrectionThreshold; // TEMPORARY
  861. switch (mode) {
  862. case SINGLE: {
  863. sdf = Bitmap<float, 1>(width, height);
  864. if (legacyMode)
  865. generateSDF_legacy(sdf, shape, range, scale, translate);
  866. else
  867. generateSDF(sdf, shape, projection, range, generatorConfig);
  868. break;
  869. }
  870. case PSEUDO: {
  871. sdf = Bitmap<float, 1>(width, height);
  872. if (legacyMode)
  873. generatePseudoSDF_legacy(sdf, shape, range, scale, translate);
  874. else
  875. generatePseudoSDF(sdf, shape, projection, range, generatorConfig);
  876. break;
  877. }
  878. case MULTI: {
  879. if (!skipColoring)
  880. edgeColoring(shape, angleThreshold, coloringSeed);
  881. if (edgeAssignment)
  882. parseColoring(shape, edgeAssignment);
  883. msdf = Bitmap<float, 3>(width, height);
  884. if (legacyMode)
  885. generateMSDF_legacy(msdf, shape, range, scale, translate, scanlinePass ? 0 : errorCorrectionThreshold);
  886. else
  887. generateMSDF(msdf, shape, projection, range, generatorConfig, artifactPatcherConfig);
  888. break;
  889. }
  890. case MULTI_AND_TRUE: {
  891. if (!skipColoring)
  892. edgeColoring(shape, angleThreshold, coloringSeed);
  893. if (edgeAssignment)
  894. parseColoring(shape, edgeAssignment);
  895. mtsdf = Bitmap<float, 4>(width, height);
  896. if (legacyMode)
  897. generateMTSDF_legacy(mtsdf, shape, range, scale, translate, scanlinePass ? 0 : errorCorrectionThreshold);
  898. else
  899. generateMTSDF(mtsdf, shape, projection, range, generatorConfig, artifactPatcherConfig);
  900. break;
  901. }
  902. default:;
  903. }
  904. if (orientation == GUESS) {
  905. // Get sign of signed distance outside bounds
  906. Point2 p(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1);
  907. double distance = SimpleTrueShapeDistanceFinder::oneShotDistance(shape, p);
  908. orientation = distance <= 0 ? KEEP : REVERSE;
  909. }
  910. if (orientation == REVERSE) {
  911. switch (mode) {
  912. case SINGLE:
  913. case PSEUDO:
  914. invertColor<1>(sdf);
  915. break;
  916. case MULTI:
  917. invertColor<3>(msdf);
  918. break;
  919. case MULTI_AND_TRUE:
  920. invertColor<4>(mtsdf);
  921. break;
  922. default:;
  923. }
  924. }
  925. if (scanlinePass) {
  926. switch (mode) {
  927. case SINGLE:
  928. case PSEUDO:
  929. distanceSignCorrection(sdf, shape, projection, fillRule);
  930. break;
  931. case MULTI:
  932. distanceSignCorrection(msdf, shape, projection, fillRule);
  933. if (errorCorrectionThreshold > 0)
  934. msdfErrorCorrection(msdf, errorCorrectionThreshold/(scale*range));
  935. break;
  936. case MULTI_AND_TRUE:
  937. distanceSignCorrection(mtsdf, shape, projection, fillRule);
  938. if (errorCorrectionThreshold > 0)
  939. msdfErrorCorrection(mtsdf, errorCorrectionThreshold/(scale*range));
  940. break;
  941. default:;
  942. }
  943. }
  944. if (outputDistanceShift) {
  945. float *pixel = NULL, *pixelsEnd = NULL;
  946. switch (mode) {
  947. case SINGLE:
  948. case PSEUDO:
  949. pixel = (float *) sdf;
  950. pixelsEnd = pixel+1*sdf.width()*sdf.height();
  951. break;
  952. case MULTI:
  953. pixel = (float *) msdf;
  954. pixelsEnd = pixel+3*msdf.width()*msdf.height();
  955. break;
  956. case MULTI_AND_TRUE:
  957. pixel = (float *) mtsdf;
  958. pixelsEnd = pixel+4*mtsdf.width()*mtsdf.height();
  959. break;
  960. default:;
  961. }
  962. while (pixel < pixelsEnd)
  963. *pixel++ += outputDistanceShift;
  964. }
  965. // Save output
  966. if (shapeExport) {
  967. FILE *file = fopen(shapeExport, "w");
  968. if (file) {
  969. writeShapeDescription(file, shape);
  970. fclose(file);
  971. } else
  972. puts("Failed to write shape export file.");
  973. }
  974. const char *error = NULL;
  975. switch (mode) {
  976. case SINGLE:
  977. case PSEUDO:
  978. error = writeOutput<1>(sdf, output, format);
  979. if (error)
  980. ABORT(error);
  981. if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError))
  982. simulate8bit(sdf);
  983. if (estimateError) {
  984. double sdfError = estimateSDFError(sdf, shape, projection, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
  985. printf("SDF error ~ %e\n", sdfError);
  986. }
  987. if (testRenderMulti) {
  988. Bitmap<float, 3> render(testWidthM, testHeightM);
  989. renderSDF(render, sdf, avgScale*range, .5f+outputDistanceShift);
  990. if (!savePng(render, testRenderMulti))
  991. puts("Failed to write test render file.");
  992. }
  993. if (testRender) {
  994. Bitmap<float, 1> render(testWidth, testHeight);
  995. renderSDF(render, sdf, avgScale*range, .5f+outputDistanceShift);
  996. if (!savePng(render, testRender))
  997. puts("Failed to write test render file.");
  998. }
  999. break;
  1000. case MULTI:
  1001. error = writeOutput<3>(msdf, output, format);
  1002. if (error)
  1003. ABORT(error);
  1004. if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError))
  1005. simulate8bit(msdf);
  1006. if (estimateError) {
  1007. double sdfError = estimateSDFError(msdf, shape, projection, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
  1008. printf("SDF error ~ %e\n", sdfError);
  1009. }
  1010. if (testRenderMulti) {
  1011. Bitmap<float, 3> render(testWidthM, testHeightM);
  1012. renderSDF(render, msdf, avgScale*range, .5f+outputDistanceShift);
  1013. if (!savePng(render, testRenderMulti))
  1014. puts("Failed to write test render file.");
  1015. }
  1016. if (testRender) {
  1017. Bitmap<float, 1> render(testWidth, testHeight);
  1018. renderSDF(render, msdf, avgScale*range, .5f+outputDistanceShift);
  1019. if (!savePng(render, testRender))
  1020. ABORT("Failed to write test render file.");
  1021. }
  1022. break;
  1023. case MULTI_AND_TRUE:
  1024. error = writeOutput<4>(mtsdf, output, format);
  1025. if (error)
  1026. ABORT(error);
  1027. if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError))
  1028. simulate8bit(mtsdf);
  1029. if (estimateError) {
  1030. double sdfError = estimateSDFError(mtsdf, shape, projection, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
  1031. printf("SDF error ~ %e\n", sdfError);
  1032. }
  1033. if (testRenderMulti) {
  1034. Bitmap<float, 4> render(testWidthM, testHeightM);
  1035. renderSDF(render, mtsdf, avgScale*range, .5f+outputDistanceShift);
  1036. if (!savePng(render, testRenderMulti))
  1037. puts("Failed to write test render file.");
  1038. }
  1039. if (testRender) {
  1040. Bitmap<float, 1> render(testWidth, testHeight);
  1041. renderSDF(render, mtsdf, avgScale*range, .5f+outputDistanceShift);
  1042. if (!savePng(render, testRender))
  1043. ABORT("Failed to write test render file.");
  1044. }
  1045. break;
  1046. default:;
  1047. }
  1048. return 0;
  1049. }
  1050. #endif