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.
 
 
 
 
 
 

549 lines
14 KiB

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