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.

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