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

Last change on this file since 2960 was 2960, checked in by Sam Hocevar, 12 years ago

libcaca: fix a minor warning on DOS targets.

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