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.
 
 
 
 
 
 

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