您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

664 行
17 KiB

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