1002 lines
37KB

  1. /*
  2. FIGlet.js (a FIGDriver for FIGlet fonts)
  3. By Patrick Gillespie (patorjk@gmail.com)
  4. Originally Written For: http://patorjk.com/software/taag/
  5. License: MIT (with this header staying intact)
  6. This JavaScript code aims to fully implement the FIGlet spec.
  7. Full FIGlet spec: http://patorjk.com/software/taag/docs/figfont.txt
  8. FIGlet fonts are actually kind of complex, which is why you will see
  9. a lot of code about parsing and interpreting rules. The actual generation
  10. code is pretty simple and is done near the bottom of the code.
  11. */
  12. "use strict";
  13. var figlet = figlet || (function() {
  14. // ---------------------------------------------------------------------
  15. // Private static variables
  16. var FULL_WIDTH = 0,
  17. FITTING = 1,
  18. SMUSHING = 2,
  19. CONTROLLED_SMUSHING = 3;
  20. // ---------------------------------------------------------------------
  21. // Variable that will hold information about the fonts
  22. var figFonts = {}; // What stores all of the FIGlet font data
  23. var figDefaults = {
  24. font: 'Standard',
  25. fontPath: './fonts'
  26. };
  27. // ---------------------------------------------------------------------
  28. // Private static methods
  29. /*
  30. This method takes in the oldLayout and newLayout data from the FIGfont header file and returns
  31. the layout information.
  32. */
  33. function getSmushingRules(oldLayout, newLayout) {
  34. var rules = {};
  35. var val, index, len, code;
  36. var codes = [[16384,"vLayout",SMUSHING], [8192,"vLayout",FITTING], [4096, "vRule5", true], [2048, "vRule4", true],
  37. [1024, "vRule3", true], [512, "vRule2", true], [256, "vRule1", true], [128, "hLayout", SMUSHING],
  38. [64, "hLayout", FITTING], [32, "hRule6", true], [16, "hRule5", true], [8, "hRule4", true], [4, "hRule3", true],
  39. [2, "hRule2", true], [1, "hRule1", true]];
  40. val = (newLayout !== null) ? newLayout : oldLayout;
  41. index = 0;
  42. len = codes.length;
  43. while ( index < len ) {
  44. code = codes[index];
  45. if (val >= code[0]) {
  46. val = val - code[0];
  47. rules[code[1]] = (typeof rules[code[1]] === "undefined") ? code[2] : rules[code[1]];
  48. } else if (code[1] !== "vLayout" && code[1] !== "hLayout") {
  49. rules[code[1]] = false;
  50. }
  51. index++;
  52. }
  53. if (typeof rules["hLayout"] === "undefined") {
  54. if (oldLayout === 0) {
  55. rules["hLayout"] = FITTING;
  56. } else if (oldLayout === -1) {
  57. rules["hLayout"] = FULL_WIDTH;
  58. } else {
  59. if (rules["hRule1"] || rules["hRule2"] || rules["hRule3"] || rules["hRule4"] ||rules["hRule5"] || rules["hRule6"] ) {
  60. rules["hLayout"] = CONTROLLED_SMUSHING;
  61. } else {
  62. rules["hLayout"] = SMUSHING;
  63. }
  64. }
  65. } else if (rules["hLayout"] === SMUSHING) {
  66. if (rules["hRule1"] || rules["hRule2"] || rules["hRule3"] || rules["hRule4"] ||rules["hRule5"] || rules["hRule6"] ) {
  67. rules["hLayout"] = CONTROLLED_SMUSHING;
  68. }
  69. }
  70. if (typeof rules["vLayout"] === "undefined") {
  71. if (rules["vRule1"] || rules["vRule2"] || rules["vRule3"] || rules["vRule4"] ||rules["vRule5"] ) {
  72. rules["vLayout"] = CONTROLLED_SMUSHING;
  73. } else {
  74. rules["vLayout"] = FULL_WIDTH;
  75. }
  76. } else if (rules["vLayout"] === SMUSHING) {
  77. if (rules["vRule1"] || rules["vRule2"] || rules["vRule3"] || rules["vRule4"] ||rules["vRule5"] ) {
  78. rules["vLayout"] = CONTROLLED_SMUSHING;
  79. }
  80. }
  81. return rules;
  82. }
  83. /* The [vh]Rule[1-6]_Smush functions return the smushed character OR false if the two characters can't be smushed */
  84. /*
  85. Rule 1: EQUAL CHARACTER SMUSHING (code value 1)
  86. Two sub-characters are smushed into a single sub-character
  87. if they are the same. This rule does not smush
  88. hardblanks. (See rule 6 on hardblanks below)
  89. */
  90. function hRule1_Smush(ch1, ch2, hardBlank) {
  91. if (ch1 === ch2 && ch1 !== hardBlank) {return ch1;}
  92. return false;
  93. }
  94. /*
  95. Rule 2: UNDERSCORE SMUSHING (code value 2)
  96. An underscore ("_") will be replaced by any of: "|", "/",
  97. "\", "[", "]", "{", "}", "(", ")", "<" or ">".
  98. */
  99. function hRule2_Smush(ch1, ch2) {
  100. var rule2Str = "|/\\[]{}()<>";
  101. if (ch1 === "_") {
  102. if (rule2Str.indexOf(ch2) !== -1) {return ch2;}
  103. } else if (ch2 === "_") {
  104. if (rule2Str.indexOf(ch1) !== -1) {return ch1;}
  105. }
  106. return false;
  107. }
  108. /*
  109. Rule 3: HIERARCHY SMUSHING (code value 4)
  110. A hierarchy of six classes is used: "|", "/\", "[]", "{}",
  111. "()", and "<>". When two smushing sub-characters are
  112. from different classes, the one from the latter class
  113. will be used.
  114. */
  115. function hRule3_Smush(ch1, ch2) {
  116. var rule3Classes = "| /\\ [] {} () <>";
  117. var r3_pos1 = rule3Classes.indexOf(ch1);
  118. var r3_pos2 = rule3Classes.indexOf(ch2);
  119. if (r3_pos1 !== -1 && r3_pos2 !== -1) {
  120. if (r3_pos1 !== r3_pos2 && Math.abs(r3_pos1-r3_pos2) !== 1) {
  121. return rule3Classes.substr(Math.max(r3_pos1,r3_pos2), 1);
  122. }
  123. }
  124. return false;
  125. }
  126. /*
  127. Rule 4: OPPOSITE PAIR SMUSHING (code value 8)
  128. Smushes opposing brackets ("[]" or "]["), braces ("{}" or
  129. "}{") and parentheses ("()" or ")(") together, replacing
  130. any such pair with a vertical bar ("|").
  131. */
  132. function hRule4_Smush(ch1, ch2) {
  133. var rule4Str = "[] {} ()";
  134. var r4_pos1 = rule4Str.indexOf(ch1);
  135. var r4_pos2 = rule4Str.indexOf(ch2);
  136. if (r4_pos1 !== -1 && r4_pos2 !== -1) {
  137. if (Math.abs(r4_pos1-r4_pos2) <= 1) {
  138. return "|";
  139. }
  140. }
  141. return false;
  142. }
  143. /*
  144. Rule 5: BIG X SMUSHING (code value 16)
  145. Smushes "/\" into "|", "\/" into "Y", and "><" into "X".
  146. Note that "<>" is not smushed in any way by this rule.
  147. The name "BIG X" is historical; originally all three pairs
  148. were smushed into "X".
  149. */
  150. function hRule5_Smush(ch1, ch2) {
  151. var rule5Str = "/\\ \\/ ><";
  152. var rule5Hash = {"0": "|", "3": "Y", "6": "X"};
  153. var r5_pos1 = rule5Str.indexOf(ch1);
  154. var r5_pos2 = rule5Str.indexOf(ch2);
  155. if (r5_pos1 !== -1 && r5_pos2 !== -1) {
  156. if ((r5_pos2-r5_pos1) === 1) {
  157. return rule5Hash[r5_pos1];
  158. }
  159. }
  160. return false;
  161. }
  162. /*
  163. Rule 6: HARDBLANK SMUSHING (code value 32)
  164. Smushes two hardblanks together, replacing them with a
  165. single hardblank. (See "Hardblanks" below.)
  166. */
  167. function hRule6_Smush(ch1, ch2, hardBlank) {
  168. if (ch1 === hardBlank && ch2 === hardBlank) {
  169. return hardBlank;
  170. }
  171. return false;
  172. }
  173. /*
  174. Rule 1: EQUAL CHARACTER SMUSHING (code value 256)
  175. Same as horizontal smushing rule 1.
  176. */
  177. function vRule1_Smush(ch1, ch2) {
  178. if (ch1 === ch2) {return ch1;}
  179. return false;
  180. }
  181. /*
  182. Rule 2: UNDERSCORE SMUSHING (code value 512)
  183. Same as horizontal smushing rule 2.
  184. */
  185. function vRule2_Smush(ch1, ch2) {
  186. var rule2Str = "|/\\[]{}()<>";
  187. if (ch1 === "_") {
  188. if (rule2Str.indexOf(ch2) !== -1) {return ch2;}
  189. } else if (ch2 === "_") {
  190. if (rule2Str.indexOf(ch1) !== -1) {return ch1;}
  191. }
  192. return false;
  193. }
  194. /*
  195. Rule 3: HIERARCHY SMUSHING (code value 1024)
  196. Same as horizontal smushing rule 3.
  197. */
  198. function vRule3_Smush(ch1, ch2) {
  199. var rule3Classes = "| /\\ [] {} () <>";
  200. var r3_pos1 = rule3Classes.indexOf(ch1);
  201. var r3_pos2 = rule3Classes.indexOf(ch2);
  202. if (r3_pos1 !== -1 && r3_pos2 !== -1) {
  203. if (r3_pos1 !== r3_pos2 && Math.abs(r3_pos1-r3_pos2) !== 1) {
  204. return rule3Classes.substr(Math.max(r3_pos1,r3_pos2), 1);
  205. }
  206. }
  207. return false;
  208. }
  209. /*
  210. Rule 4: HORIZONTAL LINE SMUSHING (code value 2048)
  211. Smushes stacked pairs of "-" and "_", replacing them with
  212. a single "=" sub-character. It does not matter which is
  213. found above the other. Note that vertical smushing rule 1
  214. will smush IDENTICAL pairs of horizontal lines, while this
  215. rule smushes horizontal lines consisting of DIFFERENT
  216. sub-characters.
  217. */
  218. function vRule4_Smush(ch1, ch2) {
  219. if ( (ch1 === "-" && ch2 === "_") || (ch1 === "_" && ch2 === "-") ) {
  220. return "=";
  221. }
  222. return false;
  223. }
  224. /*
  225. Rule 5: VERTICAL LINE SUPERSMUSHING (code value 4096)
  226. This one rule is different from all others, in that it
  227. "supersmushes" vertical lines consisting of several
  228. vertical bars ("|"). This creates the illusion that
  229. FIGcharacters have slid vertically against each other.
  230. Supersmushing continues until any sub-characters other
  231. than "|" would have to be smushed. Supersmushing can
  232. produce impressive results, but it is seldom possible,
  233. since other sub-characters would usually have to be
  234. considered for smushing as soon as any such stacked
  235. vertical lines are encountered.
  236. */
  237. function vRule5_Smush(ch1, ch2) {
  238. if ( ch1 === "|" && ch2 === "|" ) {
  239. return "|";
  240. }
  241. return false;
  242. }
  243. /*
  244. Universal smushing simply overrides the sub-character from the
  245. earlier FIGcharacter with the sub-character from the later
  246. FIGcharacter. This produces an "overlapping" effect with some
  247. FIGfonts, wherin the latter FIGcharacter may appear to be "in
  248. front".
  249. */
  250. function uni_Smush(ch1, ch2, hardBlank) {
  251. if (ch2 === " " || ch2 === "") {
  252. return ch1;
  253. } else if (ch2 === hardBlank && ch1 !== " ") {
  254. return ch1;
  255. } else {
  256. return ch2;
  257. }
  258. }
  259. // --------------------------------------------------------------------------
  260. // main vertical smush routines (excluding rules)
  261. /*
  262. txt1 - A line of text
  263. txt2 - A line of text
  264. opts - FIGlet options array
  265. About: Takes in two lines of text and returns one of the following:
  266. "valid" - These lines can be smushed together given the current smushing rules
  267. "end" - The lines can be smushed, but we're at a stopping point
  268. "invalid" - The two lines cannot be smushed together
  269. */
  270. function canVerticalSmush(txt1, txt2, opts) {
  271. if (opts.fittingRules.vLayout === FULL_WIDTH) {return "invalid";}
  272. var ii, len = Math.min(txt1.length, txt2.length);
  273. var ch1, ch2, endSmush = false, validSmush;
  274. if (len===0) {return "invalid";}
  275. for (ii = 0; ii < len; ii++) {
  276. ch1 = txt1.substr(ii,1);
  277. ch2 = txt2.substr(ii,1);
  278. if (ch1 !== " " && ch2 !== " ") {
  279. if (opts.fittingRules.vLayout === FITTING) {
  280. return "invalid";
  281. } else if (opts.fittingRules.vLayout === SMUSHING) {
  282. return "end";
  283. } else {
  284. if (vRule5_Smush(ch1,ch2)) {endSmush = endSmush || false; continue;} // rule 5 allow for "super" smushing, but only if we're not already ending this smush
  285. validSmush = false;
  286. validSmush = (opts.fittingRules.vRule1) ? vRule1_Smush(ch1,ch2) : validSmush;
  287. validSmush = (!validSmush && opts.fittingRules.vRule2) ? vRule2_Smush(ch1,ch2) : validSmush;
  288. validSmush = (!validSmush && opts.fittingRules.vRule3) ? vRule3_Smush(ch1,ch2) : validSmush;
  289. validSmush = (!validSmush && opts.fittingRules.vRule4) ? vRule4_Smush(ch1,ch2) : validSmush;
  290. endSmush = true;
  291. if (!validSmush) {return "invalid";}
  292. }
  293. }
  294. }
  295. if (endSmush) {
  296. return "end";
  297. } else {
  298. return "valid";
  299. }
  300. }
  301. function getVerticalSmushDist(lines1, lines2, opts) {
  302. var maxDist = lines1.length;
  303. var len1 = lines1.length;
  304. var len2 = lines2.length;
  305. var subLines1, subLines2, slen;
  306. var curDist = 1;
  307. var ii, ret, result;
  308. while (curDist <= maxDist) {
  309. subLines1 = lines1.slice(Math.max(0,len1-curDist), len1);
  310. subLines2 = lines2.slice(0, Math.min(maxDist, curDist));
  311. slen = subLines2.length;//TODO:check this
  312. result = "";
  313. for (ii = 0; ii < slen; ii++) {
  314. ret = canVerticalSmush(subLines1[ii], subLines2[ii], opts);
  315. if (ret === "end") {
  316. result = ret;
  317. } else if (ret === "invalid") {
  318. result = ret;
  319. break;
  320. } else {
  321. if (result === "") {
  322. result = "valid";
  323. }
  324. }
  325. }
  326. if (result === "invalid") {curDist--;break;}
  327. if (result === "end") {break;}
  328. if (result === "valid") {curDist++;}
  329. }
  330. return Math.min(maxDist,curDist);
  331. }
  332. function verticallySmushLines(line1, line2, opts) {
  333. var ii, len = Math.min(line1.length, line2.length);
  334. var ch1, ch2, result = "", validSmush;
  335. for (ii = 0; ii < len; ii++) {
  336. ch1 = line1.substr(ii,1);
  337. ch2 = line2.substr(ii,1);
  338. if (ch1 !== " " && ch2 !== " ") {
  339. if (opts.fittingRules.vLayout === FITTING) {
  340. result += uni_Smush(ch1,ch2);
  341. } else if (opts.fittingRules.vLayout === SMUSHING) {
  342. result += uni_Smush(ch1,ch2);
  343. } else {
  344. validSmush = (opts.fittingRules.vRule5) ? vRule5_Smush(ch1,ch2) : validSmush;
  345. validSmush = (!validSmush && opts.fittingRules.vRule1) ? vRule1_Smush(ch1,ch2) : validSmush;
  346. validSmush = (!validSmush && opts.fittingRules.vRule2) ? vRule2_Smush(ch1,ch2) : validSmush;
  347. validSmush = (!validSmush && opts.fittingRules.vRule3) ? vRule3_Smush(ch1,ch2) : validSmush;
  348. validSmush = (!validSmush && opts.fittingRules.vRule4) ? vRule4_Smush(ch1,ch2) : validSmush;
  349. result += validSmush;
  350. }
  351. } else {
  352. result += uni_Smush(ch1,ch2);
  353. }
  354. }
  355. return result;
  356. }
  357. function verticalSmush(lines1, lines2, overlap, opts) {
  358. var len1 = lines1.length;
  359. var len2 = lines2.length;
  360. var piece1 = lines1.slice(0, Math.max(0,len1-overlap));
  361. var piece2_1 = lines1.slice(Math.max(0,len1-overlap), len1);
  362. var piece2_2 = lines2.slice(0, Math.min(overlap, len2));
  363. var ii, len, line, piece2 = [], piece3, result = [];
  364. len = piece2_1.length;
  365. for (ii = 0; ii < len; ii++) {
  366. if (ii >= len2) {
  367. line = piece2_1[ii];
  368. } else {
  369. line = verticallySmushLines(piece2_1[ii], piece2_2[ii], opts);
  370. }
  371. piece2.push(line);
  372. }
  373. piece3 = lines2.slice(Math.min(overlap,len2), len2);
  374. return result.concat(piece1,piece2,piece3);
  375. }
  376. function padLines(lines, numSpaces) {
  377. var ii, len = lines.length, padding = "";
  378. for (ii = 0; ii < numSpaces; ii++) {
  379. padding += " ";
  380. }
  381. for (ii = 0; ii < len; ii++) {
  382. lines[ii] += padding;
  383. }
  384. }
  385. function smushVerticalFigLines(output, lines, opts) {
  386. var len1 = output[0].length;
  387. var len2 = lines[0].length;
  388. var overlap;
  389. if (len1 > len2) {
  390. padLines(lines, len1-len2);
  391. } else if (len2 > len1) {
  392. padLines(output, len2-len1);
  393. }
  394. overlap = getVerticalSmushDist(output, lines, opts);
  395. return verticalSmush(output, lines, overlap,opts);
  396. }
  397. // -------------------------------------------------------------------------
  398. // Main horizontal smush routines (excluding rules)
  399. function getHorizontalSmushLength(txt1, txt2, opts) {
  400. if (opts.fittingRules.hLayout === FULL_WIDTH) {return 0;}
  401. var ii, len1 = txt1.length, len2 = txt2.length;
  402. var maxDist = len1;
  403. var curDist = 1;
  404. var breakAfter = false;
  405. var validSmush = false;
  406. var seg1, seg2, ch1, ch2;
  407. if (len1 === 0) {return 0;}
  408. distCal: while (curDist <= maxDist) {
  409. seg1 = txt1.substr(len1-curDist,curDist);
  410. seg2 = txt2.substr(0,Math.min(curDist,len2));
  411. for (ii = 0; ii < Math.min(curDist,len2); ii++) {
  412. ch1 = seg1.substr(ii,1);
  413. ch2 = seg2.substr(ii,1);
  414. if (ch1 !== " " && ch2 !== " " ) {
  415. if (opts.fittingRules.hLayout === FITTING) {
  416. curDist = curDist - 1;
  417. break distCal;
  418. } else if (opts.fittingRules.hLayout === SMUSHING) {
  419. if (ch1 === opts.hardBlank || ch2 === opts.hardBlank) {
  420. curDist = curDist - 1; // universal smushing does not smush hardblanks
  421. }
  422. break distCal;
  423. } else {
  424. breakAfter = true; // we know we need to break, but we need to check if our smushing rules will allow us to smush the overlapped characters
  425. validSmush = false; // the below checks will let us know if we can smush these characters
  426. validSmush = (opts.fittingRules.hRule1) ? hRule1_Smush(ch1,ch2,opts.hardBlank) : validSmush;
  427. validSmush = (!validSmush && opts.fittingRules.hRule2) ? hRule2_Smush(ch1,ch2,opts.hardBlank) : validSmush;
  428. validSmush = (!validSmush && opts.fittingRules.hRule3) ? hRule3_Smush(ch1,ch2,opts.hardBlank) : validSmush;
  429. validSmush = (!validSmush && opts.fittingRules.hRule4) ? hRule4_Smush(ch1,ch2,opts.hardBlank) : validSmush;
  430. validSmush = (!validSmush && opts.fittingRules.hRule5) ? hRule5_Smush(ch1,ch2,opts.hardBlank) : validSmush;
  431. validSmush = (!validSmush && opts.fittingRules.hRule6) ? hRule6_Smush(ch1,ch2,opts.hardBlank) : validSmush;
  432. if (!validSmush) {
  433. curDist = curDist - 1;
  434. break distCal;
  435. }
  436. }
  437. }
  438. }
  439. if (breakAfter) {break;}
  440. curDist++;
  441. }
  442. return Math.min(maxDist,curDist);
  443. }
  444. function horizontalSmush(textBlock1, textBlock2, overlap, opts) {
  445. var ii, jj, ch, outputFig = [],
  446. overlapStart,piece1,piece2,piece3,len1,len2,txt1,txt2;
  447. for (ii = 0; ii < opts.height; ii++) {
  448. txt1 = textBlock1[ii];
  449. txt2 = textBlock2[ii];
  450. len1 = txt1.length;
  451. len2 = txt2.length;
  452. overlapStart = len1-overlap;
  453. piece1 = txt1.substr(0,Math.max(0,overlapStart));
  454. piece2 = "";
  455. // determine overlap piece
  456. var seg1 = txt1.substr(Math.max(0,len1-overlap),overlap);
  457. var seg2 = txt2.substr(0,Math.min(overlap,len2));
  458. for (jj = 0; jj < overlap; jj++) {
  459. var ch1 = (jj < len1) ? seg1.substr(jj,1) : " ";
  460. var ch2 = (jj < len2) ? seg2.substr(jj,1) : " ";
  461. if (ch1 !== " " && ch2 !== " ") {
  462. if (opts.fittingRules.hLayout === FITTING) {
  463. piece2 += uni_Smush(ch1, ch2, opts.hardBlank);
  464. } else if (opts.fittingRules.hLayout === SMUSHING) {
  465. piece2 += uni_Smush(ch1, ch2, opts.hardBlank);
  466. } else {
  467. // Controlled Smushing
  468. var nextCh = "";
  469. nextCh = (!nextCh && opts.fittingRules.hRule1) ? hRule1_Smush(ch1,ch2,opts.hardBlank) : nextCh;
  470. nextCh = (!nextCh && opts.fittingRules.hRule2) ? hRule2_Smush(ch1,ch2,opts.hardBlank) : nextCh;
  471. nextCh = (!nextCh && opts.fittingRules.hRule3) ? hRule3_Smush(ch1,ch2,opts.hardBlank) : nextCh;
  472. nextCh = (!nextCh && opts.fittingRules.hRule4) ? hRule4_Smush(ch1,ch2,opts.hardBlank) : nextCh;
  473. nextCh = (!nextCh && opts.fittingRules.hRule5) ? hRule5_Smush(ch1,ch2,opts.hardBlank) : nextCh;
  474. nextCh = (!nextCh && opts.fittingRules.hRule6) ? hRule6_Smush(ch1,ch2,opts.hardBlank) : nextCh;
  475. nextCh = nextCh || uni_Smush(ch1, ch2, opts.hardBlank);
  476. piece2 += nextCh;
  477. }
  478. } else {
  479. piece2 += uni_Smush(ch1, ch2, opts.hardBlank);
  480. }
  481. }
  482. if (overlap >= len2) {
  483. piece3 = "";
  484. } else {
  485. piece3 = txt2.substr(overlap,Math.max(0,len2-overlap));
  486. }
  487. outputFig[ii] = piece1 + piece2 + piece3;
  488. }
  489. return outputFig;
  490. }
  491. function generateFigTextLine(txt, figChars, opts) {
  492. var charIndex, figChar, overlap = 0, row, outputFigText = [], len=opts.height;
  493. for (row = 0; row < len; row++) {
  494. outputFigText[row] = "";
  495. }
  496. if (opts.printDirection === 1) {
  497. txt = txt.split('').reverse().join('');
  498. }
  499. len=txt.length;
  500. for (charIndex = 0; charIndex < len; charIndex++) {
  501. figChar = figChars[txt.substr(charIndex,1).charCodeAt(0)];
  502. if (figChar) {
  503. if (opts.fittingRules.hLayout !== FULL_WIDTH) {
  504. overlap = 10000;// a value too high to be the overlap
  505. for (row = 0; row < opts.height; row++) {
  506. overlap = Math.min(overlap, getHorizontalSmushLength(outputFigText[row], figChar[row], opts));
  507. }
  508. overlap = (overlap === 10000) ? 0 : overlap;
  509. }
  510. outputFigText = horizontalSmush(outputFigText, figChar, overlap, opts);
  511. }
  512. }
  513. // remove hardblanks
  514. if (opts.showHardBlanks !== true) {
  515. len = outputFigText.length;
  516. for (row = 0; row < len; row++) {
  517. outputFigText[row] = outputFigText[row].replace(new RegExp("\\"+opts.hardBlank,"g")," ");
  518. }
  519. }
  520. return outputFigText;
  521. }
  522. // -------------------------------------------------------------------------
  523. // Parsing and Generation methods
  524. var getHorizontalFittingRules = function(layout, options) {
  525. var props = ["hLayout", "hRule1","hRule2","hRule3","hRule4","hRule5","hRule6"],
  526. params = {}, prop, ii;
  527. if (layout === "default") {
  528. for (ii = 0; ii < props.length; ii++) {
  529. params[props[ii]] = options.fittingRules[props[ii]];
  530. }
  531. } else if (layout === "full") {
  532. params = {"hLayout": FULL_WIDTH,"hRule1":false,"hRule2":false,"hRule3":false,"hRule4":false,"hRule5":false,"hRule6":false};
  533. } else if (layout === "fitted") {
  534. params = {"hLayout": FITTING,"hRule1":false,"hRule2":false,"hRule3":false,"hRule4":false,"hRule5":false,"hRule6":false};
  535. } else if (layout === "controlled smushing") {
  536. params = {"hLayout": CONTROLLED_SMUSHING,"hRule1":true,"hRule2":true,"hRule3":true,"hRule4":true,"hRule5":true,"hRule6":true};
  537. } else if (layout === "universal smushing") {
  538. params = {"hLayout": SMUSHING,"hRule1":false,"hRule2":false,"hRule3":false,"hRule4":false,"hRule5":false,"hRule6":false};
  539. } else {
  540. return;
  541. }
  542. return params;
  543. };
  544. var getVerticalFittingRules = function(layout, options) {
  545. var props = ["vLayout", "vRule1","vRule2","vRule3","vRule4","vRule5"],
  546. params = {}, prop, ii;
  547. if (layout === "default") {
  548. for (ii = 0; ii < props.length; ii++) {
  549. params[props[ii]] = options.fittingRules[props[ii]];
  550. }
  551. } else if (layout === "full") {
  552. params = {"vLayout": FULL_WIDTH,"vRule1":false,"vRule2":false,"vRule3":false,"vRule4":false,"vRule5":false};
  553. } else if (layout === "fitted") {
  554. params = {"vLayout": FITTING,"vRule1":false,"vRule2":false,"vRule3":false,"vRule4":false,"vRule5":false};
  555. } else if (layout === "controlled smushing") {
  556. params = {"vLayout": CONTROLLED_SMUSHING,"vRule1":true,"vRule2":true,"vRule3":true,"vRule4":true,"vRule5":true};
  557. } else if (layout === "universal smushing") {
  558. params = {"vLayout": SMUSHING,"vRule1":false,"vRule2":false,"vRule3":false,"vRule4":false,"vRule5":false};
  559. } else {
  560. return;
  561. }
  562. return params;
  563. };
  564. /*
  565. Generates the ASCII Art
  566. - fontName: Font to use
  567. - option: Options to override the defaults
  568. - txt: The text to make into ASCII Art
  569. */
  570. var generateText = function(fontName, options, txt) {
  571. txt = txt.replace(/\r\n/g,"\n").replace(/\r/g,"\n");
  572. var lines = txt.split("\n");
  573. var figLines = [];
  574. var ii, len, output;
  575. len = lines.length;
  576. for (ii = 0; ii < len; ii++) {
  577. figLines.push( generateFigTextLine(lines[ii], figFonts[fontName], options) );
  578. }
  579. len = figLines.length;
  580. output = figLines[0];
  581. for (ii = 1; ii < len; ii++) {
  582. output = smushVerticalFigLines(output, figLines[ii], options);
  583. }
  584. return output.join("\n");
  585. };
  586. // -------------------------------------------------------------------------
  587. // Public methods
  588. /*
  589. A short-cut for the figlet.text method
  590. Parameters:
  591. - txt (string): The text to make into ASCII Art
  592. - options (object/string - optional): Options that will override the current font's default options.
  593. If a string is provided instead of an object, it is assumed to be the font name.
  594. * font
  595. * horizontalLayout
  596. * verticalLayout
  597. * showHardBlanks - Wont remove hardblank characters
  598. - next (function): A callback function, it will contained the outputted ASCII Art.
  599. */
  600. var me = function(txt, options, next) {
  601. me.text(txt, options, next);
  602. };
  603. me.text = function(txt, options, next) {
  604. var fontName = '';
  605. // Validate inputs
  606. txt = txt + '';
  607. if (typeof arguments[1] === 'function') {
  608. next = options;
  609. options = {};
  610. options.font = figDefaults.font; // default font
  611. }
  612. if (typeof options === 'string') {
  613. fontName = options;
  614. options = {};
  615. } else {
  616. options = options || {};
  617. fontName = options.font || figDefaults.font;
  618. }
  619. /*
  620. Load the font. If it loads, it's data will be contained in the figFonts object.
  621. The callback will recieve a fontsOpts object, which contains the default
  622. options of the font (its fitting rules, etc etc).
  623. */
  624. me.loadFont(fontName, function(err, fontOpts) {
  625. if (err) {
  626. return next(err);
  627. }
  628. next(null, generateText(fontName, _reworkFontOpts(fontOpts, options), txt));
  629. });
  630. };
  631. /*
  632. Synchronous version of figlet.text.
  633. Accepts the same parameters.
  634. */
  635. me.textSync = function(txt, options) {
  636. var fontName = '';
  637. // Validate inputs
  638. txt = txt + '';
  639. if (typeof options === 'string') {
  640. fontName = options;
  641. options = {};
  642. } else {
  643. options = options || {};
  644. fontName = options.font || figDefaults.font;
  645. }
  646. var fontOpts = _reworkFontOpts(me.loadFontSync(fontName), options);
  647. return generateText(fontName, fontOpts, txt);
  648. };
  649. /*
  650. takes assigned options and merges them with the default options from the choosen font
  651. */
  652. function _reworkFontOpts(fontOpts, options) {
  653. var myOpts = JSON.parse(JSON.stringify(fontOpts)), // make a copy because we may edit this (see below)
  654. params,
  655. prop;
  656. /*
  657. If the user is chosing to use a specific type of layout (e.g., 'full', 'fitted', etc etc)
  658. Then we need to override the default font options.
  659. */
  660. if (typeof options.horizontalLayout !== 'undefined') {
  661. params = getHorizontalFittingRules(options.horizontalLayout, fontOpts);
  662. for (prop in params) {
  663. myOpts.fittingRules[prop] = params[prop];
  664. }
  665. }
  666. if (typeof options.verticalLayout !== 'undefined') {
  667. params = getVerticalFittingRules(options.verticalLayout, fontOpts);
  668. for (prop in params) {
  669. myOpts.fittingRules[prop] = params[prop];
  670. }
  671. }
  672. myOpts.printDirection = (typeof options.printDirection !== 'undefined') ? options.printDirection : fontOpts.printDirection;
  673. myOpts.showHardBlanks = options.showHardBlanks || false;
  674. return myOpts;
  675. }
  676. /*
  677. Returns metadata about a specfic FIGlet font.
  678. Returns:
  679. next(err, options, headerComment)
  680. - err: The error if an error occurred, otherwise null/falsey.
  681. - options (object): The options defined for the font.
  682. - headerComment (string): The font's header comment.
  683. */
  684. me.metadata = function(fontName, next) {
  685. fontName = fontName + '';
  686. /*
  687. Load the font. If it loads, it's data will be contained in the figFonts object.
  688. The callback will recieve a fontsOpts object, which contains the default
  689. options of the font (its fitting rules, etc etc).
  690. */
  691. me.loadFont(fontName, function(err, fontOpts) {
  692. if (err) {
  693. next(err);
  694. return;
  695. }
  696. next(null, fontOpts, figFonts[fontName].comment);
  697. });
  698. };
  699. /*
  700. Allows you to override defaults. See the definition of the figDefaults object up above
  701. to see what properties can be overridden.
  702. Returns the options for the font.
  703. */
  704. me.defaults = function(opts) {
  705. if (typeof opts === 'object' && opts !== null) {
  706. for (var prop in opts) {
  707. if (opts.hasOwnProperty(prop)) {
  708. figDefaults[prop] = opts[prop];
  709. }
  710. }
  711. }
  712. return JSON.parse(JSON.stringify(figDefaults));
  713. };
  714. /*
  715. Parses data from a FIGlet font file and places it into the figFonts object.
  716. */
  717. me.parseFont = function(fontName, data) {
  718. data = data.replace(/\r\n/g,"\n").replace(/\r/g,"\n");
  719. figFonts[fontName] = {};
  720. var lines = data.split("\n");
  721. var headerData = lines.splice(0,1)[0].split(" ");
  722. var figFont = figFonts[fontName];
  723. var opts = {};
  724. opts.hardBlank = headerData[0].substr(5,1);
  725. opts.height = parseInt(headerData[1], 10);
  726. opts.baseline = parseInt(headerData[2], 10);
  727. opts.maxLength = parseInt(headerData[3], 10);
  728. opts.oldLayout = parseInt(headerData[4], 10);
  729. opts.numCommentLines = parseInt(headerData[5], 10);
  730. opts.printDirection = (headerData.length >= 6) ? parseInt(headerData[6], 10) : 0;
  731. opts.fullLayout = (headerData.length >= 7) ? parseInt(headerData[7], 10) : null;
  732. opts.codeTagCount = (headerData.length >= 8) ? parseInt(headerData[8], 10) : null;
  733. opts.fittingRules = getSmushingRules(opts.oldLayout, opts.fullLayout);
  734. figFont.options = opts;
  735. // error check
  736. if (opts.hardBlank.length !== 1 ||
  737. isNaN(opts.height) ||
  738. isNaN(opts.baseline) ||
  739. isNaN(opts.maxLength) ||
  740. isNaN(opts.oldLayout) ||
  741. isNaN(opts.numCommentLines) )
  742. {
  743. throw new Error('FIGlet header contains invalid values.');
  744. }
  745. /*
  746. All FIGlet fonts must contain chars 32-126, 196, 214, 220, 228, 246, 252, 223
  747. */
  748. var charNums = [], ii;
  749. for (ii = 32; ii <= 126; ii++) {
  750. charNums.push(ii);
  751. }
  752. charNums = charNums.concat(196, 214, 220, 228, 246, 252, 223);
  753. // error check - validate that there are enough lines in the file
  754. if (lines.length < (opts.numCommentLines + (opts.height * charNums.length)) ) {
  755. throw new Error('FIGlet file is missing data.');
  756. }
  757. /*
  758. Parse out the context of the file and put it into our figFont object
  759. */
  760. var cNum, endCharRegEx, parseError = false;
  761. figFont.comment = lines.splice(0,opts.numCommentLines).join("\n");
  762. figFont.numChars = 0;
  763. while (lines.length > 0 && figFont.numChars < charNums.length) {
  764. cNum = charNums[figFont.numChars];
  765. figFont[cNum] = lines.splice(0,opts.height);
  766. // remove end sub-chars
  767. for (ii = 0; ii < opts.height; ii++) {
  768. if (typeof figFont[cNum][ii] === "undefined") {
  769. figFont[cNum][ii] = "";
  770. } else {
  771. endCharRegEx = new RegExp("\\"+figFont[cNum][ii].substr(figFont[cNum][ii].length-1,1)+"+$");
  772. figFont[cNum][ii] = figFont[cNum][ii].replace(endCharRegEx,"");
  773. }
  774. }
  775. figFont.numChars++;
  776. }
  777. /*
  778. Now we check to see if any additional characters are present
  779. */
  780. while (lines.length > 0) {
  781. cNum = lines.splice(0,1)[0].split(" ")[0];
  782. if ( /^0[xX][0-9a-fA-F]+$/.test(cNum) ) {
  783. cNum = parseInt(cNum, 16);
  784. } else if ( /^0[0-7]+$/.test(cNum) ) {
  785. cNum = parseInt(cNum, 8);
  786. } else if ( /^[0-9]+$/.test(cNum) ) {
  787. cNum = parseInt(cNum, 10);
  788. } else if ( /^-0[xX][0-9a-fA-F]+$/.test(cNum) ) {
  789. cNum = parseInt(cNum, 16);
  790. } else {
  791. if (cNum === "") {break;}
  792. // something's wrong
  793. console.log("Invalid data:"+cNum);
  794. parseError = true;
  795. break;
  796. }
  797. figFont[cNum] = lines.splice(0,opts.height);
  798. // remove end sub-chars
  799. for (ii = 0; ii < opts.height; ii++) {
  800. if (typeof figFont[cNum][ii] === "undefined") {
  801. figFont[cNum][ii] = "";
  802. } else {
  803. endCharRegEx = new RegExp("\\"+figFont[cNum][ii].substr(figFont[cNum][ii].length-1,1)+"+$");
  804. figFont[cNum][ii] = figFont[cNum][ii].replace(endCharRegEx,"");
  805. }
  806. }
  807. figFont.numChars++;
  808. }
  809. // error check
  810. if (parseError === true) {
  811. throw new Error('Error parsing data.');
  812. }
  813. return opts;
  814. };
  815. /*
  816. Loads a font.
  817. */
  818. me.loadFont = function(fontName, next) {
  819. if (figFonts[fontName]) {
  820. next(null, figFonts[fontName].options);
  821. return;
  822. }
  823. if (typeof fetch !== 'function') {
  824. console.error('figlet.js requires the fetch API or a fetch polyfill such as https://cdnjs.com/libraries/fetch');
  825. throw new Error('fetch is required for figlet.js to work.')
  826. }
  827. fetch(figDefaults.fontPath + '/' + fontName + '.flf')
  828. .then(function(response) {
  829. if(response.ok) {
  830. return response.text();
  831. }
  832. console.log('Unexpected response', response);
  833. throw new Error('Network response was not ok.');
  834. })
  835. .then(function(text) {
  836. next(null, me.parseFont(fontName, text));
  837. })
  838. .catch(next);
  839. };
  840. /*
  841. loads a font synchronously, not implemented for the browser
  842. */
  843. me.loadFontSync = function(name) {
  844. if (figFonts[name]) {
  845. return figFonts[name].options;
  846. }
  847. throw new Error('synchronous font loading is not implemented for the browser');
  848. };
  849. /*
  850. preloads a list of fonts prior to using textSync
  851. - fonts: an array of font names (i.e. ["Standard","Soft"])
  852. - next: callback function
  853. */
  854. me.preloadFonts = function(fonts, next) {
  855. var fontData = [];
  856. fonts.reduce(function(promise, name){
  857. return promise.then(function() {
  858. return fetch(figDefaults.fontPath + '/' + name + '.flf').then((response) => {
  859. return response.text();
  860. }).then(function(data) {
  861. fontData.push(data);
  862. });
  863. });
  864. }, Promise.resolve()).then(function(res){
  865. for(var i in fonts){
  866. me.parseFont(fonts[i], fontData[i]);
  867. }
  868. if(next)next();
  869. });
  870. };
  871. me.figFonts = figFonts;
  872. return me;
  873. })();
  874. // for node.js
  875. if (typeof module !== 'undefined') {
  876. if (typeof module.exports !== 'undefined') {
  877. module.exports = figlet;
  878. }
  879. }