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.
 
 
 
 
 
 

570 lines
15 KiB

  1. /*
  2. * libcaca Colour ASCII-Art library
  3. * Copyright (c) 2002-2010 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. struct caca_figfont
  27. {
  28. int term_width;
  29. int x, y, w, h, lines;
  30. enum { H_DEFAULT, H_KERN, H_SMUSH, H_NONE, H_OVERLAP } hmode;
  31. int hsmushrule;
  32. uint32_t hardblank;
  33. int height, baseline, max_length;
  34. int old_layout;
  35. int print_direction, full_layout, codetag_count;
  36. int glyphs;
  37. caca_canvas_t *fontcv, *charcv;
  38. int *left, *right; /* Unused yet */
  39. uint32_t *lookup;
  40. };
  41. static uint32_t hsmush(uint32_t ch1, uint32_t ch2, int rule);
  42. static caca_figfont_t * open_figfont(char const *);
  43. static int free_figfont(caca_figfont_t *);
  44. /** \brief load a figfont and attach it to a canvas */
  45. int caca_canvas_set_figfont(caca_canvas_t *cv, char const *path)
  46. {
  47. caca_figfont_t *ff = NULL;
  48. if(path)
  49. {
  50. ff = open_figfont(path);
  51. if(!ff)
  52. return -1;
  53. }
  54. if(cv->ff)
  55. {
  56. caca_free_canvas(cv->ff->charcv);
  57. free(cv->ff->left);
  58. free(cv->ff->right);
  59. free_figfont(cv->ff);
  60. }
  61. cv->ff = ff;
  62. if(!path)
  63. return 0;
  64. /* from TOIlet’s main.c */
  65. ff->term_width = 80;
  66. ff->hmode = H_DEFAULT;
  67. /* from TOIlet’s render.c */
  68. ff->x = ff->y = 0;
  69. ff->w = ff->h = 0;
  70. ff->lines = 0;
  71. caca_set_canvas_size(cv, 0, 0); /* XXX */
  72. /* from TOIlet’s figlet.c */
  73. if(ff->full_layout & 0x3f)
  74. ff->hsmushrule = ff->full_layout & 0x3f;
  75. else if(ff->old_layout > 0)
  76. ff->hsmushrule = ff->old_layout;
  77. switch(ff->hmode)
  78. {
  79. case H_DEFAULT:
  80. if(ff->old_layout == -1)
  81. ff->hmode = H_NONE;
  82. else if(ff->old_layout == 0 && (ff->full_layout & 0xc0) == 0x40)
  83. ff->hmode = H_KERN;
  84. else if((ff->old_layout & 0x3f) && (ff->full_layout & 0x3f)
  85. && (ff->full_layout & 0x80))
  86. {
  87. ff->hmode = H_SMUSH;
  88. ff->hsmushrule = ff->full_layout & 0x3f;
  89. }
  90. else if(ff->old_layout == 0 && (ff->full_layout & 0xbf) == 0x80)
  91. {
  92. ff->hmode = H_SMUSH;
  93. ff->hsmushrule = 0x3f;
  94. }
  95. else
  96. ff->hmode = H_OVERLAP;
  97. break;
  98. default:
  99. break;
  100. }
  101. ff->charcv = caca_create_canvas(ff->max_length - 2, ff->height);
  102. ff->left = malloc(ff->height * sizeof(int));
  103. ff->right = malloc(ff->height * sizeof(int));
  104. cv->ff = ff;
  105. return 0;
  106. }
  107. /** \brief paste a character using the current figfont */
  108. int caca_put_figchar(caca_canvas_t *cv, uint32_t ch)
  109. {
  110. caca_figfont_t *ff = cv->ff;
  111. int c, w, h, x, y, overlap, extra, xleft, xright;
  112. if (!ff)
  113. return -1;
  114. switch(ch)
  115. {
  116. case (uint32_t)'\r':
  117. return 0;
  118. case (uint32_t)'\n':
  119. ff->x = 0;
  120. ff->y += ff->height;
  121. return 0;
  122. /* FIXME: handle '\t' */
  123. }
  124. /* Look whether our glyph is available */
  125. for(c = 0; c < ff->glyphs; c++)
  126. if(ff->lookup[c * 2] == ch)
  127. break;
  128. if(c == ff->glyphs)
  129. return 0;
  130. w = ff->lookup[c * 2 + 1];
  131. h = ff->height;
  132. caca_set_canvas_handle(ff->fontcv, 0, c * ff->height);
  133. caca_blit(ff->charcv, 0, 0, ff->fontcv, NULL);
  134. /* Check whether we reached the end of the screen */
  135. if(ff->x && ff->x + w > ff->term_width)
  136. {
  137. ff->x = 0;
  138. ff->y += h;
  139. }
  140. /* Compute how much the next character will overlap */
  141. switch(ff->hmode)
  142. {
  143. case H_SMUSH:
  144. case H_KERN:
  145. case H_OVERLAP:
  146. extra = (ff->hmode == H_OVERLAP);
  147. overlap = w;
  148. for(y = 0; y < h; y++)
  149. {
  150. /* Compute how much spaces we can eat from the new glyph */
  151. for(xright = 0; xright < overlap; xright++)
  152. if(caca_get_char(ff->charcv, xright, y) != ' ')
  153. break;
  154. /* Compute how much spaces we can eat from the previous glyph */
  155. for(xleft = 0; xright + xleft < overlap && xleft < ff->x; xleft++)
  156. if(caca_get_char(cv, ff->x - 1 - xleft, ff->y + y) != ' ')
  157. break;
  158. /* Handle overlapping */
  159. if(ff->hmode == H_OVERLAP && xleft < ff->x)
  160. xleft++;
  161. /* Handle smushing */
  162. if(ff->hmode == H_SMUSH)
  163. {
  164. if(xleft < ff->x &&
  165. hsmush(caca_get_char(cv, ff->x - 1 - xleft, ff->y + y),
  166. caca_get_char(ff->charcv, xright, y),
  167. ff->hsmushrule))
  168. xleft++;
  169. }
  170. if(xleft + xright < overlap)
  171. overlap = xleft + xright;
  172. }
  173. break;
  174. case H_NONE:
  175. overlap = 0;
  176. break;
  177. default:
  178. return -1;
  179. }
  180. /* Check whether the current canvas is large enough */
  181. if(ff->x + w - overlap > ff->w)
  182. ff->w = ff->x + w - overlap < ff->term_width
  183. ? ff->x + w - overlap : ff->term_width;
  184. if(ff->y + h > ff->h)
  185. ff->h = ff->y + h;
  186. #if 0 /* deactivated for libcaca insertion */
  187. if(attr)
  188. caca_set_attr(cv, attr);
  189. #endif
  190. caca_set_canvas_size(cv, ff->w, ff->h);
  191. /* Render our char (FIXME: create a rect-aware caca_blit_canvas?) */
  192. for(y = 0; y < h; y++)
  193. for(x = 0; x < w; x++)
  194. {
  195. uint32_t ch1, ch2;
  196. uint32_t tmpat = caca_get_attr(ff->fontcv, x, y + c * ff->height);
  197. ch2 = caca_get_char(ff->charcv, x, y);
  198. if(ch2 == ' ')
  199. continue;
  200. ch1 = caca_get_char(cv, ff->x + x - overlap, ff->y + y);
  201. if(ch1 == ' ' || ff->hmode != H_SMUSH)
  202. caca_put_char(cv, ff->x + x - overlap, ff->y + y, ch2);
  203. else
  204. caca_put_char(cv, ff->x + x - overlap, ff->y + y,
  205. hsmush(ch1, ch2, ff->hsmushrule));
  206. caca_put_attr(cv, ff->x + x, ff->y + y, tmpat);
  207. }
  208. /* Advance cursor */
  209. ff->x += w - overlap;
  210. return 0;
  211. }
  212. /** \brief flush the figlet context */
  213. int caca_flush_figlet(caca_canvas_t *cv)
  214. {
  215. caca_figfont_t *ff = cv->ff;
  216. int x, y;
  217. if (!ff)
  218. return -1;
  219. //ff->torender = cv;
  220. //caca_set_canvas_size(ff->torender, ff->w, ff->h);
  221. caca_set_canvas_size(cv, ff->w, ff->h);
  222. /* FIXME: do this somewhere else, or record hardblank positions */
  223. for(y = 0; y < ff->h; y++)
  224. for(x = 0; x < ff->w; x++)
  225. if(caca_get_char(cv, x, y) == 0xa0)
  226. {
  227. uint32_t attr = caca_get_attr(cv, x, y);
  228. caca_put_char(cv, x, y, ' ');
  229. caca_put_attr(cv, x, y, attr);
  230. }
  231. ff->x = ff->y = 0;
  232. ff->w = ff->h = 0;
  233. //cv = caca_create_canvas(1, 1); /* XXX */
  234. /* from render.c */
  235. ff->lines += caca_get_canvas_height(cv);
  236. return 0;
  237. }
  238. #define STD_GLYPHS (127 - 32)
  239. #define EXT_GLYPHS (STD_GLYPHS + 7)
  240. static caca_figfont_t * open_figfont(char const *path)
  241. {
  242. #if !defined __KERNEL__ && defined HAVE_SNPRINTF
  243. char altpath[2048];
  244. #endif
  245. char buf[2048];
  246. char hardblank[10];
  247. caca_figfont_t *ff;
  248. char *data = NULL;
  249. caca_file_t *f;
  250. int i, j, size, comment_lines;
  251. ff = malloc(sizeof(caca_figfont_t));
  252. if(!ff)
  253. {
  254. seterrno(ENOMEM);
  255. return NULL;
  256. }
  257. /* Open font: if not found, try .tlf, then .flf */
  258. f = caca_file_open(path, "r");
  259. #if !defined __KERNEL__ && defined HAVE_SNPRINTF
  260. #if (! defined(snprintf)) && ( defined(_WIN32) || defined(WIN32) ) && (! defined(__CYGWIN__))
  261. #define snprintf _snprintf
  262. #endif
  263. if(!f)
  264. {
  265. snprintf(altpath, 2047, "%s.tlf", path);
  266. altpath[2047] = '\0';
  267. f = caca_file_open(altpath, "r");
  268. }
  269. if(!f)
  270. {
  271. snprintf(altpath, 2047, "%s.flf", path);
  272. altpath[2047] = '\0';
  273. f = caca_file_open(altpath, "r");
  274. }
  275. #endif
  276. if(!f)
  277. {
  278. free(ff);
  279. seterrno(ENOENT);
  280. return NULL;
  281. }
  282. /* Read header */
  283. ff->print_direction = 0;
  284. ff->full_layout = 0;
  285. ff->codetag_count = 0;
  286. caca_file_gets(f, buf, 2048);
  287. if(sscanf(buf, "%*[ft]lf2a%6s %u %u %u %i %u %u %u %u\n", hardblank,
  288. &ff->height, &ff->baseline, &ff->max_length,
  289. &ff->old_layout, &comment_lines, &ff->print_direction,
  290. &ff->full_layout, &ff->codetag_count) < 6)
  291. {
  292. debug("figfont error: `%s' has invalid header: %s", path, buf);
  293. caca_file_close(f);
  294. free(ff);
  295. seterrno(EINVAL);
  296. return NULL;
  297. }
  298. if(ff->old_layout < -1 || ff->old_layout > 63 || ff->full_layout > 32767
  299. || ((ff->full_layout & 0x80) && (ff->full_layout & 0x3f) == 0
  300. && ff->old_layout))
  301. {
  302. debug("figfont error: `%s' has invalid layout %i/%u",
  303. path, ff->old_layout, ff->full_layout);
  304. caca_file_close(f);
  305. free(ff);
  306. seterrno(EINVAL);
  307. return NULL;
  308. }
  309. ff->hardblank = caca_utf8_to_utf32(hardblank, NULL);
  310. /* Skip comment lines */
  311. for(i = 0; i < comment_lines; i++)
  312. caca_file_gets(f, buf, 2048);
  313. /* Read mandatory characters (32-127, 196, 214, 220, 228, 246, 252, 223)
  314. * then read additional characters. */
  315. ff->glyphs = 0;
  316. ff->lookup = NULL;
  317. for(i = 0, size = 0; !caca_file_eof(f); ff->glyphs++)
  318. {
  319. if((ff->glyphs % 2048) == 0)
  320. ff->lookup = realloc(ff->lookup,
  321. (ff->glyphs + 2048) * 2 * sizeof(int));
  322. if(ff->glyphs < STD_GLYPHS)
  323. {
  324. ff->lookup[ff->glyphs * 2] = 32 + ff->glyphs;
  325. }
  326. else if(ff->glyphs < EXT_GLYPHS)
  327. {
  328. static int const tab[7] = { 196, 214, 220, 228, 246, 252, 223 };
  329. ff->lookup[ff->glyphs * 2] = tab[ff->glyphs - STD_GLYPHS];
  330. }
  331. else
  332. {
  333. unsigned int tmp;
  334. if(caca_file_gets(f, buf, 2048) == NULL)
  335. break;
  336. /* Ignore blank lines, as in jacky.flf */
  337. if(buf[0] == '\n' || buf[0] == '\r')
  338. continue;
  339. /* Ignore negative indices for now, as in ivrit.flf */
  340. if(buf[0] == '-')
  341. {
  342. for(j = 0; j < ff->height; j++)
  343. caca_file_gets(f, buf, 2048);
  344. continue;
  345. }
  346. if(!buf[0] || buf[0] < '0' || buf[0] > '9')
  347. {
  348. debug("figfont error: glyph #%u in `%s'", ff->glyphs, path);
  349. free(data);
  350. free(ff->lookup);
  351. free(ff);
  352. seterrno(EINVAL);
  353. return NULL;
  354. }
  355. sscanf(buf, buf[1] == 'x' ? "%x" : "%u", &tmp);
  356. ff->lookup[ff->glyphs * 2] = tmp;
  357. }
  358. ff->lookup[ff->glyphs * 2 + 1] = 0;
  359. for(j = 0; j < ff->height; j++)
  360. {
  361. if(i + 2048 >= size)
  362. data = realloc(data, size += 2048);
  363. caca_file_gets(f, data + i, 2048);
  364. i = (uintptr_t)strchr(data + i, 0) - (uintptr_t)data;
  365. }
  366. }
  367. caca_file_close(f);
  368. if(ff->glyphs < EXT_GLYPHS)
  369. {
  370. debug("figfont error: only %u glyphs in `%s', expected at least %u",
  371. ff->glyphs, path, EXT_GLYPHS);
  372. free(data);
  373. free(ff->lookup);
  374. free(ff);
  375. seterrno(EINVAL);
  376. return NULL;
  377. }
  378. /* Import buffer into canvas */
  379. ff->fontcv = caca_create_canvas(0, 0);
  380. caca_import_canvas_from_memory(ff->fontcv, data, i, "utf8");
  381. free(data);
  382. /* Remove EOL characters. For now we ignore hardblanks, don’t do any
  383. * smushing, nor any kind of error checking. */
  384. for(j = 0; j < ff->height * ff->glyphs; j++)
  385. {
  386. uint32_t ch, oldch = 0;
  387. for(i = ff->max_length; i--;)
  388. {
  389. ch = caca_get_char(ff->fontcv, i, j);
  390. /* Replace hardblanks with U+00A0 NO-BREAK SPACE */
  391. if(ch == ff->hardblank)
  392. caca_put_char(ff->fontcv, i, j, ch = 0xa0);
  393. if(oldch && ch != oldch)
  394. {
  395. if(!ff->lookup[j / ff->height * 2 + 1])
  396. ff->lookup[j / ff->height * 2 + 1] = i + 1;
  397. }
  398. else if(oldch && ch == oldch)
  399. caca_put_char(ff->fontcv, i, j, ' ');
  400. else if(ch != ' ')
  401. {
  402. oldch = ch;
  403. caca_put_char(ff->fontcv, i, j, ' ');
  404. }
  405. }
  406. }
  407. return ff;
  408. }
  409. int free_figfont(caca_figfont_t *ff)
  410. {
  411. caca_free_canvas(ff->fontcv);
  412. free(ff->lookup);
  413. free(ff);
  414. return 0;
  415. }
  416. static uint32_t hsmush(uint32_t ch1, uint32_t ch2, int rule)
  417. {
  418. /* Rule 1 */
  419. if((rule & 0x01) && ch1 == ch2 && ch1 != 0xa0)
  420. return ch2;
  421. if(ch1 < 0x80 && ch2 < 0x80)
  422. {
  423. char const charlist[] = "|/\\[]{}()<>";
  424. char *tmp1, *tmp2;
  425. /* Rule 2 */
  426. if(rule & 0x02)
  427. {
  428. if(ch1 == '_' && strchr(charlist, ch2))
  429. return ch2;
  430. if(ch2 == '_' && strchr(charlist, ch1))
  431. return ch1;
  432. }
  433. /* Rule 3 */
  434. if((rule & 0x04) &&
  435. (tmp1 = strchr(charlist, ch1)) && (tmp2 = strchr(charlist, ch2)))
  436. {
  437. int cl1 = (tmp1 + 1 - charlist) / 2;
  438. int cl2 = (tmp2 + 1 - charlist) / 2;
  439. if(cl1 < cl2)
  440. return ch2;
  441. if(cl1 > cl2)
  442. return ch1;
  443. }
  444. /* Rule 4 */
  445. if(rule & 0x08)
  446. {
  447. uint16_t s = ch1 + ch2;
  448. uint16_t p = ch1 * ch2;
  449. if(p == 15375 /* '{' * '}' */
  450. || p == 8463 /* '[' * ']' */
  451. || (p == 1640 && s == 81)) /* '(' *|+ ')' */
  452. return '|';
  453. }
  454. /* Rule 5 */
  455. if(rule & 0x10)
  456. {
  457. switch((ch1 << 8) | ch2)
  458. {
  459. case 0x2f5c: return '|'; /* /\ */
  460. case 0x5c2f: return 'Y'; /* \/ */
  461. case 0x3e3c: return 'X'; /* >< */
  462. }
  463. }
  464. /* Rule 6 */
  465. if((rule & 0x20) && ch1 == ch2 && ch1 == 0xa0)
  466. return 0xa0;
  467. }
  468. return 0;
  469. }
  470. /*
  471. * XXX: The following functions are aliases.
  472. */
  473. int cucul_canvas_set_figfont(cucul_canvas_t *, char const *)
  474. CACA_ALIAS(caca_canvas_set_figfont);
  475. int cucul_put_figchar(cucul_canvas_t *, uint32_t) CACA_ALIAS(caca_put_figchar);
  476. int cucul_flush_figlet(cucul_canvas_t *) CACA_ALIAS(caca_flush_figlet);