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

Last change on this file since 2821 was 2821, checked in by Sam Hocevar, 11 years ago

Starting refactoring to get rid of libcucul. The initial reason for the
split is rendered moot by the plugin system: when enabled, binaries do
not link directly with libX11 or libGL. I hope this is a step towards
more consisteny and clarity.

File size: 14.4 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    char altpath[2048];
289    char buf[2048];
290    char hardblank[10];
291    caca_figfont_t *ff;
292    char *data = NULL;
293    caca_file_t *f;
294    int i, j, size, comment_lines;
295
296    ff = malloc(sizeof(caca_figfont_t));
297    if(!ff)
298    {
299        seterrno(ENOMEM);
300        return NULL;
301    }
302
303    /* Open font: if not found, try .tlf, then .flf */
304    f = caca_file_open(path, "r");
305#if !defined __KERNEL__ && defined HAVE_SNPRINTF
306
307#if (! defined(snprintf)) && ( defined(_WIN32) || defined(WIN32) ) && (! defined(__CYGWIN__))
308#define snprintf _snprintf
309#endif
310
311    if(!f)
312    {
313        snprintf(altpath, 2047, "%s.tlf", path);
314        altpath[2047] = '\0';
315        f = caca_file_open(altpath, "r");
316    }
317    if(!f)
318    {
319        snprintf(altpath, 2047, "%s.flf", path);
320        altpath[2047] = '\0';
321        f = caca_file_open(altpath, "r");
322    }
323#endif
324    if(!f)
325    {
326        free(ff);
327        seterrno(ENOENT);
328        return NULL;
329    }
330
331    /* Read header */
332    ff->print_direction = 0;
333    ff->full_layout = 0;
334    ff->codetag_count = 0;
335    caca_file_gets(f, buf, 2048);
336    if(sscanf(buf, "%*[ft]lf2a%6s %u %u %u %i %u %u %u %u\n", hardblank,
337              &ff->height, &ff->baseline, &ff->max_length,
338              &ff->old_layout, &comment_lines, &ff->print_direction,
339              &ff->full_layout, &ff->codetag_count) < 6)
340    {
341        debug("figfont error: `%s' has invalid header: %s", path, buf);
342        caca_file_close(f);
343        free(ff);
344        seterrno(EINVAL);
345        return NULL;
346    }
347
348    if(ff->old_layout < -1 || ff->old_layout > 63 || ff->full_layout > 32767
349        || ((ff->full_layout & 0x80) && (ff->full_layout & 0x3f) == 0
350            && ff->old_layout))
351    {
352        debug("figfont error: `%s' has invalid layout %i/%u",
353                path, ff->old_layout, ff->full_layout);
354        caca_file_close(f);
355        free(ff);
356        seterrno(EINVAL);
357        return NULL;
358    }
359
360    ff->hardblank = caca_utf8_to_utf32(hardblank, NULL);
361
362    /* Skip comment lines */
363    for(i = 0; i < comment_lines; i++)
364        caca_file_gets(f, buf, 2048);
365
366    /* Read mandatory characters (32-127, 196, 214, 220, 228, 246, 252, 223)
367     * then read additional characters. */
368    ff->glyphs = 0;
369    ff->lookup = NULL;
370
371    for(i = 0, size = 0; !caca_file_eof(f); ff->glyphs++)
372    {
373        if((ff->glyphs % 2048) == 0)
374            ff->lookup = realloc(ff->lookup,
375                                   (ff->glyphs + 2048) * 2 * sizeof(int));
376
377        if(ff->glyphs < STD_GLYPHS)
378        {
379            ff->lookup[ff->glyphs * 2] = 32 + ff->glyphs;
380        }
381        else if(ff->glyphs < EXT_GLYPHS)
382        {
383            static int const tab[7] = { 196, 214, 220, 228, 246, 252, 223 };
384            ff->lookup[ff->glyphs * 2] = tab[ff->glyphs - STD_GLYPHS];
385        }
386        else
387        {
388            if(caca_file_gets(f, buf, 2048) == NULL)
389                break;
390
391            /* Ignore blank lines, as in jacky.flf */
392            if(buf[0] == '\n' || buf[0] == '\r')
393                continue;
394
395            /* Ignore negative indices for now, as in ivrit.flf */
396            if(buf[0] == '-')
397            {
398                for(j = 0; j < ff->height; j++)
399                    caca_file_gets(f, buf, 2048);
400                continue;
401            }
402
403            if(!buf[0] || buf[0] < '0' || buf[0] > '9')
404            {
405                debug("figfont error: glyph #%u in `%s'", ff->glyphs, path);
406                free(data);
407                free(ff->lookup);
408                free(ff);
409                seterrno(EINVAL);
410                return NULL;
411            }
412
413            if(buf[1] == 'x')
414                sscanf(buf, "%x", &ff->lookup[ff->glyphs * 2]);
415            else
416                sscanf(buf, "%u", &ff->lookup[ff->glyphs * 2]);
417        }
418
419        ff->lookup[ff->glyphs * 2 + 1] = 0;
420
421        for(j = 0; j < ff->height; j++)
422        {
423            if(i + 2048 >= size)
424                data = realloc(data, size += 2048);
425
426            caca_file_gets(f, data + i, 2048);
427            i = (uintptr_t)strchr(data + i, 0) - (uintptr_t)data;
428        }
429    }
430
431    caca_file_close(f);
432
433    if(ff->glyphs < EXT_GLYPHS)
434    {
435        debug("figfont error: only %u glyphs in `%s', expected at least %u",
436                        ff->glyphs, path, EXT_GLYPHS);
437        free(data);
438        free(ff->lookup);
439        free(ff);
440        seterrno(EINVAL);
441        return NULL;
442    }
443
444    /* Import buffer into canvas */
445    ff->fontcv = caca_create_canvas(0, 0);
446    caca_import_memory(ff->fontcv, data, i, "utf8");
447    free(data);
448
449    /* Remove EOL characters. For now we ignore hardblanks, don’t do any
450     * smushing, nor any kind of error checking. */
451    for(j = 0; j < ff->height * ff->glyphs; j++)
452    {
453        uint32_t ch, oldch = 0;
454
455        for(i = ff->max_length; i--;)
456        {
457            ch = caca_get_char(ff->fontcv, i, j);
458
459            /* Replace hardblanks with U+00A0 NO-BREAK SPACE */
460            if(ch == ff->hardblank)
461                caca_put_char(ff->fontcv, i, j, ch = 0xa0);
462
463            if(oldch && ch != oldch)
464            {
465                if(!ff->lookup[j / ff->height * 2 + 1])
466                    ff->lookup[j / ff->height * 2 + 1] = i + 1;
467            }
468            else if(oldch && ch == oldch)
469                caca_put_char(ff->fontcv, i, j, ' ');
470            else if(ch != ' ')
471            {
472                oldch = ch;
473                caca_put_char(ff->fontcv, i, j, ' ');
474            }
475        }
476    }
477
478    return ff;
479}
480
481int free_figfont(caca_figfont_t *ff)
482{
483    caca_free_canvas(ff->fontcv);
484    free(ff->lookup);
485    free(ff);
486
487    return 0;
488}
489
490static uint32_t hsmush(uint32_t ch1, uint32_t ch2, int rule)
491{
492    /* Rule 1 */
493    if((rule & 0x01) && ch1 == ch2 && ch1 != 0xa0)
494        return ch2;
495
496    if(ch1 < 0x80 && ch2 < 0x80)
497    {
498        char const charlist[] = "|/\\[]{}()<>";
499        char *tmp1, *tmp2;
500
501        /* Rule 2 */
502        if(rule & 0x02)
503        {
504            if(ch1 == '_' && strchr(charlist, ch2))
505                return ch2;
506
507            if(ch2 == '_' && strchr(charlist, ch1))
508                return ch1;
509        }
510
511        /* Rule 3 */
512        if((rule & 0x04) &&
513           (tmp1 = strchr(charlist, ch1)) && (tmp2 = strchr(charlist, ch2)))
514        {
515            int cl1 = (tmp1 + 1 - charlist) / 2;
516            int cl2 = (tmp2 + 1 - charlist) / 2;
517
518            if(cl1 < cl2)
519                return ch2;
520            if(cl1 > cl2)
521                return ch1;
522        }
523
524        /* Rule 4 */
525        if(rule & 0x08)
526        {
527            uint16_t s = ch1 + ch2;
528            uint16_t p = ch1 * ch2;
529
530            if(p == 15375 /* '{' * '}' */
531                || p == 8463 /* '[' * ']' */
532                || (p == 1640 && s == 81)) /* '(' *|+ ')' */
533                return '|';
534        }
535
536        /* Rule 5 */
537        if(rule & 0x10)
538        {
539            switch((ch1 << 8) | ch2)
540            {
541                case 0x2f5c: return '|'; /* /\ */
542                case 0x5c2f: return 'Y'; /* \/ */
543                case 0x3e3c: return 'X'; /* >< */
544            }
545        }
546
547        /* Rule 6 */
548        if((rule & 0x20) && ch1 == ch2 && ch1 == 0xa0)
549            return 0xa0;
550    }
551
552    return 0;
553}
554
Note: See TracBrowser for help on using the repository browser.