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

Last change on this file since 4806 was 4806, checked in by sam, 2 years ago

build: fix copyright information.

File size: 16.0 KB
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    if (!cv->ff)
100        return 0;
101
102    caca_charfont_t *ff = cv->ff;
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    if (!cv->ff)
115        return 0;
116
117    caca_charfont_t *ff = cv->ff;
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#if !defined __KERNEL__ && defined HAVE_SNPRINTF
301    char altpath[2048];
302#endif
303    char buf[2048];
304    char hardblank[10];
305    caca_charfont_t *ff;
306    char *data = NULL;
307    caca_file_t *f;
308    int i, j, size, comment_lines;
309
310    ff = malloc(sizeof(caca_charfont_t));
311    if(!ff)
312    {
313        seterrno(ENOMEM);
314        return NULL;
315    }
316
317    /* Open font: if not found, try .tlf, then .flf */
318    f = caca_file_open(path, "r");
319#if !defined __KERNEL__ && defined HAVE_SNPRINTF
320
321#if (! defined(snprintf)) && ( defined(_WIN32) || defined(WIN32) ) && (! defined(__CYGWIN__))
322#define snprintf _snprintf
323#endif
324
325    if(!f)
326    {
327        snprintf(altpath, 2047, "%s.tlf", path);
328        altpath[2047] = '\0';
329        f = caca_file_open(altpath, "r");
330    }
331    if(!f)
332    {
333        snprintf(altpath, 2047, "%s.flf", path);
334        altpath[2047] = '\0';
335        f = caca_file_open(altpath, "r");
336    }
337#endif
338    if(!f)
339    {
340        free(ff);
341        seterrno(ENOENT);
342        return NULL;
343    }
344
345    /* Read header */
346    ff->print_direction = 0;
347    ff->full_layout = 0;
348    ff->codetag_count = 0;
349    caca_file_gets(f, buf, 2048);
350    if(sscanf(buf, "%*[ft]lf2a%6s %u %u %u %i %u %u %u %u\n", hardblank,
351              &ff->height, &ff->baseline, &ff->max_length,
352              &ff->old_layout, &comment_lines, &ff->print_direction,
353              &ff->full_layout, &ff->codetag_count) < 6)
354    {
355        debug("figfont error: `%s' has invalid header: %s", path, buf);
356        caca_file_close(f);
357        free(ff);
358        seterrno(EINVAL);
359        return NULL;
360    }
361
362    if(ff->old_layout < -1 || ff->old_layout > 63 || ff->full_layout > 32767
363        || ((ff->full_layout & 0x80) && (ff->full_layout & 0x3f) == 0
364            && ff->old_layout))
365    {
366        debug("figfont error: `%s' has invalid layout %i/%u",
367                path, ff->old_layout, ff->full_layout);
368        caca_file_close(f);
369        free(ff);
370        seterrno(EINVAL);
371        return NULL;
372    }
373
374    ff->hardblank = caca_utf8_to_utf32(hardblank, NULL);
375
376    /* Skip comment lines */
377    for(i = 0; i < comment_lines; i++)
378        caca_file_gets(f, buf, 2048);
379
380    /* Read mandatory characters (32-127, 196, 214, 220, 228, 246, 252, 223)
381     * then read additional characters. */
382    ff->glyphs = 0;
383    ff->lookup = NULL;
384
385    for(i = 0, size = 0; !caca_file_eof(f); ff->glyphs++)
386    {
387        if((ff->glyphs % 2048) == 0)
388            ff->lookup = realloc(ff->lookup,
389                                   (ff->glyphs + 2048) * 2 * sizeof(int));
390
391        if(ff->glyphs < STD_GLYPHS)
392        {
393            ff->lookup[ff->glyphs * 2] = 32 + ff->glyphs;
394        }
395        else if(ff->glyphs < EXT_GLYPHS)
396        {
397            static int const tab[7] = { 196, 214, 220, 228, 246, 252, 223 };
398            ff->lookup[ff->glyphs * 2] = tab[ff->glyphs - STD_GLYPHS];
399        }
400        else
401        {
402            unsigned int tmp;
403
404            if(caca_file_gets(f, buf, 2048) == NULL)
405                break;
406
407            /* Ignore blank lines, as in jacky.flf */
408            if(buf[0] == '\n' || buf[0] == '\r')
409                continue;
410
411            /* Ignore negative indices for now, as in ivrit.flf */
412            if(buf[0] == '-')
413            {
414                for(j = 0; j < ff->height; j++)
415                    caca_file_gets(f, buf, 2048);
416                continue;
417            }
418
419            if(!buf[0] || buf[0] < '0' || buf[0] > '9')
420            {
421                debug("figfont error: glyph #%u in `%s'", ff->glyphs, path);
422                free(data);
423                free(ff->lookup);
424                free(ff);
425                seterrno(EINVAL);
426                return NULL;
427            }
428
429            sscanf(buf, buf[1] == 'x' ? "%x" : "%u", &tmp);
430            ff->lookup[ff->glyphs * 2] = tmp;
431        }
432
433        ff->lookup[ff->glyphs * 2 + 1] = 0;
434
435        for(j = 0; j < ff->height; j++)
436        {
437            if(i + 2048 >= size)
438                data = realloc(data, size += 2048);
439
440            caca_file_gets(f, data + i, 2048);
441            i = (uintptr_t)strchr(data + i, 0) - (uintptr_t)data;
442        }
443    }
444
445    caca_file_close(f);
446
447    if(ff->glyphs < EXT_GLYPHS)
448    {
449        debug("figfont error: only %u glyphs in `%s', expected at least %u",
450                        ff->glyphs, path, EXT_GLYPHS);
451        free(data);
452        free(ff->lookup);
453        free(ff);
454        seterrno(EINVAL);
455        return NULL;
456    }
457
458    /* Import buffer into canvas */
459    ff->fontcv = caca_create_canvas(0, 0);
460    caca_import_canvas_from_memory(ff->fontcv, data, i, "utf8");
461    free(data);
462
463    /* Remove EOL characters. For now we ignore hardblanks, don’t do any
464     * smushing, nor any kind of error checking. */
465    for(j = 0; j < ff->height * ff->glyphs; j++)
466    {
467        uint32_t ch, oldch = 0;
468
469        for(i = ff->max_length; i--;)
470        {
471            ch = caca_get_char(ff->fontcv, i, j);
472
473            /* Replace hardblanks with U+00A0 NO-BREAK SPACE */
474            if(ch == ff->hardblank)
475                caca_put_char(ff->fontcv, i, j, ch = 0xa0);
476
477            if(oldch && ch != oldch)
478            {
479                if(!ff->lookup[j / ff->height * 2 + 1])
480                    ff->lookup[j / ff->height * 2 + 1] = i + 1;
481            }
482            else if(oldch && ch == oldch)
483                caca_put_char(ff->fontcv, i, j, ' ');
484            else if(ch != ' ')
485            {
486                oldch = ch;
487                caca_put_char(ff->fontcv, i, j, ' ');
488            }
489        }
490    }
491
492    return ff;
493}
494
495int free_charfont(caca_charfont_t *ff)
496{
497    caca_free_canvas(ff->fontcv);
498    free(ff->lookup);
499    free(ff);
500
501    return 0;
502}
503
504static void update_figfont_settings(caca_canvas_t *cv)
505{
506    if (!cv->ff)
507        return;
508
509    caca_charfont_t *ff = cv->ff;
510
511    /* from TOIlet’s figlet.c */
512    if (ff->full_layout & 0x3f)
513        ff->hsmushrule = ff->full_layout & 0x3f;
514    else if (ff->old_layout > 0)
515        ff->hsmushrule = ff->old_layout;
516
517    switch (ff->hmode)
518    {
519    case H_DEFAULT:
520        if (ff->old_layout == -1)
521            ff->hmode = H_NONE;
522        else if (ff->old_layout == 0 && (ff->full_layout & 0xc0) == 0x40)
523            ff->hmode = H_KERN;
524        else if ((ff->old_layout & 0x3f) && (ff->full_layout & 0x3f)
525                 && (ff->full_layout & 0x80))
526        {
527            ff->hmode = H_SMUSH;
528            ff->hsmushrule = ff->full_layout & 0x3f;
529        }
530        else if (ff->old_layout == 0 && (ff->full_layout & 0xbf) == 0x80)
531        {
532            ff->hmode = H_SMUSH;
533            ff->hsmushrule = 0x3f;
534        }
535        else
536            ff->hmode = H_OVERLAP;
537        break;
538    default:
539        break;
540    }
541
542    if (ff->charcv)
543        caca_free_canvas(ff->charcv);
544    ff->charcv = caca_create_canvas(ff->max_length - 2, ff->height);
545
546    free(ff->left);
547    free(ff->right);
548    ff->left = malloc(ff->height * sizeof(int));
549    ff->right = malloc(ff->height * sizeof(int));
550}
551
552static uint32_t hsmush(uint32_t ch1, uint32_t ch2, int rule)
553{
554    /* Rule 1 */
555    if((rule & 0x01) && ch1 == ch2 && ch1 != 0xa0)
556        return ch2;
557
558    if(ch1 < 0x80 && ch2 < 0x80)
559    {
560        char const charlist[] = "|/\\[]{}()<>";
561        char *tmp1, *tmp2;
562
563        /* Rule 2 */
564        if(rule & 0x02)
565        {
566            if(ch1 == '_' && strchr(charlist, ch2))
567                return ch2;
568
569            if(ch2 == '_' && strchr(charlist, ch1))
570                return ch1;
571        }
572
573        /* Rule 3 */
574        if((rule & 0x04) &&
575           (tmp1 = strchr(charlist, ch1)) && (tmp2 = strchr(charlist, ch2)))
576        {
577            int cl1 = (tmp1 + 1 - charlist) / 2;
578            int cl2 = (tmp2 + 1 - charlist) / 2;
579
580            if(cl1 < cl2)
581                return ch2;
582            if(cl1 > cl2)
583                return ch1;
584        }
585
586        /* Rule 4 */
587        if(rule & 0x08)
588        {
589            uint16_t s = ch1 + ch2;
590            uint16_t p = ch1 * ch2;
591
592            if(p == 15375 /* '{' * '}' */
593                || p == 8463 /* '[' * ']' */
594                || (p == 1640 && s == 81)) /* '(' *|+ ')' */
595                return '|';
596        }
597
598        /* Rule 5 */
599        if(rule & 0x10)
600        {
601            switch((ch1 << 8) | ch2)
602            {
603                case 0x2f5c: return '|'; /* /\ */
604                case 0x5c2f: return 'Y'; /* \/ */
605                case 0x3e3c: return 'X'; /* >< */
606            }
607        }
608
609        /* Rule 6 */
610        if((rule & 0x20) && ch1 == ch2 && ch1 == 0xa0)
611            return 0xa0;
612    }
613
614    return 0;
615}
616
617/*
618 * XXX: The following functions are aliases.
619 */
620
621int cucul_canvas_set_figfont(cucul_canvas_t *, char const *)
622         CACA_ALIAS(caca_canvas_set_figfont);
623int cucul_put_figchar(cucul_canvas_t *, uint32_t) CACA_ALIAS(caca_put_figchar);
624int cucul_flush_figlet(cucul_canvas_t *) CACA_ALIAS(caca_flush_figlet);
625
Note: See TracBrowser for help on using the repository browser.