source: libcaca/trunk/caca/figfont.c @ 4321

Last change on this file since 4321 was 4321, checked in by Sam Hocevar, 10 years ago

Add brief documentation to figfont functions.

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