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.

преди 13 години
преди 15 години
преди 15 години
преди 17 години
преди 17 години
преди 17 години
преди 15 години
преди 15 години
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  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. #if !defined __KERNEL__ && defined HAVE_SNPRINTF
  244. char altpath[2048];
  245. #endif
  246. char buf[2048];
  247. char hardblank[10];
  248. caca_charfont_t *ff;
  249. char *data = NULL;
  250. caca_file_t *f;
  251. int i, j, size, comment_lines;
  252. ff = malloc(sizeof(caca_charfont_t));
  253. if(!ff)
  254. {
  255. seterrno(ENOMEM);
  256. return NULL;
  257. }
  258. /* Open font: if not found, try .tlf, then .flf */
  259. f = caca_file_open(path, "r");
  260. #if !defined __KERNEL__ && defined HAVE_SNPRINTF
  261. #if (! defined(snprintf)) && ( defined(_WIN32) || defined(WIN32) ) && (! defined(__CYGWIN__))
  262. #define snprintf _snprintf
  263. #endif
  264. if(!f)
  265. {
  266. snprintf(altpath, 2047, "%s.tlf", path);
  267. altpath[2047] = '\0';
  268. f = caca_file_open(altpath, "r");
  269. }
  270. if(!f)
  271. {
  272. snprintf(altpath, 2047, "%s.flf", path);
  273. altpath[2047] = '\0';
  274. f = caca_file_open(altpath, "r");
  275. }
  276. #endif
  277. if(!f)
  278. {
  279. free(ff);
  280. seterrno(ENOENT);
  281. return NULL;
  282. }
  283. /* Read header */
  284. ff->print_direction = 0;
  285. ff->full_layout = 0;
  286. ff->codetag_count = 0;
  287. caca_file_gets(f, buf, 2048);
  288. if(sscanf(buf, "%*[ft]lf2a%6s %u %u %u %i %u %u %u %u\n", hardblank,
  289. &ff->height, &ff->baseline, &ff->max_length,
  290. &ff->old_layout, &comment_lines, &ff->print_direction,
  291. &ff->full_layout, &ff->codetag_count) < 6)
  292. {
  293. debug("figfont error: `%s' has invalid header: %s", path, buf);
  294. caca_file_close(f);
  295. free(ff);
  296. seterrno(EINVAL);
  297. return NULL;
  298. }
  299. if(ff->old_layout < -1 || ff->old_layout > 63 || ff->full_layout > 32767
  300. || ((ff->full_layout & 0x80) && (ff->full_layout & 0x3f) == 0
  301. && ff->old_layout))
  302. {
  303. debug("figfont error: `%s' has invalid layout %i/%u",
  304. path, ff->old_layout, ff->full_layout);
  305. caca_file_close(f);
  306. free(ff);
  307. seterrno(EINVAL);
  308. return NULL;
  309. }
  310. ff->hardblank = caca_utf8_to_utf32(hardblank, NULL);
  311. /* Skip comment lines */
  312. for(i = 0; i < comment_lines; i++)
  313. caca_file_gets(f, buf, 2048);
  314. /* Read mandatory characters (32-127, 196, 214, 220, 228, 246, 252, 223)
  315. * then read additional characters. */
  316. ff->glyphs = 0;
  317. ff->lookup = NULL;
  318. for(i = 0, size = 0; !caca_file_eof(f); ff->glyphs++)
  319. {
  320. if((ff->glyphs % 2048) == 0)
  321. ff->lookup = realloc(ff->lookup,
  322. (ff->glyphs + 2048) * 2 * sizeof(int));
  323. if(ff->glyphs < STD_GLYPHS)
  324. {
  325. ff->lookup[ff->glyphs * 2] = 32 + ff->glyphs;
  326. }
  327. else if(ff->glyphs < EXT_GLYPHS)
  328. {
  329. static int const tab[7] = { 196, 214, 220, 228, 246, 252, 223 };
  330. ff->lookup[ff->glyphs * 2] = tab[ff->glyphs - STD_GLYPHS];
  331. }
  332. else
  333. {
  334. unsigned int tmp;
  335. if(caca_file_gets(f, buf, 2048) == NULL)
  336. break;
  337. /* Ignore blank lines, as in jacky.flf */
  338. if(buf[0] == '\n' || buf[0] == '\r')
  339. continue;
  340. /* Ignore negative indices for now, as in ivrit.flf */
  341. if(buf[0] == '-')
  342. {
  343. for(j = 0; j < ff->height; j++)
  344. caca_file_gets(f, buf, 2048);
  345. continue;
  346. }
  347. if(!buf[0] || buf[0] < '0' || buf[0] > '9')
  348. {
  349. debug("figfont error: glyph #%u in `%s'", ff->glyphs, path);
  350. free(data);
  351. free(ff->lookup);
  352. free(ff);
  353. seterrno(EINVAL);
  354. return NULL;
  355. }
  356. sscanf(buf, buf[1] == 'x' ? "%x" : "%u", &tmp);
  357. ff->lookup[ff->glyphs * 2] = tmp;
  358. }
  359. ff->lookup[ff->glyphs * 2 + 1] = 0;
  360. for(j = 0; j < ff->height; j++)
  361. {
  362. if(i + 2048 >= size)
  363. data = realloc(data, size += 2048);
  364. caca_file_gets(f, data + i, 2048);
  365. i = (uintptr_t)strchr(data + i, 0) - (uintptr_t)data;
  366. }
  367. }
  368. caca_file_close(f);
  369. if(ff->glyphs < EXT_GLYPHS)
  370. {
  371. debug("figfont error: only %u glyphs in `%s', expected at least %u",
  372. ff->glyphs, path, EXT_GLYPHS);
  373. free(data);
  374. free(ff->lookup);
  375. free(ff);
  376. seterrno(EINVAL);
  377. return NULL;
  378. }
  379. /* Import buffer into canvas */
  380. ff->fontcv = caca_create_canvas(0, 0);
  381. caca_import_canvas_from_memory(ff->fontcv, data, i, "utf8");
  382. free(data);
  383. /* Remove EOL characters. For now we ignore hardblanks, don’t do any
  384. * smushing, nor any kind of error checking. */
  385. for(j = 0; j < ff->height * ff->glyphs; j++)
  386. {
  387. uint32_t ch, oldch = 0;
  388. for(i = ff->max_length; i--;)
  389. {
  390. ch = caca_get_char(ff->fontcv, i, j);
  391. /* Replace hardblanks with U+00A0 NO-BREAK SPACE */
  392. if(ch == ff->hardblank)
  393. caca_put_char(ff->fontcv, i, j, ch = 0xa0);
  394. if(oldch && ch != oldch)
  395. {
  396. if(!ff->lookup[j / ff->height * 2 + 1])
  397. ff->lookup[j / ff->height * 2 + 1] = i + 1;
  398. }
  399. else if(oldch && ch == oldch)
  400. caca_put_char(ff->fontcv, i, j, ' ');
  401. else if(ch != ' ')
  402. {
  403. oldch = ch;
  404. caca_put_char(ff->fontcv, i, j, ' ');
  405. }
  406. }
  407. }
  408. return ff;
  409. }
  410. int free_charfont(caca_charfont_t *ff)
  411. {
  412. caca_free_canvas(ff->fontcv);
  413. free(ff->lookup);
  414. free(ff);
  415. return 0;
  416. }
  417. static void update_figfont_settings(caca_canvas_t *cv)
  418. {
  419. caca_charfont_t *ff = cv->ff;
  420. if (!cv->ff)
  421. return;
  422. /* from TOIlet’s figlet.c */
  423. if (ff->full_layout & 0x3f)
  424. ff->hsmushrule = ff->full_layout & 0x3f;
  425. else if (ff->old_layout > 0)
  426. ff->hsmushrule = ff->old_layout;
  427. switch (ff->hmode)
  428. {
  429. case H_DEFAULT:
  430. if (ff->old_layout == -1)
  431. ff->hmode = H_NONE;
  432. else if (ff->old_layout == 0 && (ff->full_layout & 0xc0) == 0x40)
  433. ff->hmode = H_KERN;
  434. else if ((ff->old_layout & 0x3f) && (ff->full_layout & 0x3f)
  435. && (ff->full_layout & 0x80))
  436. {
  437. ff->hmode = H_SMUSH;
  438. ff->hsmushrule = ff->full_layout & 0x3f;
  439. }
  440. else if (ff->old_layout == 0 && (ff->full_layout & 0xbf) == 0x80)
  441. {
  442. ff->hmode = H_SMUSH;
  443. ff->hsmushrule = 0x3f;
  444. }
  445. else
  446. ff->hmode = H_OVERLAP;
  447. break;
  448. default:
  449. break;
  450. }
  451. if (ff->charcv)
  452. caca_free_canvas(ff->charcv);
  453. ff->charcv = caca_create_canvas(ff->max_length - 2, ff->height);
  454. free(ff->left);
  455. free(ff->right);
  456. ff->left = malloc(ff->height * sizeof(int));
  457. ff->right = malloc(ff->height * sizeof(int));
  458. }
  459. static uint32_t hsmush(uint32_t ch1, uint32_t ch2, int rule)
  460. {
  461. /* Rule 1 */
  462. if((rule & 0x01) && ch1 == ch2 && ch1 != 0xa0)
  463. return ch2;
  464. if(ch1 < 0x80 && ch2 < 0x80)
  465. {
  466. char const charlist[] = "|/\\[]{}()<>";
  467. char *tmp1, *tmp2;
  468. /* Rule 2 */
  469. if(rule & 0x02)
  470. {
  471. if(ch1 == '_' && strchr(charlist, ch2))
  472. return ch2;
  473. if(ch2 == '_' && strchr(charlist, ch1))
  474. return ch1;
  475. }
  476. /* Rule 3 */
  477. if((rule & 0x04) &&
  478. (tmp1 = strchr(charlist, ch1)) && (tmp2 = strchr(charlist, ch2)))
  479. {
  480. int cl1 = (tmp1 + 1 - charlist) / 2;
  481. int cl2 = (tmp2 + 1 - charlist) / 2;
  482. if(cl1 < cl2)
  483. return ch2;
  484. if(cl1 > cl2)
  485. return ch1;
  486. }
  487. /* Rule 4 */
  488. if(rule & 0x08)
  489. {
  490. uint16_t s = ch1 + ch2;
  491. uint16_t p = ch1 * ch2;
  492. if(p == 15375 /* '{' * '}' */
  493. || p == 8463 /* '[' * ']' */
  494. || (p == 1640 && s == 81)) /* '(' *|+ ')' */
  495. return '|';
  496. }
  497. /* Rule 5 */
  498. if(rule & 0x10)
  499. {
  500. switch((ch1 << 8) | ch2)
  501. {
  502. case 0x2f5c: return '|'; /* /\ */
  503. case 0x5c2f: return 'Y'; /* \/ */
  504. case 0x3e3c: return 'X'; /* >< */
  505. }
  506. }
  507. /* Rule 6 */
  508. if((rule & 0x20) && ch1 == ch2 && ch1 == 0xa0)
  509. return 0xa0;
  510. }
  511. return 0;
  512. }
  513. /*
  514. * XXX: The following functions are aliases.
  515. */
  516. int cucul_canvas_set_figfont(cucul_canvas_t *, char const *)
  517. CACA_ALIAS(caca_canvas_set_figfont);
  518. int cucul_put_figchar(cucul_canvas_t *, uint32_t) CACA_ALIAS(caca_put_figchar);
  519. int cucul_flush_figlet(cucul_canvas_t *) CACA_ALIAS(caca_flush_figlet);