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

Revision 4822, 16.7 KB checked in by sam, 12 months ago (diff)

win32: define a custom sprintf_s() weak symbol. The VS2010 runtime does not
provide the deprecated snprintf(). The mingw32 runtime does not provide the
MS-specific sprintf_s(). Mingw-w64 copes with both. So we switch to sprintf_s
but also provide it as a weak symbol so that mingw32 does not complain.

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