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

Revision 4817, 16.3 KB checked in by sam, 11 months ago (diff)

win32: use sprintf_s and vsnprintf_s on Windows, so that our static library
works with the VS2010 runtime, too. Also reduce the stack size requirements
to avoid depending on chkstk_ms().

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