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

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

Large source code cleanup, getting rid of spaces, tabs, and svn keywords.

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