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

Revision 4803, 14.8 KB checked in by sam, 14 months ago (diff)

figfont: stick the source canvas's width, if specified.

RevLine 
[2110]1/*
[2821]2 *  libcaca       Colour ASCII-Art library
[4369]3 *  Copyright (c) 2002-2010 Sam Hocevar <sam@hocevar.net>
[2110]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
[2111]17/*
18 *  FIXME: this file needs huge cleanup to be usable
19 */
20
[2110]21#include "config.h"
22
23#if !defined(__KERNEL__)
24#   include <stdio.h>
25#   include <stdlib.h>
26#   include <string.h>
27#endif
28
[2821]29#include "caca.h"
30#include "caca_internals.h"
[2110]31
[4766]32struct caca_charfont
[2110]33{
[2305]34    int term_width;
[2111]35    int x, y, w, h, lines;
36
37    enum { H_DEFAULT, H_KERN, H_SMUSH, H_NONE, H_OVERLAP } hmode;
[2305]38    int hsmushrule;
[2303]39    uint32_t hardblank;
[2305]40    int height, baseline, max_length;
[2110]41    int old_layout;
[2305]42    int print_direction, full_layout, codetag_count;
43    int glyphs;
[2821]44    caca_canvas_t *fontcv, *charcv;
[2111]45    int *left, *right; /* Unused yet */
[2305]46    uint32_t *lookup;
[2110]47};
48
[2305]49static uint32_t hsmush(uint32_t ch1, uint32_t ch2, int rule);
[4766]50static caca_charfont_t * open_charfont(char const *);
51static int free_charfont(caca_charfont_t *);
[2111]52
[4321]53/** \brief load a figfont and attach it to a canvas */
[2821]54int caca_canvas_set_figfont(caca_canvas_t *cv, char const *path)
[2110]55{
[4766]56    caca_charfont_t *ff = NULL;
[2110]57
58    if(path)
59    {
[4766]60        ff = open_charfont(path);
[2110]61        if(!ff)
62            return -1;
63    }
64
65    if(cv->ff)
[2111]66    {
[2821]67        caca_free_canvas(cv->ff->charcv);
[2111]68        free(cv->ff->left);
69        free(cv->ff->right);
[4766]70        free_charfont(cv->ff);
[2111]71    }
[2110]72
73    cv->ff = ff;
74
[2111]75    if(!path)
76        return 0;
77
78    /* from TOIlet’s main.c */
[4803]79    ff->term_width = cv->width > 0 ? cv->width : 80;
[2111]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;
[2821]86    caca_set_canvas_size(cv, 0, 0); /* XXX */
[2111]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
[2821]119    ff->charcv = caca_create_canvas(ff->max_length - 2, ff->height);
[2111]120
121    ff->left = malloc(ff->height * sizeof(int));
122    ff->right = malloc(ff->height * sizeof(int));
123
124    cv->ff = ff;
125
[2110]126    return 0;
127}
128
[4321]129/** \brief paste a character using the current figfont */
[2821]130int caca_put_figchar(caca_canvas_t *cv, uint32_t ch)
[2111]131{
[4766]132    caca_charfont_t *ff = cv->ff;
[2305]133    int c, w, h, x, y, overlap, extra, xleft, xright;
[2111]134
[3582]135    if (!ff)
[3130]136        return -1;
137
[2111]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
[2821]160    caca_set_canvas_handle(ff->fontcv, 0, c * ff->height);
161    caca_blit(ff->charcv, 0, 0, ff->fontcv, NULL);
[2111]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++)
[2821]182                if(caca_get_char(ff->charcv, xright, y) != ' ')
[2111]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++)
[2821]187                if(caca_get_char(cv, ff->x - 1 - xleft, ff->y + y) != ' ')
[2111]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 &&
[2821]198                    hsmush(caca_get_char(cv, ff->x - 1 - xleft, ff->y + y),
199                          caca_get_char(ff->charcv, xright, y),
[2111]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)
[2821]225        caca_set_attr(cv, attr);
[2111]226#endif
[2821]227    caca_set_canvas_size(cv, ff->w, ff->h);
[2111]228
[2821]229    /* Render our char (FIXME: create a rect-aware caca_blit_canvas?) */
[2111]230    for(y = 0; y < h; y++)
231        for(x = 0; x < w; x++)
232    {
233        uint32_t ch1, ch2;
[4304]234        uint32_t tmpat = caca_get_attr(ff->fontcv, x, y + c * ff->height);
[2821]235        ch2 = caca_get_char(ff->charcv, x, y);
[2111]236        if(ch2 == ' ')
237            continue;
[2821]238        ch1 = caca_get_char(cv, ff->x + x - overlap, ff->y + y);
[2111]239        if(ch1 == ' ' || ff->hmode != H_SMUSH)
[2821]240            caca_put_char(cv, ff->x + x - overlap, ff->y + y, ch2);
[2111]241        else
[2821]242            caca_put_char(cv, ff->x + x - overlap, ff->y + y,
[2111]243                           hsmush(ch1, ch2, ff->hsmushrule));
[4304]244        caca_put_attr(cv, ff->x + x, ff->y + y, tmpat);
[2111]245    }
246
247    /* Advance cursor */
248    ff->x += w - overlap;
249
250    return 0;
251}
252
[4321]253/** \brief flush the figlet context */
[2821]254int caca_flush_figlet(caca_canvas_t *cv)
[2111]255{
[4766]256    caca_charfont_t *ff = cv->ff;
[2305]257    int x, y;
[2111]258
[4139]259    if (!ff)
260        return -1;
261
[2111]262    //ff->torender = cv;
[2821]263    //caca_set_canvas_size(ff->torender, ff->w, ff->h);
264    caca_set_canvas_size(cv, ff->w, ff->h);
[2111]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++)
[2821]269            if(caca_get_char(cv, x, y) == 0xa0)
[2111]270            {
[2821]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);
[2111]274            }
275
276    ff->x = ff->y = 0;
277    ff->w = ff->h = 0;
278
[2821]279    //cv = caca_create_canvas(1, 1); /* XXX */
[2111]280
281    /* from render.c */
[2821]282    ff->lines += caca_get_canvas_height(cv);
[2111]283
284    return 0;
285}
286
[2110]287#define STD_GLYPHS (127 - 32)
288#define EXT_GLYPHS (STD_GLYPHS + 7)
289
[4766]290static caca_charfont_t * open_charfont(char const *path)
[2110]291{
[2960]292#if !defined __KERNEL__ && defined HAVE_SNPRINTF
[2110]293    char altpath[2048];
[2960]294#endif
[2110]295    char buf[2048];
296    char hardblank[10];
[4766]297    caca_charfont_t *ff;
[2110]298    char *data = NULL;
[2821]299    caca_file_t *f;
[2305]300    int i, j, size, comment_lines;
[2110]301
[4766]302    ff = malloc(sizeof(caca_charfont_t));
[2110]303    if(!ff)
304    {
305        seterrno(ENOMEM);
306        return NULL;
307    }
308
309    /* Open font: if not found, try .tlf, then .flf */
[2821]310    f = caca_file_open(path, "r");
[2144]311#if !defined __KERNEL__ && defined HAVE_SNPRINTF
[2225]312
313#if (! defined(snprintf)) && ( defined(_WIN32) || defined(WIN32) ) && (! defined(__CYGWIN__))
314#define snprintf _snprintf
315#endif
316
[2110]317    if(!f)
318    {
[2225]319        snprintf(altpath, 2047, "%s.tlf", path);
[2110]320        altpath[2047] = '\0';
[2821]321        f = caca_file_open(altpath, "r");
[2110]322    }
323    if(!f)
324    {
[2225]325        snprintf(altpath, 2047, "%s.flf", path);
[2110]326        altpath[2047] = '\0';
[2821]327        f = caca_file_open(altpath, "r");
[2110]328    }
[2144]329#endif
[2110]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;
[2821]341    caca_file_gets(f, buf, 2048);
[2110]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);
[2821]348        caca_file_close(f);
[2110]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);
[2821]360        caca_file_close(f);
[2110]361        free(ff);
362        seterrno(EINVAL);
363        return NULL;
364    }
365
[2821]366    ff->hardblank = caca_utf8_to_utf32(hardblank, NULL);
[2110]367
368    /* Skip comment lines */
369    for(i = 0; i < comment_lines; i++)
[2821]370        caca_file_gets(f, buf, 2048);
[2110]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
[2821]377    for(i = 0, size = 0; !caca_file_eof(f); ff->glyphs++)
[2110]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        {
[3484]394            unsigned int tmp;
395
[2821]396            if(caca_file_gets(f, buf, 2048) == NULL)
[2110]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++)
[2821]407                    caca_file_gets(f, buf, 2048);
[2110]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
[3484]421            sscanf(buf, buf[1] == 'x' ? "%x" : "%u", &tmp);
422            ff->lookup[ff->glyphs * 2] = tmp;
[2110]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
[2821]432            caca_file_gets(f, data + i, 2048);
[2110]433            i = (uintptr_t)strchr(data + i, 0) - (uintptr_t)data;
434        }
435    }
436
[2821]437    caca_file_close(f);
[2110]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 */
[2821]451    ff->fontcv = caca_create_canvas(0, 0);
[3495]452    caca_import_canvas_from_memory(ff->fontcv, data, i, "utf8");
[2110]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    {
[2303]459        uint32_t ch, oldch = 0;
[2110]460
461        for(i = ff->max_length; i--;)
462        {
[2821]463            ch = caca_get_char(ff->fontcv, i, j);
[2110]464
465            /* Replace hardblanks with U+00A0 NO-BREAK SPACE */
466            if(ch == ff->hardblank)
[2821]467                caca_put_char(ff->fontcv, i, j, ch = 0xa0);
[2110]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)
[2821]475                caca_put_char(ff->fontcv, i, j, ' ');
[2110]476            else if(ch != ' ')
477            {
478                oldch = ch;
[2821]479                caca_put_char(ff->fontcv, i, j, ' ');
[2110]480            }
481        }
482    }
483
484    return ff;
485}
486
[4766]487int free_charfont(caca_charfont_t *ff)
[2110]488{
[2821]489    caca_free_canvas(ff->fontcv);
[2110]490    free(ff->lookup);
491    free(ff);
492
493    return 0;
494}
495
[2305]496static uint32_t hsmush(uint32_t ch1, uint32_t ch2, int rule)
[2111]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
[2826]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.