You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

665 lines
17 KiB

  1. /*
  2. * libcaca Colour ASCII-Art library
  3. * Copyright (c) 2002-2012 Sam Hocevar <sam@hocevar.net>
  4. * All Rights Reserved
  5. *
  6. * This library is free software. It comes without any warranty, to
  7. * the extent permitted by applicable law. You can redistribute it
  8. * and/or modify it under the terms of the Do What The Fuck You Want
  9. * To Public License, Version 2, as published by Sam Hocevar. See
  10. * http://sam.zoy.org/wtfpl/COPYING for more details.
  11. */
  12. /*
  13. * This file contains FIGlet and TOIlet font handling functions.
  14. */
  15. /*
  16. * FIXME: this file needs huge cleanup to be usable
  17. */
  18. #include "config.h"
  19. #if !defined(__KERNEL__)
  20. # include <stdio.h>
  21. # include <stdlib.h>
  22. # include <string.h>
  23. #endif
  24. #include "caca.h"
  25. #include "caca_internals.h"
  26. #if defined _WIN32 && defined __GNUC__ && __GNUC__ >= 3
  27. int sprintf_s(char *s, size_t n, const char *fmt, ...) CACA_WEAK;
  28. int vsnprintf(char *s, size_t n, const char *fmt, va_list ap) CACA_WEAK;
  29. #endif
  30. struct caca_charfont
  31. {
  32. int term_width;
  33. int x, y, w, h, lines;
  34. enum { H_DEFAULT, H_KERN, H_SMUSH, H_NONE, H_OVERLAP } hmode;
  35. int hsmushrule;
  36. uint32_t hardblank;
  37. int height, baseline, max_length;
  38. int old_layout;
  39. int print_direction, full_layout, codetag_count;
  40. int glyphs;
  41. caca_canvas_t *fontcv, *charcv;
  42. int *left, *right; /* Unused yet */
  43. uint32_t *lookup;
  44. };
  45. static uint32_t hsmush(uint32_t ch1, uint32_t ch2, int rule);
  46. static caca_charfont_t * open_charfont(char const *);
  47. static int free_charfont(caca_charfont_t *);
  48. static void update_figfont_settings(caca_canvas_t *cv);
  49. /** \brief load a figfont and attach it to a canvas */
  50. int caca_canvas_set_figfont(caca_canvas_t *cv, char const *path)
  51. {
  52. caca_charfont_t *ff = NULL;
  53. if (path)
  54. {
  55. ff = open_charfont(path);
  56. if (!ff)
  57. return -1;
  58. }
  59. if (cv->ff)
  60. {
  61. caca_free_canvas(cv->ff->charcv);
  62. free(cv->ff->left);
  63. free(cv->ff->right);
  64. free_charfont(cv->ff);
  65. }
  66. cv->ff = ff;
  67. if (!path)
  68. return 0;
  69. /* from TOIlet’s main.c -- can be overriden by user */
  70. ff->term_width = 80;
  71. ff->hmode = H_DEFAULT;
  72. /* from TOIlet’s render.c */
  73. ff->x = ff->y = 0;
  74. ff->w = ff->h = 0;
  75. ff->lines = 0;
  76. caca_set_canvas_size(cv, 0, 0); /* XXX */
  77. cv->ff = ff;
  78. update_figfont_settings(cv);
  79. return 0;
  80. }
  81. /** \brief set the width of the figfont rendering */
  82. int caca_set_figfont_width(caca_canvas_t *cv, int width)
  83. {
  84. caca_charfont_t *ff = cv->ff;
  85. if (!cv->ff)
  86. return 0;
  87. ff->term_width = width;
  88. update_figfont_settings(cv);
  89. return 0;
  90. }
  91. /** \brief set the smushing mode of the figfont rendering */
  92. int caca_set_figfont_smush(caca_canvas_t *cv, char const *mode)
  93. {
  94. caca_charfont_t *ff = cv->ff;
  95. if (!cv->ff)
  96. return 0;
  97. if (!strcasecmp(mode, "default"))
  98. ff->hmode = H_DEFAULT;
  99. else if (!strcasecmp(mode, "kern"))
  100. ff->hmode = H_KERN;
  101. else if (!strcasecmp(mode, "smush"))
  102. ff->hmode = H_SMUSH;
  103. else if (!strcasecmp(mode, "none"))
  104. ff->hmode = H_NONE;
  105. else if (!strcasecmp(mode, "overlap"))
  106. ff->hmode = H_OVERLAP;
  107. else
  108. ff->hmode = H_DEFAULT;
  109. update_figfont_settings(cv);
  110. return 0;
  111. }
  112. /** \brief paste a character using the current figfont */
  113. int caca_put_figchar(caca_canvas_t *cv, uint32_t ch)
  114. {
  115. caca_charfont_t *ff = cv->ff;
  116. int c, w, h, x, y, overlap, extra, xleft, xright;
  117. if (!ff)
  118. return -1;
  119. switch(ch)
  120. {
  121. case (uint32_t)'\r':
  122. return 0;
  123. case (uint32_t)'\n':
  124. ff->x = 0;
  125. ff->y += ff->height;
  126. return 0;
  127. /* FIXME: handle '\t' */
  128. }
  129. /* Look whether our glyph is available */
  130. for(c = 0; c < ff->glyphs; c++)
  131. if(ff->lookup[c * 2] == ch)
  132. break;
  133. if(c == ff->glyphs)
  134. return 0;
  135. w = ff->lookup[c * 2 + 1];
  136. h = ff->height;
  137. caca_set_canvas_handle(ff->fontcv, 0, c * ff->height);
  138. caca_blit(ff->charcv, 0, 0, ff->fontcv, NULL);
  139. /* Check whether we reached the end of the screen */
  140. if(ff->x && ff->x + w > ff->term_width)
  141. {
  142. ff->x = 0;
  143. ff->y += h;
  144. }
  145. /* Compute how much the next character will overlap */
  146. switch(ff->hmode)
  147. {
  148. case H_SMUSH:
  149. case H_KERN:
  150. case H_OVERLAP:
  151. extra = (ff->hmode == H_OVERLAP);
  152. overlap = w;
  153. for(y = 0; y < h; y++)
  154. {
  155. /* Compute how much spaces we can eat from the new glyph */
  156. for(xright = 0; xright < overlap; xright++)
  157. if(caca_get_char(ff->charcv, xright, y) != ' ')
  158. break;
  159. /* Compute how much spaces we can eat from the previous glyph */
  160. for(xleft = 0; xright + xleft < overlap && xleft < ff->x; xleft++)
  161. if(caca_get_char(cv, ff->x - 1 - xleft, ff->y + y) != ' ')
  162. break;
  163. /* Handle overlapping */
  164. if(ff->hmode == H_OVERLAP && xleft < ff->x)
  165. xleft++;
  166. /* Handle smushing */
  167. if(ff->hmode == H_SMUSH)
  168. {
  169. if(xleft < ff->x &&
  170. hsmush(caca_get_char(cv, ff->x - 1 - xleft, ff->y + y),
  171. caca_get_char(ff->charcv, xright, y),
  172. ff->hsmushrule))
  173. xleft++;
  174. }
  175. if(xleft + xright < overlap)
  176. overlap = xleft + xright;
  177. }
  178. break;
  179. case H_NONE:
  180. overlap = 0;
  181. break;
  182. default:
  183. return -1;
  184. }
  185. /* Check whether the current canvas is large enough */
  186. if(ff->x + w - overlap > ff->w)
  187. ff->w = ff->x + w - overlap < ff->term_width
  188. ? ff->x + w - overlap : ff->term_width;
  189. if(ff->y + h > ff->h)
  190. ff->h = ff->y + h;
  191. #if 0 /* deactivated for libcaca insertion */
  192. if(attr)
  193. caca_set_attr(cv, attr);
  194. #endif
  195. caca_set_canvas_size(cv, ff->w, ff->h);
  196. /* Render our char (FIXME: create a rect-aware caca_blit_canvas?) */
  197. for(y = 0; y < h; y++)
  198. for(x = 0; x < w; x++)
  199. {
  200. uint32_t ch1, ch2;
  201. uint32_t tmpat = caca_get_attr(ff->fontcv, x, y + c * ff->height);
  202. ch2 = caca_get_char(ff->charcv, x, y);
  203. if(ch2 == ' ')
  204. continue;
  205. ch1 = caca_get_char(cv, ff->x + x - overlap, ff->y + y);
  206. if(ch1 == ' ' || ff->hmode != H_SMUSH)
  207. caca_put_char(cv, ff->x + x - overlap, ff->y + y, ch2);
  208. else
  209. caca_put_char(cv, ff->x + x - overlap, ff->y + y,
  210. hsmush(ch1, ch2, ff->hsmushrule));
  211. caca_put_attr(cv, ff->x + x, ff->y + y, tmpat);
  212. }
  213. /* Advance cursor */
  214. ff->x += w - overlap;
  215. return 0;
  216. }
  217. /** \brief flush the figlet context */
  218. int caca_flush_figlet(caca_canvas_t *cv)
  219. {
  220. caca_charfont_t *ff = cv->ff;
  221. int x, y;
  222. if (!ff)
  223. return -1;
  224. //ff->torender = cv;
  225. //caca_set_canvas_size(ff->torender, ff->w, ff->h);
  226. caca_set_canvas_size(cv, ff->w, ff->h);
  227. /* FIXME: do this somewhere else, or record hardblank positions */
  228. for(y = 0; y < ff->h; y++)
  229. for(x = 0; x < ff->w; x++)
  230. if(caca_get_char(cv, x, y) == 0xa0)
  231. {
  232. uint32_t attr = caca_get_attr(cv, x, y);
  233. caca_put_char(cv, x, y, ' ');
  234. caca_put_attr(cv, x, y, attr);
  235. }
  236. ff->x = ff->y = 0;
  237. ff->w = ff->h = 0;
  238. //cv = caca_create_canvas(1, 1); /* XXX */
  239. /* from render.c */
  240. ff->lines += caca_get_canvas_height(cv);
  241. return 0;
  242. }
  243. #define STD_GLYPHS (127 - 32)
  244. #define EXT_GLYPHS (STD_GLYPHS + 7)
  245. static caca_charfont_t * open_charfont(char const *path)
  246. {
  247. char buf[2048];
  248. char hardblank[10];
  249. caca_charfont_t *ff;
  250. char *data = NULL;
  251. caca_file_t *f;
  252. #if !defined __KERNEL__ && (defined HAVE_SNPRINTF || defined HAVE_SPRINTF_S)
  253. int const pathlen = 2048;
  254. char *altpath = NULL;
  255. #endif
  256. int i, j, size, comment_lines;
  257. ff = malloc(sizeof(caca_charfont_t));
  258. if(!ff)
  259. {
  260. seterrno(ENOMEM);
  261. return NULL;
  262. }
  263. /* Open font: if not found, try .tlf, then .flf */
  264. f = caca_file_open(path, "r");
  265. #if !defined __KERNEL__ && (defined HAVE_SNPRINTF || defined HAVE_SPRINTF_S)
  266. if(!f)
  267. altpath = malloc(pathlen);
  268. if(!f)
  269. {
  270. #if defined HAVE_SPRINTF_S
  271. sprintf_s(altpath, pathlen - 1, "%s.tlf", path);
  272. #else
  273. snprintf(altpath, pathlen - 1, "%s.tlf", path);
  274. #endif
  275. altpath[pathlen - 1] = '\0';
  276. f = caca_file_open(altpath, "r");
  277. }
  278. if(!f)
  279. {
  280. #if defined HAVE_SPRINTF_S
  281. sprintf_s(altpath, pathlen - 1, "%s.flf", path);
  282. #else
  283. snprintf(altpath, pathlen - 1, "%s.flf", path);
  284. #endif
  285. altpath[pathlen - 1] = '\0';
  286. f = caca_file_open(altpath, "r");
  287. }
  288. if (altpath)
  289. free(altpath);
  290. #endif
  291. if(!f)
  292. {
  293. free(ff);
  294. seterrno(ENOENT);
  295. return NULL;
  296. }
  297. /* Read header */
  298. ff->print_direction = 0;
  299. ff->full_layout = 0;
  300. ff->codetag_count = 0;
  301. caca_file_gets(f, buf, 2048);
  302. if(sscanf(buf, "%*[ft]lf2a%6s %u %u %u %i %u %u %u %u\n", hardblank,
  303. &ff->height, &ff->baseline, &ff->max_length,
  304. &ff->old_layout, &comment_lines, &ff->print_direction,
  305. &ff->full_layout, &ff->codetag_count) < 6)
  306. {
  307. debug("figfont error: `%s' has invalid header: %s", path, buf);
  308. caca_file_close(f);
  309. free(ff);
  310. seterrno(EINVAL);
  311. return NULL;
  312. }
  313. if(ff->old_layout < -1 || ff->old_layout > 63 || ff->full_layout > 32767
  314. || ((ff->full_layout & 0x80) && (ff->full_layout & 0x3f) == 0
  315. && ff->old_layout))
  316. {
  317. debug("figfont error: `%s' has invalid layout %i/%u",
  318. path, ff->old_layout, ff->full_layout);
  319. caca_file_close(f);
  320. free(ff);
  321. seterrno(EINVAL);
  322. return NULL;
  323. }
  324. ff->hardblank = caca_utf8_to_utf32(hardblank, NULL);
  325. /* Skip comment lines */
  326. for(i = 0; i < comment_lines; i++)
  327. caca_file_gets(f, buf, 2048);
  328. /* Read mandatory characters (32-127, 196, 214, 220, 228, 246, 252, 223)
  329. * then read additional characters. */
  330. ff->glyphs = 0;
  331. ff->lookup = NULL;
  332. for(i = 0, size = 0; !caca_file_eof(f); ff->glyphs++)
  333. {
  334. if((ff->glyphs % 2048) == 0)
  335. ff->lookup = realloc(ff->lookup,
  336. (ff->glyphs + 2048) * 2 * sizeof(int));
  337. if(ff->glyphs < STD_GLYPHS)
  338. {
  339. ff->lookup[ff->glyphs * 2] = 32 + ff->glyphs;
  340. }
  341. else if(ff->glyphs < EXT_GLYPHS)
  342. {
  343. static int const tab[7] = { 196, 214, 220, 228, 246, 252, 223 };
  344. ff->lookup[ff->glyphs * 2] = tab[ff->glyphs - STD_GLYPHS];
  345. }
  346. else
  347. {
  348. unsigned int tmp;
  349. if(caca_file_gets(f, buf, 2048) == NULL)
  350. break;
  351. /* Ignore blank lines, as in jacky.flf */
  352. if(buf[0] == '\n' || buf[0] == '\r')
  353. continue;
  354. /* Ignore negative indices for now, as in ivrit.flf */
  355. if(buf[0] == '-')
  356. {
  357. for(j = 0; j < ff->height; j++)
  358. caca_file_gets(f, buf, 2048);
  359. continue;
  360. }
  361. if(!buf[0] || buf[0] < '0' || buf[0] > '9')
  362. {
  363. debug("figfont error: glyph #%u in `%s'", ff->glyphs, path);
  364. free(data);
  365. free(ff->lookup);
  366. free(ff);
  367. seterrno(EINVAL);
  368. return NULL;
  369. }
  370. sscanf(buf, buf[1] == 'x' ? "%x" : "%u", &tmp);
  371. ff->lookup[ff->glyphs * 2] = tmp;
  372. }
  373. ff->lookup[ff->glyphs * 2 + 1] = 0;
  374. for(j = 0; j < ff->height; j++)
  375. {
  376. if(i + 2048 >= size)
  377. data = realloc(data, size += 2048);
  378. caca_file_gets(f, data + i, 2048);
  379. i = (uintptr_t)strchr(data + i, 0) - (uintptr_t)data;
  380. }
  381. }
  382. caca_file_close(f);
  383. if(ff->glyphs < EXT_GLYPHS)
  384. {
  385. debug("figfont error: only %u glyphs in `%s', expected at least %u",
  386. ff->glyphs, path, EXT_GLYPHS);
  387. free(data);
  388. free(ff->lookup);
  389. free(ff);
  390. seterrno(EINVAL);
  391. return NULL;
  392. }
  393. /* Remaining initialisation */
  394. ff->charcv = NULL;
  395. ff->left = NULL;
  396. ff->right = NULL;
  397. /* Import buffer into canvas */
  398. ff->fontcv = caca_create_canvas(0, 0);
  399. caca_import_canvas_from_memory(ff->fontcv, data, i, "utf8");
  400. free(data);
  401. /* Remove EOL characters. For now we ignore hardblanks, don’t do any
  402. * smushing, nor any kind of error checking. */
  403. for(j = 0; j < ff->height * ff->glyphs; j++)
  404. {
  405. uint32_t ch, oldch = 0;
  406. for(i = ff->max_length; i--;)
  407. {
  408. ch = caca_get_char(ff->fontcv, i, j);
  409. /* Replace hardblanks with U+00A0 NO-BREAK SPACE */
  410. if(ch == ff->hardblank)
  411. caca_put_char(ff->fontcv, i, j, ch = 0xa0);
  412. if(oldch && ch != oldch)
  413. {
  414. if(!ff->lookup[j / ff->height * 2 + 1])
  415. ff->lookup[j / ff->height * 2 + 1] = i + 1;
  416. }
  417. else if(oldch && ch == oldch)
  418. caca_put_char(ff->fontcv, i, j, ' ');
  419. else if(ch != ' ')
  420. {
  421. oldch = ch;
  422. caca_put_char(ff->fontcv, i, j, ' ');
  423. }
  424. }
  425. }
  426. return ff;
  427. }
  428. int free_charfont(caca_charfont_t *ff)
  429. {
  430. caca_free_canvas(ff->fontcv);
  431. free(ff->lookup);
  432. free(ff);
  433. return 0;
  434. }
  435. static void update_figfont_settings(caca_canvas_t *cv)
  436. {
  437. caca_charfont_t *ff = cv->ff;
  438. if (!cv->ff)
  439. return;
  440. /* from TOIlet’s figlet.c */
  441. if (ff->full_layout & 0x3f)
  442. ff->hsmushrule = ff->full_layout & 0x3f;
  443. else if (ff->old_layout > 0)
  444. ff->hsmushrule = ff->old_layout;
  445. switch (ff->hmode)
  446. {
  447. case H_DEFAULT:
  448. if (ff->old_layout == -1)
  449. ff->hmode = H_NONE;
  450. else if (ff->old_layout == 0 && (ff->full_layout & 0xc0) == 0x40)
  451. ff->hmode = H_KERN;
  452. else if ((ff->old_layout & 0x3f) && (ff->full_layout & 0x3f)
  453. && (ff->full_layout & 0x80))
  454. {
  455. ff->hmode = H_SMUSH;
  456. ff->hsmushrule = ff->full_layout & 0x3f;
  457. }
  458. else if (ff->old_layout == 0 && (ff->full_layout & 0xbf) == 0x80)
  459. {
  460. ff->hmode = H_SMUSH;
  461. ff->hsmushrule = 0x3f;
  462. }
  463. else
  464. ff->hmode = H_OVERLAP;
  465. break;
  466. default:
  467. break;
  468. }
  469. if (ff->charcv)
  470. caca_free_canvas(ff->charcv);
  471. ff->charcv = caca_create_canvas(ff->max_length - 2, ff->height);
  472. free(ff->left);
  473. free(ff->right);
  474. ff->left = malloc(ff->height * sizeof(int));
  475. ff->right = malloc(ff->height * sizeof(int));
  476. }
  477. static uint32_t hsmush(uint32_t ch1, uint32_t ch2, int rule)
  478. {
  479. /* Rule 1 */
  480. if((rule & 0x01) && ch1 == ch2 && ch1 != 0xa0)
  481. return ch2;
  482. if(ch1 < 0x80 && ch2 < 0x80)
  483. {
  484. char const charlist[] = "|/\\[]{}()<>";
  485. char *tmp1, *tmp2;
  486. /* Rule 2 */
  487. if(rule & 0x02)
  488. {
  489. if(ch1 == '_' && strchr(charlist, ch2))
  490. return ch2;
  491. if(ch2 == '_' && strchr(charlist, ch1))
  492. return ch1;
  493. }
  494. /* Rule 3 */
  495. if((rule & 0x04) &&
  496. (tmp1 = strchr(charlist, ch1)) && (tmp2 = strchr(charlist, ch2)))
  497. {
  498. int cl1 = (tmp1 + 1 - charlist) / 2;
  499. int cl2 = (tmp2 + 1 - charlist) / 2;
  500. if(cl1 < cl2)
  501. return ch2;
  502. if(cl1 > cl2)
  503. return ch1;
  504. }
  505. /* Rule 4 */
  506. if(rule & 0x08)
  507. {
  508. uint16_t s = ch1 + ch2;
  509. uint16_t p = ch1 * ch2;
  510. if(p == 15375 /* '{' * '}' */
  511. || p == 8463 /* '[' * ']' */
  512. || (p == 1640 && s == 81)) /* '(' *|+ ')' */
  513. return '|';
  514. }
  515. /* Rule 5 */
  516. if(rule & 0x10)
  517. {
  518. switch((ch1 << 8) | ch2)
  519. {
  520. case 0x2f5c: return '|'; /* /\ */
  521. case 0x5c2f: return 'Y'; /* \/ */
  522. case 0x3e3c: return 'X'; /* >< */
  523. }
  524. }
  525. /* Rule 6 */
  526. if((rule & 0x20) && ch1 == ch2 && ch1 == 0xa0)
  527. return 0xa0;
  528. }
  529. return 0;
  530. }
  531. /*
  532. * Functions for the mingw32 runtime
  533. */
  534. #if defined _WIN32 && defined __GNUC__ && __GNUC__ >= 3
  535. int sprintf_s(char *s, size_t n, const char *fmt, ...)
  536. {
  537. va_list args;
  538. int ret;
  539. va_start(args, fmt);
  540. ret = vsnprintf(s, n, fmt, args);
  541. va_end(args);
  542. return ret;
  543. }
  544. int vsnprintf(char *s, size_t n, const char *fmt, va_list ap)
  545. {
  546. return 0;
  547. }
  548. #endif
  549. /*
  550. * XXX: The following functions are aliases.
  551. */
  552. int cucul_canvas_set_figfont(cucul_canvas_t *, char const *)
  553. CACA_ALIAS(caca_canvas_set_figfont);
  554. int cucul_put_figchar(cucul_canvas_t *, uint32_t) CACA_ALIAS(caca_put_figchar);
  555. int cucul_flush_figlet(cucul_canvas_t *) CACA_ALIAS(caca_flush_figlet);